niceeval 0.15.1-canary.76 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (714) hide show
  1. package/INDEX.md +45 -40
  2. package/dist/adapter-execution-trace.cjs.map +1 -1
  3. package/dist/adapter-execution-trace.d.cts +26 -0
  4. package/dist/adapter-execution-trace.d.mts +26 -0
  5. package/dist/adapter-execution-trace.d.ts +26 -0
  6. package/dist/adapter-usage.cjs.map +1 -1
  7. package/dist/adapter-usage.d.cts +9 -0
  8. package/dist/adapter-usage.d.mts +9 -0
  9. package/dist/adapter-usage.d.ts +9 -0
  10. package/dist/adapter.cjs +19 -4
  11. package/dist/adapter.cjs.map +1 -1
  12. package/dist/adapter.d.cts +37 -12
  13. package/dist/adapter.d.mts +37 -12
  14. package/dist/adapter.d.ts +37 -12
  15. package/dist/agents/ai-sdk.cjs +3 -0
  16. package/dist/agents/ai-sdk.cjs.map +1 -1
  17. package/dist/agents/bub.cjs +3 -0
  18. package/dist/agents/bub.cjs.map +1 -1
  19. package/dist/agents/claude-code-native.cjs +28 -2
  20. package/dist/agents/claude-code-native.cjs.map +1 -1
  21. package/dist/agents/claude-code.cjs +3 -0
  22. package/dist/agents/claude-code.cjs.map +1 -1
  23. package/dist/agents/codex.cjs +3 -0
  24. package/dist/agents/codex.cjs.map +1 -1
  25. package/dist/agents/coding-cli-versions.cjs +9 -10
  26. package/dist/agents/coding-cli-versions.cjs.map +1 -1
  27. package/dist/agents/coding-cli-versions.d.cts +1 -1
  28. package/dist/agents/coding-cli-versions.d.mts +1 -1
  29. package/dist/agents/coding-cli-versions.d.ts +1 -1
  30. package/dist/agents/deepseek-harness.cjs +3 -0
  31. package/dist/agents/deepseek-harness.cjs.map +1 -1
  32. package/dist/agents/hermes.cjs +3 -0
  33. package/dist/agents/hermes.cjs.map +1 -1
  34. package/dist/agents/index.cjs.map +1 -1
  35. package/dist/agents/index.d.cts +3 -2
  36. package/dist/agents/index.d.mts +3 -2
  37. package/dist/agents/index.d.ts +3 -2
  38. package/dist/agents/omp.cjs +3 -0
  39. package/dist/agents/omp.cjs.map +1 -1
  40. package/dist/agents/openai-compat.cjs +6 -0
  41. package/dist/agents/openai-compat.cjs.map +1 -1
  42. package/dist/agents/openclaw.cjs +3 -0
  43. package/dist/agents/openclaw.cjs.map +1 -1
  44. package/dist/agents/opencode.cjs +3 -0
  45. package/dist/agents/opencode.cjs.map +1 -1
  46. package/dist/agents/sdk-streams.cjs +9 -0
  47. package/dist/agents/sdk-streams.cjs.map +1 -1
  48. package/dist/agents/ui-message-stream.cjs +3 -0
  49. package/dist/agents/ui-message-stream.cjs.map +1 -1
  50. package/dist/assertions/api.cjs.map +1 -1
  51. package/dist/assertions/api.d.cts +27 -5
  52. package/dist/assertions/api.d.mts +27 -5
  53. package/dist/assertions/api.d.ts +27 -5
  54. package/dist/assertions/collection.cjs +6 -0
  55. package/dist/assertions/collection.cjs.map +1 -1
  56. package/dist/assertions/collection.d.cts +4 -2
  57. package/dist/assertions/collection.d.mts +4 -2
  58. package/dist/assertions/collection.d.ts +4 -2
  59. package/dist/assertions/collection.js +12 -8
  60. package/dist/assertions/context-match.cjs +70 -0
  61. package/dist/assertions/context-match.cjs.map +1 -0
  62. package/dist/assertions/context-match.d.cts +107 -0
  63. package/dist/assertions/context-match.d.mts +107 -0
  64. package/dist/assertions/context-match.d.ts +107 -0
  65. package/dist/assertions/context-match.js +12 -0
  66. package/dist/assertions/context-match.js.map +1 -0
  67. package/dist/assertions/full-content.cjs +109 -0
  68. package/dist/assertions/full-content.cjs.map +1 -0
  69. package/dist/assertions/full-content.d.cts +6 -0
  70. package/dist/assertions/full-content.d.mts +6 -0
  71. package/dist/assertions/full-content.d.ts +6 -0
  72. package/dist/assertions/full-content.js +12 -0
  73. package/dist/assertions/full-content.js.map +1 -0
  74. package/dist/assertions/judge-presets.cjs +2 -34
  75. package/dist/assertions/judge-presets.cjs.map +1 -1
  76. package/dist/assertions/judge-presets.d.cts +6 -9
  77. package/dist/assertions/judge-presets.d.mts +6 -9
  78. package/dist/assertions/judge-presets.d.ts +6 -9
  79. package/dist/assertions/judge-presets.js +8 -8
  80. package/dist/assertions/judge.cjs +107 -29
  81. package/dist/assertions/judge.cjs.map +1 -1
  82. package/dist/assertions/judge.d.cts +2 -0
  83. package/dist/assertions/judge.d.mts +2 -0
  84. package/dist/assertions/judge.d.ts +2 -0
  85. package/dist/assertions/match.cjs +244 -5
  86. package/dist/assertions/match.cjs.map +1 -1
  87. package/dist/assertions/match.d.cts +43 -3
  88. package/dist/assertions/match.d.mts +43 -3
  89. package/dist/assertions/match.d.ts +43 -3
  90. package/dist/assertions/match.js +88 -78
  91. package/dist/assertions/material.cjs +520 -0
  92. package/dist/assertions/material.cjs.map +1 -0
  93. package/dist/assertions/material.d.cts +56 -0
  94. package/dist/assertions/material.d.mts +56 -0
  95. package/dist/assertions/material.d.ts +56 -0
  96. package/dist/assertions/material.js +20 -0
  97. package/dist/assertions/material.js.map +1 -0
  98. package/dist/assertions/record/attachment.cjs +5 -2
  99. package/dist/assertions/record/attachment.cjs.map +1 -1
  100. package/dist/assertions/record/codec.cjs +2 -1
  101. package/dist/assertions/record/codec.cjs.map +1 -1
  102. package/dist/assertions/record/model.cjs.map +1 -1
  103. package/dist/assertions/record/model.d.cts +6 -1
  104. package/dist/assertions/record/model.d.mts +6 -1
  105. package/dist/assertions/record/model.d.ts +6 -1
  106. package/dist/assertions/runtime.cjs +207 -14
  107. package/dist/assertions/runtime.cjs.map +1 -1
  108. package/dist/assertions/runtime.d.cts +9 -0
  109. package/dist/assertions/runtime.d.mts +9 -0
  110. package/dist/assertions/runtime.d.ts +9 -0
  111. package/dist/assertions/runtime.js +14 -8
  112. package/dist/assertions/score-match-audit.cjs +18 -8
  113. package/dist/assertions/score-match-audit.cjs.map +1 -1
  114. package/dist/assertions/score-match-gateway.cjs +83 -25
  115. package/dist/assertions/score-match-gateway.cjs.map +1 -1
  116. package/dist/assertions/score-match-gateway.d.cts +2 -0
  117. package/dist/assertions/score-match-gateway.d.mts +2 -0
  118. package/dist/assertions/score-match-gateway.d.ts +2 -0
  119. package/dist/assertions/value-composition.test-d.cjs +77 -0
  120. package/dist/assertions/value-composition.test-d.cjs.map +1 -0
  121. package/dist/assertions/value-composition.test-d.d.cts +1 -0
  122. package/dist/assertions/value-composition.test-d.d.mts +1 -0
  123. package/dist/assertions/value-composition.test-d.d.ts +1 -0
  124. package/dist/assertions/value-composition.test-d.js +4 -0
  125. package/dist/assertions/value-composition.test-d.js.map +1 -0
  126. package/dist/context/assert-first.cjs +207 -213
  127. package/dist/context/assert-first.cjs.map +1 -1
  128. package/dist/context/assert-first.d.cts +49 -23
  129. package/dist/context/assert-first.d.mts +49 -23
  130. package/dist/context/assert-first.d.ts +49 -23
  131. package/dist/context/session.cjs +111 -9
  132. package/dist/context/session.cjs.map +1 -1
  133. package/dist/context/session.d.cts +18 -0
  134. package/dist/context/session.d.mts +18 -0
  135. package/dist/context/session.d.ts +18 -0
  136. package/dist/define.cjs +17 -1
  137. package/dist/define.cjs.map +1 -1
  138. package/dist/eval/cli/contribution.cjs +2 -0
  139. package/dist/eval/cli/contribution.cjs.map +1 -1
  140. package/dist/eval/host/index.cjs +2 -0
  141. package/dist/eval/host/index.cjs.map +1 -1
  142. package/dist/eval/record/evaluation.cjs +3 -0
  143. package/dist/eval/record/evaluation.cjs.map +1 -1
  144. package/dist/eval/record/membership-provenance.d.cts +15 -15
  145. package/dist/eval/record/membership-provenance.d.mts +15 -15
  146. package/dist/eval/record/membership-provenance.d.ts +15 -15
  147. package/dist/eval/record/score.cjs +7 -0
  148. package/dist/eval/record/score.cjs.map +1 -1
  149. package/dist/eval/record/sealed-assertion.cjs +3 -0
  150. package/dist/eval/record/sealed-assertion.cjs.map +1 -1
  151. package/dist/eval/record/verdict.cjs +7 -0
  152. package/dist/eval/record/verdict.cjs.map +1 -1
  153. package/dist/expect/index.cjs +8 -2
  154. package/dist/expect/index.cjs.map +1 -1
  155. package/dist/expect/index.d.cts +9 -4
  156. package/dist/expect/index.d.mts +9 -4
  157. package/dist/expect/index.d.ts +9 -4
  158. package/dist/expect/index.js +72 -62
  159. package/dist/expect/index.mjs +72 -62
  160. package/dist/experiment/host/cli/contribution.cjs +37 -23
  161. package/dist/experiment/host/cli/contribution.cjs.map +1 -1
  162. package/dist/experiment/host/cli/contribution.d.cts +9 -1
  163. package/dist/experiment/host/cli/contribution.d.mts +9 -1
  164. package/dist/experiment/host/cli/contribution.d.ts +9 -1
  165. package/dist/experiment/host/current.cjs +74 -0
  166. package/dist/experiment/host/current.cjs.map +1 -0
  167. package/dist/experiment/host/current.d.cts +33 -0
  168. package/dist/experiment/host/current.d.mts +33 -0
  169. package/dist/experiment/host/current.d.ts +33 -0
  170. package/dist/experiment/host/current.js +10 -0
  171. package/dist/experiment/host/current.js.map +1 -0
  172. package/dist/experiment/host/index.cjs +17 -174
  173. package/dist/experiment/host/index.cjs.map +1 -1
  174. package/dist/experiment/host/index.d.cts +8 -54
  175. package/dist/experiment/host/index.d.mts +8 -54
  176. package/dist/experiment/host/index.d.ts +8 -54
  177. package/dist/experiment/host/operations.cjs +33 -10
  178. package/dist/experiment/host/operations.cjs.map +1 -1
  179. package/dist/experiment/host/operations.d.cts +29 -1
  180. package/dist/experiment/host/operations.d.mts +29 -1
  181. package/dist/experiment/host/operations.d.ts +29 -1
  182. package/dist/experiment/host/operations.js +6 -4
  183. package/dist/experiment/host/requirements.cjs +3 -0
  184. package/dist/experiment/host/requirements.cjs.map +1 -0
  185. package/dist/experiment/host/requirements.d.cts +3 -0
  186. package/dist/experiment/host/requirements.d.mts +3 -0
  187. package/dist/experiment/host/requirements.d.ts +3 -0
  188. package/dist/experiment/host/requirements.js +4 -0
  189. package/dist/experiment/host/requirements.js.map +1 -0
  190. package/dist/experiment/host/runtime.cjs +179 -0
  191. package/dist/experiment/host/runtime.cjs.map +1 -0
  192. package/dist/experiment/host/runtime.d.cts +79 -0
  193. package/dist/experiment/host/runtime.d.mts +79 -0
  194. package/dist/experiment/host/runtime.d.ts +79 -0
  195. package/dist/experiment/host/runtime.js +6 -0
  196. package/dist/experiment/host/runtime.js.map +1 -0
  197. package/dist/experiment/host/types.cjs.map +1 -1
  198. package/dist/experiment/host/types.d.cts +17 -16
  199. package/dist/experiment/host/types.d.mts +17 -16
  200. package/dist/experiment/host/types.d.ts +17 -16
  201. package/dist/fresh-import.cjs +2 -1
  202. package/dist/fresh-import.cjs.map +1 -1
  203. package/dist/index.cjs +4 -2
  204. package/dist/index.cjs.map +1 -1
  205. package/dist/index.d.cts +12 -6
  206. package/dist/index.d.mts +12 -6
  207. package/dist/index.d.ts +12 -6
  208. package/dist/index.js +32 -30
  209. package/dist/index.mjs +32 -30
  210. package/dist/inspection/artifacts.cjs +83 -54
  211. package/dist/inspection/artifacts.cjs.map +1 -1
  212. package/dist/inspection/artifacts.d.cts +8 -0
  213. package/dist/inspection/artifacts.d.mts +8 -0
  214. package/dist/inspection/artifacts.d.ts +8 -0
  215. package/dist/inspection/artifacts.js +2 -0
  216. package/dist/inspection/assertions.cjs +4 -1
  217. package/dist/inspection/assertions.cjs.map +1 -1
  218. package/dist/inspection/cli/contribution.cjs +27 -4
  219. package/dist/inspection/cli/contribution.cjs.map +1 -1
  220. package/dist/inspection/cli/contribution.d.cts +2 -1
  221. package/dist/inspection/cli/contribution.d.mts +2 -1
  222. package/dist/inspection/cli/contribution.d.ts +2 -1
  223. package/dist/inspection/execution-traces.cjs +114 -8
  224. package/dist/inspection/execution-traces.cjs.map +1 -1
  225. package/dist/inspection/execution-traces.d.cts +38 -0
  226. package/dist/inspection/execution-traces.d.mts +38 -0
  227. package/dist/inspection/execution-traces.d.ts +38 -0
  228. package/dist/inspection/facts.cjs +12 -0
  229. package/dist/inspection/facts.cjs.map +1 -1
  230. package/dist/inspection/limits.cjs +5 -1
  231. package/dist/inspection/limits.cjs.map +1 -1
  232. package/dist/inspection/limits.d.cts +4 -0
  233. package/dist/inspection/limits.d.mts +4 -0
  234. package/dist/inspection/limits.d.ts +4 -0
  235. package/dist/inspection/limits.js +6 -2
  236. package/dist/inspection/overview.cjs +74 -33
  237. package/dist/inspection/overview.cjs.map +1 -1
  238. package/dist/inspection/overview.d.cts +19 -1
  239. package/dist/inspection/overview.d.mts +19 -1
  240. package/dist/inspection/overview.d.ts +19 -1
  241. package/dist/inspection/overview.js +4 -2
  242. package/dist/inspection/project-input.cjs +9 -0
  243. package/dist/inspection/project-input.cjs.map +1 -0
  244. package/dist/inspection/project-input.d.cts +20 -0
  245. package/dist/inspection/project-input.d.mts +20 -0
  246. package/dist/inspection/project-input.d.ts +20 -0
  247. package/dist/inspection/project-input.js +8 -0
  248. package/dist/inspection/project-input.js.map +1 -0
  249. package/dist/inspection/project-result.cjs +60 -0
  250. package/dist/inspection/project-result.cjs.map +1 -0
  251. package/dist/inspection/project-result.d.cts +1264 -0
  252. package/dist/inspection/project-result.d.mts +1264 -0
  253. package/dist/inspection/project-result.d.ts +1264 -0
  254. package/dist/inspection/project-result.js +10 -0
  255. package/dist/inspection/project-result.js.map +1 -0
  256. package/dist/inspection/project-source.cjs +109 -0
  257. package/dist/inspection/project-source.cjs.map +1 -0
  258. package/dist/inspection/project-source.d.cts +10 -0
  259. package/dist/inspection/project-source.d.mts +10 -0
  260. package/dist/inspection/project-source.d.ts +10 -0
  261. package/dist/inspection/project-source.js +6 -0
  262. package/dist/inspection/project-source.js.map +1 -0
  263. package/dist/inspection/project.cjs +100 -0
  264. package/dist/inspection/project.cjs.map +1 -0
  265. package/dist/inspection/project.d.cts +3 -0
  266. package/dist/inspection/project.d.mts +3 -0
  267. package/dist/inspection/project.d.ts +3 -0
  268. package/dist/inspection/project.js +6 -0
  269. package/dist/inspection/project.js.map +1 -0
  270. package/dist/inspection/protocol.cjs +19 -9
  271. package/dist/inspection/protocol.cjs.map +1 -1
  272. package/dist/inspection/protocol.d.cts +5506 -38189
  273. package/dist/inspection/protocol.d.mts +5506 -38189
  274. package/dist/inspection/protocol.d.ts +5506 -38189
  275. package/dist/inspection/public.mjs +896 -84
  276. package/dist/inspection/query.cjs +2 -0
  277. package/dist/inspection/query.cjs.map +1 -1
  278. package/dist/inspection/results.cjs +234 -29
  279. package/dist/inspection/results.cjs.map +1 -1
  280. package/dist/inspection/results.d.cts +870 -13
  281. package/dist/inspection/results.d.mts +870 -13
  282. package/dist/inspection/results.d.ts +870 -13
  283. package/dist/inspection/results.js +36 -34
  284. package/dist/inspection/select.cjs +126 -14
  285. package/dist/inspection/select.cjs.map +1 -1
  286. package/dist/inspection/select.d.cts +1 -1
  287. package/dist/inspection/select.d.mts +1 -1
  288. package/dist/inspection/select.d.ts +1 -1
  289. package/dist/inspection/source.cjs +4 -0
  290. package/dist/inspection/source.cjs.map +1 -1
  291. package/dist/inspection/sources.cjs +5 -0
  292. package/dist/inspection/sources.cjs.map +1 -1
  293. package/dist/inspection/trace.cjs +82 -84
  294. package/dist/inspection/trace.cjs.map +1 -1
  295. package/dist/inspection/trace.d.cts +7 -2
  296. package/dist/inspection/trace.d.mts +7 -2
  297. package/dist/inspection/trace.d.ts +7 -2
  298. package/dist/model-slots.cjs +35 -0
  299. package/dist/model-slots.cjs.map +1 -0
  300. package/dist/model-slots.d.cts +19 -0
  301. package/dist/model-slots.d.mts +19 -0
  302. package/dist/model-slots.d.ts +19 -0
  303. package/dist/model-slots.js +8 -0
  304. package/dist/model-slots.js.map +1 -0
  305. package/dist/o11y/adapter-call-price.cjs +4 -0
  306. package/dist/o11y/adapter-call-price.cjs.map +1 -1
  307. package/dist/o11y/adapter-call-price.d.cts +24 -0
  308. package/dist/o11y/adapter-call-price.d.mts +24 -0
  309. package/dist/o11y/adapter-call-price.d.ts +24 -0
  310. package/dist/o11y/adapter-call-price.js +6 -2
  311. package/dist/o11y/adapter-usage-projection.cjs +158 -0
  312. package/dist/o11y/adapter-usage-projection.cjs.map +1 -0
  313. package/dist/o11y/adapter-usage-projection.d.cts +141 -0
  314. package/dist/o11y/adapter-usage-projection.d.mts +141 -0
  315. package/dist/o11y/adapter-usage-projection.d.ts +141 -0
  316. package/dist/o11y/adapter-usage-projection.js +20 -0
  317. package/dist/o11y/adapter-usage-projection.js.map +1 -0
  318. package/dist/o11y/eval-usage.cjs +149 -0
  319. package/dist/o11y/eval-usage.cjs.map +1 -0
  320. package/dist/o11y/eval-usage.d.cts +46 -0
  321. package/dist/o11y/eval-usage.d.mts +46 -0
  322. package/dist/o11y/eval-usage.d.ts +46 -0
  323. package/dist/o11y/eval-usage.js +8 -0
  324. package/dist/o11y/eval-usage.js.map +1 -0
  325. package/dist/o11y/judge-call-price.cjs +62 -0
  326. package/dist/o11y/judge-call-price.cjs.map +1 -0
  327. package/dist/o11y/judge-call-price.d.cts +3 -0
  328. package/dist/o11y/judge-call-price.d.mts +3 -0
  329. package/dist/o11y/judge-call-price.d.ts +3 -0
  330. package/dist/o11y/judge-call-price.js +6 -0
  331. package/dist/o11y/judge-call-price.js.map +1 -0
  332. package/dist/o11y/judge-usage-projection.cjs +134 -0
  333. package/dist/o11y/judge-usage-projection.cjs.map +1 -0
  334. package/dist/o11y/judge-usage-projection.d.cts +18 -0
  335. package/dist/o11y/judge-usage-projection.d.mts +18 -0
  336. package/dist/o11y/judge-usage-projection.d.ts +18 -0
  337. package/dist/o11y/judge-usage-projection.js +20 -0
  338. package/dist/o11y/judge-usage-projection.js.map +1 -0
  339. package/dist/o11y/judge-usage-receipt.cjs +135 -0
  340. package/dist/o11y/judge-usage-receipt.cjs.map +1 -0
  341. package/dist/o11y/judge-usage-receipt.d.cts +5 -0
  342. package/dist/o11y/judge-usage-receipt.d.mts +5 -0
  343. package/dist/o11y/judge-usage-receipt.d.ts +5 -0
  344. package/dist/o11y/judge-usage-receipt.js +6 -0
  345. package/dist/o11y/judge-usage-receipt.js.map +1 -0
  346. package/dist/o11y/judge-usage.cjs +171 -0
  347. package/dist/o11y/judge-usage.cjs.map +1 -0
  348. package/dist/o11y/judge-usage.d.cts +35 -0
  349. package/dist/o11y/judge-usage.d.mts +35 -0
  350. package/dist/o11y/judge-usage.d.ts +35 -0
  351. package/dist/o11y/judge-usage.js +6 -0
  352. package/dist/o11y/judge-usage.js.map +1 -0
  353. package/dist/plugin/contracts.cjs +2 -0
  354. package/dist/plugin/contracts.cjs.map +1 -1
  355. package/dist/plugin/link.cjs +7 -0
  356. package/dist/plugin/link.cjs.map +1 -1
  357. package/dist/record/attachment/protocol.cjs +8 -6
  358. package/dist/record/attachment/protocol.cjs.map +1 -1
  359. package/dist/record/attachment/protocol.d.cts +10 -0
  360. package/dist/record/attachment/protocol.d.mts +10 -0
  361. package/dist/record/attachment/protocol.d.ts +10 -0
  362. package/dist/record/attachment/protocol.js +38 -36
  363. package/dist/record/codec/seal-manifest.d.cts +3 -3
  364. package/dist/record/codec/seal-manifest.d.mts +3 -3
  365. package/dist/record/codec/seal-manifest.d.ts +3 -3
  366. package/dist/record/family/adapter-usage/definition.d.cts +1 -0
  367. package/dist/record/family/adapter-usage/definition.d.mts +1 -0
  368. package/dist/record/family/adapter-usage/definition.d.ts +1 -0
  369. package/dist/record/family/adapter-usage/persistence.cjs +1 -1
  370. package/dist/record/family/adapter-usage/persistence.cjs.map +1 -1
  371. package/dist/record/family/adapter-usage/persistence.d.cts +2 -1
  372. package/dist/record/family/adapter-usage/persistence.d.mts +2 -1
  373. package/dist/record/family/adapter-usage/persistence.d.ts +2 -1
  374. package/dist/record/family/adapter-usage/schema.cjs +21 -2
  375. package/dist/record/family/adapter-usage/schema.cjs.map +1 -1
  376. package/dist/record/family/adapter-usage/schema.d.cts +85 -0
  377. package/dist/record/family/adapter-usage/schema.d.mts +85 -0
  378. package/dist/record/family/adapter-usage/schema.d.ts +85 -0
  379. package/dist/record/family/adapter-usage/schema.js +18 -12
  380. package/dist/record/family/catalog.cjs +31 -27
  381. package/dist/record/family/catalog.cjs.map +1 -1
  382. package/dist/record/family/catalog.d.cts +94 -3
  383. package/dist/record/family/catalog.d.mts +94 -3
  384. package/dist/record/family/catalog.d.ts +94 -3
  385. package/dist/record/family/current.cjs +45 -42
  386. package/dist/record/family/current.cjs.map +1 -1
  387. package/dist/record/family/current.d.cts +240 -1
  388. package/dist/record/family/current.d.mts +240 -1
  389. package/dist/record/family/current.d.ts +240 -1
  390. package/dist/record/family/execution-traces/definition.d.cts +26 -0
  391. package/dist/record/family/execution-traces/definition.d.mts +26 -0
  392. package/dist/record/family/execution-traces/definition.d.ts +26 -0
  393. package/dist/record/family/execution-traces/definition.js +20 -14
  394. package/dist/record/family/execution-traces/schema.cjs +57 -1
  395. package/dist/record/family/execution-traces/schema.cjs.map +1 -1
  396. package/dist/record/family/execution-traces/schema.d.cts +114 -0
  397. package/dist/record/family/execution-traces/schema.d.mts +114 -0
  398. package/dist/record/family/execution-traces/schema.d.ts +114 -0
  399. package/dist/record/family/execution-traces/schema.js +18 -12
  400. package/dist/record/family/judge-usage/definition.cjs +12 -0
  401. package/dist/record/family/judge-usage/definition.cjs.map +1 -0
  402. package/dist/record/family/judge-usage/definition.d.cts +91 -0
  403. package/dist/record/family/judge-usage/definition.d.mts +91 -0
  404. package/dist/record/family/judge-usage/definition.d.ts +91 -0
  405. package/dist/record/family/judge-usage/definition.js +6 -0
  406. package/dist/record/family/judge-usage/definition.js.map +1 -0
  407. package/dist/record/family/judge-usage/persistence.cjs +10 -0
  408. package/dist/record/family/judge-usage/persistence.cjs.map +1 -0
  409. package/dist/record/family/judge-usage/persistence.d.cts +91 -0
  410. package/dist/record/family/judge-usage/persistence.d.mts +91 -0
  411. package/dist/record/family/judge-usage/persistence.d.ts +91 -0
  412. package/dist/record/family/judge-usage/persistence.js +6 -0
  413. package/dist/record/family/judge-usage/persistence.js.map +1 -0
  414. package/dist/record/family/judge-usage/schema.cjs +307 -0
  415. package/dist/record/family/judge-usage/schema.cjs.map +1 -0
  416. package/dist/record/family/judge-usage/schema.d.cts +351 -0
  417. package/dist/record/family/judge-usage/schema.d.mts +351 -0
  418. package/dist/record/family/judge-usage/schema.d.ts +351 -0
  419. package/dist/record/family/judge-usage/schema.js +18 -0
  420. package/dist/record/family/judge-usage/schema.js.map +1 -0
  421. package/dist/record/host/sqlite-host.cjs +55 -9
  422. package/dist/record/host/sqlite-host.cjs.map +1 -1
  423. package/dist/record/host/types.cjs.map +1 -1
  424. package/dist/record/host/types.d.cts +7 -0
  425. package/dist/record/host/types.d.mts +7 -0
  426. package/dist/record/host/types.d.ts +7 -0
  427. package/dist/record/model/run-context.cjs +7 -1
  428. package/dist/record/model/run-context.cjs.map +1 -1
  429. package/dist/record/model/run-context.d.cts +4 -0
  430. package/dist/record/model/run-context.d.mts +4 -0
  431. package/dist/record/model/run-context.d.ts +4 -0
  432. package/dist/record/model/seal-manifest.d.cts +1 -1
  433. package/dist/record/model/seal-manifest.d.mts +1 -1
  434. package/dist/record/model/seal-manifest.d.ts +1 -1
  435. package/dist/record/sqlite/client.cjs +18 -5
  436. package/dist/record/sqlite/client.cjs.map +1 -1
  437. package/dist/record/sqlite/client.d.cts +1 -1
  438. package/dist/record/sqlite/client.d.mts +1 -1
  439. package/dist/record/sqlite/client.d.ts +1 -1
  440. package/dist/record/sqlite/project-state-database.cjs +29 -8
  441. package/dist/record/sqlite/project-state-database.cjs.map +1 -1
  442. package/dist/run/host/runtime.cjs +4 -0
  443. package/dist/run/host/runtime.cjs.map +1 -1
  444. package/dist/run/storage/sqlite.cjs +20 -0
  445. package/dist/run/storage/sqlite.cjs.map +1 -1
  446. package/dist/runner/adapter-execution-trace.cjs +125 -2
  447. package/dist/runner/adapter-execution-trace.cjs.map +1 -1
  448. package/dist/runner/adapter-execution-trace.d.cts +1 -1
  449. package/dist/runner/adapter-execution-trace.d.mts +1 -1
  450. package/dist/runner/adapter-execution-trace.d.ts +1 -1
  451. package/dist/runner/adapter-usage.cjs +55 -7
  452. package/dist/runner/adapter-usage.cjs.map +1 -1
  453. package/dist/runner/adapter-usage.d.cts +9 -2
  454. package/dist/runner/adapter-usage.d.mts +9 -2
  455. package/dist/runner/adapter-usage.d.ts +9 -2
  456. package/dist/runner/adoption.cjs +9 -0
  457. package/dist/runner/adoption.cjs.map +1 -1
  458. package/dist/runner/attempt-cancellation.cjs +12 -0
  459. package/dist/runner/attempt-cancellation.cjs.map +1 -0
  460. package/dist/runner/attempt-cancellation.d.cts +6 -0
  461. package/dist/runner/attempt-cancellation.d.mts +6 -0
  462. package/dist/runner/attempt-cancellation.d.ts +6 -0
  463. package/dist/runner/attempt-cancellation.js +6 -0
  464. package/dist/runner/attempt-cancellation.js.map +1 -0
  465. package/dist/runner/attempt-resources.cjs +10 -4
  466. package/dist/runner/attempt-resources.cjs.map +1 -1
  467. package/dist/runner/attempt-resources.d.cts +2 -1
  468. package/dist/runner/attempt-resources.d.mts +2 -1
  469. package/dist/runner/attempt-resources.d.ts +2 -1
  470. package/dist/runner/attempt.cjs +1177 -1139
  471. package/dist/runner/attempt.cjs.map +1 -1
  472. package/dist/runner/config-identity.cjs +20 -1
  473. package/dist/runner/config-identity.cjs.map +1 -1
  474. package/dist/runner/config-identity.d.cts +5 -0
  475. package/dist/runner/config-identity.d.mts +5 -0
  476. package/dist/runner/config-identity.d.ts +5 -0
  477. package/dist/runner/discover.cjs +14 -0
  478. package/dist/runner/discover.cjs.map +1 -1
  479. package/dist/runner/discover.d.cts +3 -0
  480. package/dist/runner/discover.d.mts +3 -0
  481. package/dist/runner/discover.d.ts +3 -0
  482. package/dist/runner/feedback/human.cjs +9 -4
  483. package/dist/runner/feedback/human.cjs.map +1 -1
  484. package/dist/runner/fingerprint.cjs +1 -0
  485. package/dist/runner/fingerprint.cjs.map +1 -1
  486. package/dist/runner/judge-usage.cjs +18 -0
  487. package/dist/runner/judge-usage.cjs.map +1 -0
  488. package/dist/runner/judge-usage.d.cts +5 -0
  489. package/dist/runner/judge-usage.d.mts +5 -0
  490. package/dist/runner/judge-usage.d.ts +5 -0
  491. package/dist/runner/judge-usage.js +8 -0
  492. package/dist/runner/judge-usage.js.map +1 -0
  493. package/dist/runner/project-target.cjs.map +1 -1
  494. package/dist/runner/project-target.d.cts +2 -0
  495. package/dist/runner/project-target.d.mts +2 -0
  496. package/dist/runner/project-target.d.ts +2 -0
  497. package/dist/runner/record/planning.cjs +1 -0
  498. package/dist/runner/record/planning.cjs.map +1 -1
  499. package/dist/runner/record.cjs +4 -0
  500. package/dist/runner/record.cjs.map +1 -1
  501. package/dist/runner/reporters/braintrust.cjs +2 -0
  502. package/dist/runner/reporters/braintrust.cjs.map +1 -1
  503. package/dist/runner/reuse-diagnostics.cjs +9 -10
  504. package/dist/runner/reuse-diagnostics.cjs.map +1 -1
  505. package/dist/runner/reuse-diagnostics.d.cts +5 -6
  506. package/dist/runner/reuse-diagnostics.d.mts +5 -6
  507. package/dist/runner/reuse-diagnostics.d.ts +5 -6
  508. package/dist/runner/reuse-diagnostics.js +4 -4
  509. package/dist/runner/reuse-plan.cjs +100 -48
  510. package/dist/runner/reuse-plan.cjs.map +1 -1
  511. package/dist/runner/reuse-plan.d.cts +60 -3
  512. package/dist/runner/reuse-plan.d.mts +60 -3
  513. package/dist/runner/reuse-plan.d.ts +60 -3
  514. package/dist/runner/reuse-plan.js +14 -10
  515. package/dist/runner/run.cjs +8 -6
  516. package/dist/runner/run.cjs.map +1 -1
  517. package/dist/runner/session.cjs +1 -1
  518. package/dist/runner/session.cjs.map +1 -1
  519. package/dist/runner/session.d.cts +1 -1
  520. package/dist/runner/session.d.mts +1 -1
  521. package/dist/runner/session.d.ts +1 -1
  522. package/dist/runner/source-producer.cjs +3 -1
  523. package/dist/runner/source-producer.cjs.map +1 -1
  524. package/dist/runner/types.cjs.map +1 -1
  525. package/dist/runner/types.d.cts +26 -5
  526. package/dist/runner/types.d.mts +26 -5
  527. package/dist/runner/types.d.ts +26 -5
  528. package/dist/sandbox/action.cjs +3 -0
  529. package/dist/sandbox/action.cjs.map +1 -1
  530. package/dist/sandbox/commands.cjs +3 -0
  531. package/dist/sandbox/commands.cjs.map +1 -1
  532. package/dist/sandbox/compose.cjs +6 -0
  533. package/dist/sandbox/compose.cjs.map +1 -1
  534. package/dist/sandbox/docker-profile/registry.cjs +7 -0
  535. package/dist/sandbox/docker-profile/registry.cjs.map +1 -1
  536. package/dist/sandbox/docker-profile/runtime.cjs +3 -0
  537. package/dist/sandbox/docker-profile/runtime.cjs.map +1 -1
  538. package/dist/sandbox/docker-profile/schema.cjs +7 -0
  539. package/dist/sandbox/docker-profile/schema.cjs.map +1 -1
  540. package/dist/sandbox/incus.cjs +7 -0
  541. package/dist/sandbox/incus.cjs.map +1 -1
  542. package/dist/sandbox/layer.cjs +12 -0
  543. package/dist/sandbox/layer.cjs.map +1 -1
  544. package/dist/shared/adapter-cleanup.cjs +18 -0
  545. package/dist/shared/adapter-cleanup.cjs.map +1 -0
  546. package/dist/shared/adapter-cleanup.d.cts +4 -0
  547. package/dist/shared/adapter-cleanup.d.mts +4 -0
  548. package/dist/shared/adapter-cleanup.d.ts +4 -0
  549. package/dist/shared/adapter-cleanup.js +10 -0
  550. package/dist/shared/adapter-cleanup.js.map +1 -0
  551. package/dist/shared/attempt-lifecycle.cjs +3 -0
  552. package/dist/shared/attempt-lifecycle.cjs.map +1 -0
  553. package/dist/shared/attempt-lifecycle.d.cts +13 -0
  554. package/dist/shared/attempt-lifecycle.d.mts +13 -0
  555. package/dist/shared/attempt-lifecycle.d.ts +13 -0
  556. package/dist/shared/attempt-lifecycle.js +4 -0
  557. package/dist/shared/attempt-lifecycle.js.map +1 -0
  558. package/dist/show/contribution.cjs +52 -14
  559. package/dist/show/contribution.cjs.map +1 -1
  560. package/dist/show/contribution.d.cts +23 -1
  561. package/dist/show/contribution.d.mts +23 -1
  562. package/dist/show/contribution.d.ts +23 -1
  563. package/dist/show/model.cjs +8 -1
  564. package/dist/show/model.cjs.map +1 -1
  565. package/dist/show/model.d.cts +10 -1
  566. package/dist/show/model.d.mts +10 -1
  567. package/dist/show/model.d.ts +10 -1
  568. package/dist/show/render.cjs +287 -49
  569. package/dist/show/render.cjs.map +1 -1
  570. package/dist/show/render.d.cts +3 -0
  571. package/dist/show/render.d.mts +3 -0
  572. package/dist/show/render.d.ts +3 -0
  573. package/dist/sources/projector.cjs +3 -0
  574. package/dist/sources/projector.cjs.map +1 -1
  575. package/dist/types.cjs.map +1 -1
  576. package/dist/types.d.cts +1 -1
  577. package/dist/types.d.mts +1 -1
  578. package/dist/types.d.ts +1 -1
  579. package/dist/view/app-dist/.vite/manifest.json +2 -2
  580. package/dist/view/app-dist/assets/index-CmVoXbwG.js +63 -0
  581. package/dist/view/app-dist/assets/{quality-cost-scatter-CFB3wnkN.js → quality-cost-scatter-BuLISaH4.js} +1 -1
  582. package/dist/view/app-dist/index.html +1 -1
  583. package/dist/view/cli/contribution.cjs +54 -21
  584. package/dist/view/cli/contribution.cjs.map +1 -1
  585. package/dist/view/cli/contribution.d.cts +2 -1
  586. package/dist/view/cli/contribution.d.mts +2 -1
  587. package/dist/view/cli/contribution.d.ts +2 -1
  588. package/dist/view/function-runtime.mjs +13231 -11647
  589. package/dist/view/http-protocol.cjs +3 -0
  590. package/dist/view/http-protocol.cjs.map +1 -1
  591. package/dist/view/http-protocol.d.cts +8 -0
  592. package/dist/view/http-protocol.d.mts +8 -0
  593. package/dist/view/http-protocol.d.ts +8 -0
  594. package/dist/view/inspection-host.cjs +17 -1
  595. package/dist/view/inspection-host.cjs.map +1 -1
  596. package/dist/view/render.cjs +1 -0
  597. package/dist/view/render.cjs.map +1 -1
  598. package/dist/view/render.d.cts +2 -1
  599. package/dist/view/render.d.mts +2 -1
  600. package/dist/view/render.d.ts +2 -1
  601. package/dist/view/revision.cjs.map +1 -1
  602. package/dist/view/revision.d.cts +12 -0
  603. package/dist/view/revision.d.mts +12 -0
  604. package/dist/view/revision.d.ts +12 -0
  605. package/dist/view/server.cjs +10 -9
  606. package/dist/view/server.cjs.map +1 -1
  607. package/docs-site/zh/examples/ai-agent-application.mdx +1 -1
  608. package/docs-site/zh/explanation/adapter.mdx +2 -2
  609. package/docs-site/zh/explanation/assert.mdx +60 -24
  610. package/docs-site/zh/explanation/drive.mdx +1 -1
  611. package/docs-site/zh/explanation/evals.mdx +31 -29
  612. package/docs-site/zh/explanation/experiment.mdx +15 -15
  613. package/docs-site/zh/explanation/judge.mdx +23 -15
  614. package/docs-site/zh/explanation/overview.mdx +44 -21
  615. package/docs-site/zh/explanation/record.mdx +28 -13
  616. package/docs-site/zh/explanation/results-flow.mdx +37 -0
  617. package/docs-site/zh/explanation/runner.mdx +27 -6
  618. package/docs-site/zh/index.mdx +22 -115
  619. package/docs-site/zh/introduction.mdx +89 -61
  620. package/docs-site/zh/reference/cli.mdx +50 -26
  621. package/docs-site/zh/reference/define-agent.mdx +37 -5
  622. package/docs-site/zh/reference/define-eval.mdx +130 -29
  623. package/docs-site/zh/reference/define-experiment.mdx +251 -0
  624. package/docs-site/zh/reference/events.mdx +34 -30
  625. package/docs-site/zh/reference/expect.mdx +77 -2
  626. package/docs-site/zh/reference/official-adapters.mdx +2 -2
  627. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +43 -24
  628. package/docs-site/zh/troubleshooting/debugging.mdx +65 -42
  629. package/docs-site/zh/troubleshooting/recover-after-kill.mdx +35 -28
  630. package/docs-site/zh/tutorials/accept-results.mdx +34 -24
  631. package/docs-site/zh/tutorials/agent-feedback-loop.mdx +74 -43
  632. package/docs-site/zh/tutorials/agent-onboarding.mdx +27 -20
  633. package/docs-site/zh/tutorials/authoring.mdx +138 -121
  634. package/docs-site/zh/tutorials/ci-integration.mdx +46 -22
  635. package/docs-site/zh/tutorials/compare-runs.mdx +40 -13
  636. package/docs-site/zh/tutorials/concurrency.mdx +66 -62
  637. package/docs-site/zh/tutorials/configuration.mdx +17 -17
  638. package/docs-site/zh/tutorials/connect-otel.mdx +48 -37
  639. package/docs-site/zh/tutorials/connect-your-agent.mdx +80 -173
  640. package/docs-site/zh/tutorials/control-run-cost.mdx +63 -12
  641. package/docs-site/zh/tutorials/criteria-files.mdx +28 -21
  642. package/docs-site/zh/tutorials/custom-application.mdx +75 -55
  643. package/docs-site/zh/tutorials/dataset-fanout.mdx +32 -22
  644. package/docs-site/zh/tutorials/debug-lifecycle-plan.mdx +38 -31
  645. package/docs-site/zh/tutorials/docker-in-docker.mdx +83 -89
  646. package/docs-site/zh/tutorials/eval-groups.mdx +70 -89
  647. package/docs-site/zh/tutorials/evaluation-kinds.mdx +12 -5
  648. package/docs-site/zh/tutorials/experiment-services.mdx +243 -0
  649. package/docs-site/zh/tutorials/fixtures.mdx +42 -26
  650. package/docs-site/zh/tutorials/handle-execution-failures.mdx +41 -19
  651. package/docs-site/zh/tutorials/install-custom-sandbox-agent.mdx +21 -16
  652. package/docs-site/zh/tutorials/niceeval-taste-application.mdx +394 -0
  653. package/docs-site/zh/tutorials/niceeval-taste.mdx +190 -0
  654. package/docs-site/zh/tutorials/nixos-managed-dind.mdx +51 -39
  655. package/docs-site/zh/tutorials/plugins.mdx +55 -31
  656. package/docs-site/zh/tutorials/prebuilt-sandbox.mdx +204 -0
  657. package/docs-site/zh/tutorials/quickstart.mdx +91 -47
  658. package/docs-site/zh/tutorials/reporters.mdx +45 -30
  659. package/docs-site/zh/tutorials/rerun-and-cache.mdx +39 -40
  660. package/docs-site/zh/tutorials/sandbox-agent.mdx +65 -40
  661. package/docs-site/zh/tutorials/sandbox-providers.mdx +104 -282
  662. package/docs-site/zh/tutorials/sandbox-reuse.mdx +65 -97
  663. package/docs-site/zh/tutorials/select-and-preview.mdx +25 -20
  664. package/docs-site/zh/tutorials/verify-judge.mdx +49 -26
  665. package/docs-site/zh/tutorials/viewing-results.mdx +130 -80
  666. package/docs-site/zh/tutorials/write-experiment.mdx +116 -298
  667. package/docs-site/zh/tutorials/write-send.mdx +53 -52
  668. package/node_modules/undici/docs/docs/api/Agent.md +8 -8
  669. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +9 -6
  670. package/node_modules/undici/docs/docs/api/Errors.md +25 -0
  671. package/node_modules/undici/docs/docs/api/Interceptors.md +4 -3
  672. package/node_modules/undici/docs/docs/api/Pool.md +5 -6
  673. package/node_modules/undici/lib/api/readable.js +1 -2
  674. package/node_modules/undici/lib/core/request.js +23 -2
  675. package/node_modules/undici/lib/core/util.js +0 -39
  676. package/node_modules/undici/lib/dispatcher/client-h1.js +11 -4
  677. package/node_modules/undici/lib/dispatcher/client-h2.js +52 -8
  678. package/node_modules/undici/lib/dispatcher/client.js +1 -1
  679. package/node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js +3 -2
  680. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +21 -6
  681. package/node_modules/undici/lib/dispatcher/pool-base.js +63 -2
  682. package/node_modules/undici/lib/dispatcher/pool.js +90 -7
  683. package/node_modules/undici/lib/dispatcher/proxy-agent.js +16 -3
  684. package/node_modules/undici/lib/handler/deduplication-handler.js +24 -11
  685. package/node_modules/undici/lib/handler/redirect-handler.js +5 -0
  686. package/node_modules/undici/lib/handler/retry-handler.js +72 -13
  687. package/node_modules/undici/lib/interceptor/cache.js +4 -1
  688. package/node_modules/undici/lib/interceptor/decompress.js +236 -69
  689. package/node_modules/undici/lib/web/cache/cache.js +17 -8
  690. package/node_modules/undici/lib/web/cache/cachestorage.js +11 -5
  691. package/node_modules/undici/lib/web/cookies/index.js +19 -7
  692. package/node_modules/undici/lib/web/cookies/util.js +3 -1
  693. package/node_modules/undici/lib/web/eventsource/eventsource.js +25 -1
  694. package/node_modules/undici/lib/web/fetch/body.js +22 -15
  695. package/node_modules/undici/lib/web/fetch/data-url.js +6 -6
  696. package/node_modules/undici/lib/web/fetch/formdata.js +30 -36
  697. package/node_modules/undici/lib/web/fetch/headers.js +34 -31
  698. package/node_modules/undici/lib/web/fetch/index.js +38 -14
  699. package/node_modules/undici/lib/web/fetch/request.js +74 -79
  700. package/node_modules/undici/lib/web/fetch/response.js +43 -44
  701. package/node_modules/undici/lib/web/fetch/util.js +7 -7
  702. package/node_modules/undici/lib/web/infra/index.js +16 -1
  703. package/node_modules/undici/lib/web/webidl/index.js +2 -14
  704. package/node_modules/undici/lib/web/websocket/events.js +43 -26
  705. package/node_modules/undici/lib/web/websocket/receiver.js +5 -0
  706. package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +13 -10
  707. package/node_modules/undici/lib/web/websocket/websocket.js +44 -41
  708. package/node_modules/undici/package.json +1 -1
  709. package/node_modules/undici/types/diagnostics-channel.d.ts +2 -1
  710. package/node_modules/undici/types/interceptors.d.ts +1 -1
  711. package/node_modules/undici/types/webidl.d.ts +13 -7
  712. package/package.json +4 -4
  713. package/dist/view/app-dist/assets/index-DFzw1L97.js +0 -63
  714. package/docs-site/zh/tutorials/experiments.mdx +0 -101
@@ -6,6 +6,7 @@ exports.scoreFactOutcomeForAttemptError = scoreFactOutcomeForAttemptError;
6
6
  exports.errorFromThrown = errorFromThrown;
7
7
  exports.executeSandboxAction = executeSandboxAction;
8
8
  exports.experimentRunInfo = experimentRunInfo;
9
+ const attempt_cancellation_ts_1 = require("./attempt-cancellation.cjs");
9
10
  // 单个 attempt 的完整生命周期:资源(沙箱 / OTLP 接收器)经 Effect.Sample 的
10
11
  // acquireRelease 接管,无论 body 成功 / 抛错 / 被中断,stop() / close() 都保证执行。
11
12
  // 沙箱编排的固定段在 runAttemptBody(基线→setup→驱动 test→采 diff→评分→判定→收 trace),
@@ -19,6 +20,8 @@ const paths_ts_1 = require("../sandbox/paths.cjs");
19
20
  const request_executor_ts_1 = require("../sandbox/request-executor.cjs");
20
21
  const cleanup_timeout_ts_1 = require("./cleanup-timeout.cjs");
21
22
  const adapter_usage_ts_1 = require("./adapter-usage.cjs");
23
+ const judge_usage_ts_1 = require("../o11y/judge-usage.cjs");
24
+ const judge_usage_ts_2 = require("./judge-usage.cjs");
22
25
  const adapter_attachments_ts_1 = require("./adapter-attachments.cjs");
23
26
  const adapter_execution_trace_ts_1 = require("./adapter-execution-trace.cjs");
24
27
  const score_ts_1 = require("../eval/record/score.cjs");
@@ -140,765 +143,791 @@ function attemptFailureDeclaration(classify, phase, e) {
140
143
  return { class: cls, phase, text: info.text };
141
144
  }
142
145
  function runAttemptEffect(a, opts, sandboxSem, { attachmentScope, buildLocators, preparedSetupPrefix, runTiming, parentSignal, invocationSignal, onPhase, concurrencySlot, providerAdmission, onFailureClass, onEnvironmentIncomplete, onSandboxCleanupFailure, onSealedEvaluation: publishSealedEvaluation, onInterruptedResult, reusedSandbox, }) {
143
- let assistanceReported = false;
144
- const onSealedEvaluation = (sealed) => effect_1.Effect.suspend(() => {
145
- if (!assistanceReported) {
146
- assistanceReported = true;
147
- reportJudgeErrorAssistance(a, sealed);
148
- }
149
- return publishSealedEvaluation?.(sealed) ?? effect_1.Effect.void;
150
- });
151
- const config = opts.config;
152
- const { evalDef, run, attempt } = a;
153
- const adapter = run.adapter;
154
- const agent = run.agent;
155
- const coordinationRoot = opts.coordinationRoot ?? `${process.cwd()}/.niceeval`;
156
- const t0 = Date.now();
157
- // Source bytes are snapshotted at the author action, then held only through
158
- // an internal capability until the Record producer seals its own Attachments.
159
- const sourceRegistry = (0, source_loc_ts_1.createSourceRegistry)(process.cwd());
160
- const sourceCapture = (0, source_producer_ts_1.createRunnerAttemptSourceCapture)(sourceRegistry);
161
- const attemptResources = new attempt_resources_ts_1.ManagedAttemptResources();
162
- let attemptResourceReleaseFailureReported = false;
163
- const reportAttemptResourceReleaseFailure = (error) => {
164
- if (attemptResourceReleaseFailureReported)
165
- return;
166
- attemptResourceReleaseFailureReported = true;
167
- declareFailure("agent.teardown", error);
168
- diagnostics.push(teardownDiagnostic("agent.teardown", error));
169
- };
170
- const base = {
171
- id: evalDef.id,
172
- description: evalDef.description,
173
- experimentId: run.experimentId,
174
- experiment: experimentRunInfo(run, a.plan, a.sandboxPlansByEval, config, a.judge),
175
- adapter: (0, adapter_ts_1.adapterIdentity)(adapter),
176
- model: run.model,
177
- verdict: "errored",
178
- fingerprint: a.fingerprint,
179
- evaluationAlgorithm: types_ts_1.EVALUATION_ALGORITHM,
180
- configHash: a.configHash,
181
- attempt,
182
- startedAt: new Date(t0).toISOString(),
183
- durationMs: 0,
184
- factResults: [],
185
- factUses: [],
186
- evidenceCoverage: agent?.evidenceCoverage ?? attempt_resources_ts_1.adapterEvidenceUnavailable,
187
- evaluationKind: evalDef.evaluationKind ?? "pass",
188
- };
189
- /**
190
- * 调度事实:Sandbox 租借给这条 Attempt 的那一刻定死(复用池借出的实例在进入本函数时就已经
191
- * 借定,一次性沙箱在 create 返回那一刻借定)。Attempt 在任何阶段终结——Eval `setup` 失败、
192
- * 超时、资源获取失败——记录里都要带全 provider / sandboxId / reused / 编号与承接序号,
193
- * 只有 `kept` 在收尾时点决定(见 docs/feature/record/architecture.md 的 `sandbox` 字段)。
194
- * 所以它挂在唯一出口(下方 Effect.map)上,而不是分散在各条返回路径里。
195
- */
196
- let sandboxFacts;
197
- let currentSandbox;
198
- /** 留存提交成功(`--keep-sandbox`)——唯一一个在收尾时点才知道的 sandbox 记录键。 */
199
- let kept = false;
200
- if (reusedSandbox && agent?.kind === "sandbox") {
201
- if (a.plan._tag !== "Sandbox") {
202
- throw new Error(`sandbox agent ${JSON.stringify(agent.name)} received a Direct plan`);
203
- }
204
- sandboxFacts = {
205
- provider: a.plan.providerPlan.provider,
206
- sandboxId: reusedSandbox.sandbox.sandboxId,
207
- reused: true,
208
- reuseSandbox: reusedSandbox.reuseSandbox,
209
- reuseOrdinal: reusedSandbox.reuseOrdinal,
146
+ return effect_1.Effect.clockWith((clock) => effect_1.Effect.suspend(() => {
147
+ let assistanceReported = false;
148
+ const onSealedEvaluation = (sealed) => effect_1.Effect.suspend(() => {
149
+ if (!assistanceReported) {
150
+ assistanceReported = true;
151
+ reportJudgeErrorAssistance(a, sealed);
152
+ }
153
+ return publishSealedEvaluation?.(sealed) ?? effect_1.Effect.void;
154
+ });
155
+ const config = opts.config;
156
+ const { evalDef, run, attempt } = a;
157
+ const adapter = run.adapter;
158
+ const agent = run.agent;
159
+ const coordinationRoot = opts.coordinationRoot ?? `${process.cwd()}/.niceeval`;
160
+ const t0 = Date.now();
161
+ // Source bytes are snapshotted at the author action, then held only through
162
+ // an internal capability until the Record producer seals its own Attachments.
163
+ const sourceRegistry = (0, source_loc_ts_1.createSourceRegistry)(process.cwd());
164
+ const sourceCapture = (0, source_producer_ts_1.createRunnerAttemptSourceCapture)(sourceRegistry);
165
+ const attemptResources = new attempt_resources_ts_1.ManagedAttemptResources();
166
+ let attemptResourceReleaseFailureReported = false;
167
+ const reportAttemptResourceReleaseFailure = (error) => {
168
+ if (attemptResourceReleaseFailureReported)
169
+ return;
170
+ attemptResourceReleaseFailureReported = true;
171
+ declareFailure("agent.teardown", error);
172
+ diagnostics.push(teardownDiagnostic("agent.teardown", error));
210
173
  };
211
- }
212
- // 四层解析链的最后两层(eval → config)在这里接上,并把赢家那一层带出来:超时消息要说得出
213
- // 「这个上限是哪一层给的」,否则撞线时得回头逐层对照四个声明点(契约见
214
- // docs/feature/experiments/cli.md「timeout、budget 与基础设施错误」)。
215
- // 四层都没声明 = 无上限:不挂 deadline、不发软截止信号,也不给 provider 递命令超时,
216
- // 链末端不发明一条隐藏的线。
217
- const attemptTimeout = (0, timeout_ts_1.resolveAttemptTimeout)(run, evalDef, config);
218
- // deadline 的截止**时刻**:沙箱内一切时限从它派生(单条命令未显式传 timeout 时上限 =
219
- // 剩余量,见 sandbox/deadline.ts)。与 Effect.timeoutTo 同一个锚点,不各取各的 now()。
220
- const deadlineAt = attemptTimeout ? Date.now() + attemptTimeout.timeoutMs : undefined;
221
- // Adapter 的协作式 deadline 由专用 controller 承载。它绝不能自己设 timer:Effect.timeoutTo
222
- // 是唯一的 deadline owner,并会在 onTimeout 同步 abort 这个 controller;否则独立 timer
223
- // 可能让 adapter 先 reject,把 deadline 错分成外部 interrupt。
224
- const deadlineAbort = new AbortController();
225
- const parentAbort = new AbortController();
226
- const signal = AbortSignal.any([parentAbort.signal, deadlineAbort.signal]);
227
- // Attempt 阶段的正式生命周期投影(见 docs/feature/experiments/cli.md「Attempt 阶段」)。
228
- // Direct / reused Attempt 由 run.ts 在进入本函数前发 start;fresh Sandbox 则由下面的 provider
229
- // admission 在 granted 后发 start,再于同一边界进入 sandbox.create。没有对应 hook/配置的步骤
230
- // 直接不调用,不产生空阶段。没有活跃 feedback coordinator 时 lifecycle 投影静默 no-op。
231
- const identity = { experimentId: run.experimentId, evalId: evalDef.id, attempt };
232
- // 最近跨入的正式 phase:errored 结果的 `error.phase` 从它取(见下方 timeout / scope
233
- // 兜底与 runAttemptBody 的 body catch)。body 与本函数共用同一个 enterPhase 闭包(经 res 传下去),
234
- // 所以 body 内部的阶段推进也会更新它,不需要 body 再单独维护一份。
235
- // 阶段计时:live 展示、error.phase、落盘 phases[].name 用同一套 LifecyclePhase 闭集,
236
- // 一次 enterPhase 同时推进三者(词表全仓只有一套,见 runner/types.ts 的 LifecyclePhase)。
237
- let lastPhase;
238
- const recorder = (0, timing_ts_1.createTimingRecorder)();
239
- // adapter send 在飞时,错误/诊断归因到嵌套的 `agent.run`(eval.run 内打开,不单列计时条目)。
240
- let sendActive = false;
241
- // 超时证据保全的外层句柄:与 recorder 同一模式,runAttemptBody 内部一建好 SessionManager /
242
- // ChangeLedger 就经 AttemptResources.registerEvidence/registerLedger 登记回这里——中断后由
243
- // Sample 外层直接读它们组装结果,不随被 Effect 中断放弃的 body fiber 一起消失(见
244
- // docs/runner.md「超时:双层保护」超时不丢证据)。
245
- let liveEvents;
246
- let liveUsage;
247
- let liveRetryAttempts;
248
- let liveLedger;
249
- // Assert-first state becomes visible only after the real TestContext has
250
- // been constructed. Scope finalization owns the interruption seal so an
251
- // aborted Attempt cannot leave declared entries unsealed.
252
- let liveAssertions;
253
- let liveAssertionState;
254
- let adapterResources;
255
- let adapterUsage;
256
- let adapterUsageSnapshot;
257
- let adapterAttachments;
258
- let adapterExecutionTraces;
259
- let adapterSealedAssertions;
260
- let adapterCleanupError;
261
- let invocationInterrupted = false;
262
- let assertionsSealed = false;
263
- let resolveExecutionTerminal;
264
- const executionTerminal = new Promise((resolve) => {
265
- resolveExecutionTerminal = resolve;
266
- });
267
- const markExecutionTerminal = () => {
268
- if (assertionsSealed)
269
- return;
270
- assertionsSealed = true;
271
- resolveExecutionTerminal();
272
- };
273
- let timeoutSealedAssertions;
274
- // Effect.timeoutTo 的 onTimeout 是同步回调,在中断真正下发给 body fiber(从而触发下面的
275
- // finalizer 链)之前就已经跑完并同步置位这个标记——下面新增的 finalizer 靠它判断本次 Sample
276
- // release 是不是超时触发的,只在超时路径补折叠证据,正常收尾路径不重复做(见文件顶部
277
- // Effect.timeoutTo 调用点的注释)。
278
- let timedOut = false;
279
- let deadlineError;
280
- let timeoutDiff;
281
- let fileChangesCapture;
282
- let timeoutFileChangesCapture;
283
- let timeoutSources;
284
- const closeAuthoring = (reason) => {
285
- adapterResources?.closeAuthoring();
286
- liveAssertions?.closeAuthoring(reason);
287
- };
288
- const forwardParentAbort = () => {
289
- closeAuthoring("attempt-interrupted");
290
- parentAbort.abort(parentSignal?.reason);
291
- };
292
- if (parentSignal?.aborted === true)
293
- forwardParentAbort();
294
- else
295
- parentSignal?.addEventListener("abort", forwardParentAbort, { once: true });
296
- const enterPhase = (phase) => {
297
- lastPhase = phase;
298
- recorder.enter(phase);
299
- onPhase?.(phase);
300
- (0, sink_ts_1.reportAttemptLifecycle)({ type: "attempt:phase", at: Date.now(), identity, phase });
301
- };
302
- /**
303
- * 终局失败的分类与回执 —— **必须在把错误折成纯数据 `AttemptError` 之前调**:折完之后原错误
304
- * 对象(连同它携带的 `_tag`/`class` 与 cause 链)就不再是任何人的引用,空间轴声明再也读不出来。
305
- * 走生命周期链的三道决议(抛出点声明 → 实验分类器 → 缺省不可重试,见
306
- * src/shared/failure-class.ts 的 `resolveAttemptFailureClass`);send 失败的时间轴已由 send 重试
307
- * 执行体在 context 层消费完,浮出到这里的一定是终局失败,这里只读空间轴。
308
- * 缺省档(`scope` 省略或 `"attempt"`)不回执:那是「死因只属于本次执行」,没有闸可落。
309
- */
310
- const declareFailure = (phase, e) => {
311
- if (!onFailureClass)
312
- return;
313
- const declaration = attemptFailureDeclaration(run.classifyFailure, phase, e);
314
- if (declaration) {
315
- // 空间轴回执会先于 Attempt 结果封口进入 run.ts,并立即生成运行期反馈与 run.json 的
316
- // dispatch-halted 诊断。它不能依赖下面的 EvalResult 最终扫描补救,否则同一条错误在
317
- // result.json 已脱敏、Run 级诊断却仍保留原值。分类决策只消费原始错误;对外回执单独
318
- // 复制并脱敏正文,scope / phase 等路由事实保持原样。
319
- onFailureClass({
320
- ...declaration,
321
- text: (0, redaction_ts_1.redactSensitiveText)(declaration.text, sensitiveValues),
322
- });
174
+ const base = {
175
+ id: evalDef.id,
176
+ description: evalDef.description,
177
+ experimentId: run.experimentId,
178
+ experiment: experimentRunInfo(run, a.plan, a.sandboxPlansByEval, config, a.judge),
179
+ adapter: (0, adapter_ts_1.adapterIdentity)(adapter),
180
+ model: run.model,
181
+ verdict: "errored",
182
+ fingerprint: a.fingerprint,
183
+ evaluationAlgorithm: types_ts_1.EVALUATION_ALGORITHM,
184
+ configHash: a.configHash,
185
+ attempt,
186
+ startedAt: new Date(t0).toISOString(),
187
+ durationMs: 0,
188
+ factResults: [],
189
+ factUses: [],
190
+ evidenceCoverage: agent?.evidenceCoverage ?? attempt_resources_ts_1.adapterEvidenceUnavailable,
191
+ evaluationKind: evalDef.evaluationKind ?? "pass",
192
+ };
193
+ /**
194
+ * 调度事实:Sandbox 租借给这条 Attempt 的那一刻定死(复用池借出的实例在进入本函数时就已经
195
+ * 借定,一次性沙箱在 create 返回那一刻借定)。Attempt 在任何阶段终结——Eval `setup` 失败、
196
+ * 超时、资源获取失败——记录里都要带全 provider / sandboxId / reused / 编号与承接序号,
197
+ * 只有 `kept` 在收尾时点决定(见 docs/feature/record/architecture.md 的 `sandbox` 字段)。
198
+ * 所以它挂在唯一出口(下方 Effect.map)上,而不是分散在各条返回路径里。
199
+ */
200
+ let sandboxFacts;
201
+ let currentSandbox;
202
+ /** 留存提交成功(`--keep-sandbox`)——唯一一个在收尾时点才知道的 sandbox 记录键。 */
203
+ let kept = false;
204
+ if (reusedSandbox && agent?.kind === "sandbox") {
205
+ if (a.plan._tag !== "Sandbox") {
206
+ throw new Error(`sandbox agent ${JSON.stringify(agent.name)} received a Direct plan`);
207
+ }
208
+ sandboxFacts = {
209
+ provider: a.plan.providerPlan.provider,
210
+ sandboxId: reusedSandbox.sandbox.sandboxId,
211
+ reused: true,
212
+ reuseSandbox: reusedSandbox.reuseSandbox,
213
+ reuseOrdinal: reusedSandbox.reuseOrdinal,
214
+ };
323
215
  }
324
- };
325
- // 本 attempt 累计的诊断(与 verdict 独立):ScopedFeedback.diagnostic 与 teardown 失败都落这里,
326
- // 收尾时并入结果;dedupeKey 相同的并发诊断折叠成一条并累计 count。
327
- const diagnostics = [];
328
- const dedupeIndex = new Map();
329
- // Sandbox 命令证据的累加器(与 diagnostics 同一种「共享容器」模式):
330
- // withCommandTiming 在 CommandResult 交还调用方之前把每条命令(成功与非零退出都算)push 进来,
331
- // runAttemptBody 的 finally 与本函数的超时/中断兜底分支都读同一个数组引用
332
- // (见 docs/feature/record/architecture.md「commandsjson」「证据在 CommandResult 返回
333
- // 调用方之前写入内存」)。
334
- const commands = [];
335
- // CommandOptions.sensitiveValues 的 Attempt 级内存集合。值只供记录边界精确替换;最终
336
- // result 封口后集合随本次调用释放,不进入任何 artifact、指纹或 provider identity。
337
- const sensitiveValues = new Set();
338
- // Package-internal only: the final EvalResult is weakly associated with this
339
- // capture after all Runner evidence is sealed. No public result field or
340
- // legacy artifact is used as a transport.
341
- const observabilityRuntime = (0, runtime_ts_3.createRunnerAttemptObservabilityRuntime)({
342
- providerName: adapter.name,
343
- sensitiveValues,
344
- });
345
- const recordDiagnostic = (input) => {
346
- const phase = (sendActive ? "agent.run" : lastPhase) ?? "eval.run";
347
- if (input.dedupeKey !== undefined) {
348
- const existing = dedupeIndex.get(input.dedupeKey);
349
- if (existing) {
350
- existing.count = (existing.count ?? 1) + 1;
216
+ // 四层解析链的最后两层(eval → config)在这里接上,并把赢家那一层带出来:超时消息要说得出
217
+ // 「这个上限是哪一层给的」,否则撞线时得回头逐层对照四个声明点(契约见
218
+ // docs/feature/experiments/cli.md「timeout、budget 与基础设施错误」)。
219
+ // 四层都没声明 = 无上限:不挂 deadline、不发软截止信号,也不给 provider 递命令超时,
220
+ // 链末端不发明一条隐藏的线。
221
+ const attemptTimeout = (0, timeout_ts_1.resolveAttemptTimeout)(run, evalDef, config);
222
+ // deadline 的截止**时刻**:沙箱内一切时限从它派生(单条命令未显式传 timeout 时上限 =
223
+ // 剩余量,见 sandbox/deadline.ts)。与 Effect.timeoutTo 同一个锚点,不各取各的 now()。
224
+ const deadlineAt = attemptTimeout ? clock.currentTimeMillisUnsafe() + attemptTimeout.timeoutMs : undefined;
225
+ const executionStartedAt = clock.monotonicTimeNanosUnsafe();
226
+ // Adapter 的协作式 deadline 由专用 controller 承载。它绝不能自己设 timer:Effect.timeoutTo
227
+ // 是唯一的 deadline owner,并会在 onTimeout 同步 abort 这个 controller;否则独立 timer
228
+ // 可能让 adapter 先 reject,把 deadline 错分成外部 interrupt。
229
+ const cancellation = new attempt_cancellation_ts_1.AttemptCancellationController();
230
+ const signal = cancellation.signal;
231
+ // Attempt 阶段的正式生命周期投影(见 docs/feature/experiments/cli.md「Attempt 阶段」)。
232
+ // Direct / reused Attempt 由 run.ts 在进入本函数前发 start;fresh Sandbox 则由下面的 provider
233
+ // admission 在 granted 后发 start,再于同一边界进入 sandbox.create。没有对应 hook/配置的步骤
234
+ // 直接不调用,不产生空阶段。没有活跃 feedback coordinator 时 lifecycle 投影静默 no-op。
235
+ const identity = { experimentId: run.experimentId, evalId: evalDef.id, attempt };
236
+ // 最近跨入的正式 phase:errored 结果的 `error.phase` 从它取(见下方 timeout / scope
237
+ // 兜底与 runAttemptBody 的 body catch)。body 与本函数共用同一个 enterPhase 闭包(经 res 传下去),
238
+ // 所以 body 内部的阶段推进也会更新它,不需要 body 再单独维护一份。
239
+ // 阶段计时:live 展示、error.phase、落盘 phases[].name 用同一套 LifecyclePhase 闭集,
240
+ // 一次 enterPhase 同时推进三者(词表全仓只有一套,见 runner/types.ts 的 LifecyclePhase)。
241
+ let lastPhase;
242
+ const recorder = (0, timing_ts_1.createTimingRecorder)();
243
+ // adapter send 在飞时,错误/诊断归因到嵌套的 `agent.run`(eval.run 内打开,不单列计时条目)。
244
+ let sendActive = false;
245
+ // 超时证据保全的外层句柄:与 recorder 同一模式,runAttemptBody 内部一建好 SessionManager /
246
+ // ChangeLedger 就经 AttemptResources.registerEvidence/registerLedger 登记回这里——中断后由
247
+ // Sample 外层直接读它们组装结果,不随被 Effect 中断放弃的 body fiber 一起消失(见
248
+ // docs/runner.md「超时:双层保护」超时不丢证据)。
249
+ let liveEvents;
250
+ let liveUsage;
251
+ let liveRetryAttempts;
252
+ let liveLedger;
253
+ // Assert-first state becomes visible only after the real TestContext has
254
+ // been constructed. Scope finalization owns the interruption seal so an
255
+ // aborted Attempt cannot leave declared entries unsealed.
256
+ let liveAssertions;
257
+ let liveAssertionState;
258
+ let adapterResources;
259
+ let adapterUsage;
260
+ let adapterUsageSnapshot;
261
+ let adapterAttachments;
262
+ let adapterExecutionTraces;
263
+ let adapterSealedAssertions;
264
+ let adapterCleanupError;
265
+ let invocationInterrupted = false;
266
+ let assertionsSealed = false;
267
+ let resolveExecutionTerminal;
268
+ const executionTerminal = new Promise((resolve) => {
269
+ resolveExecutionTerminal = resolve;
270
+ });
271
+ const markExecutionTerminal = () => {
272
+ if (assertionsSealed)
273
+ return;
274
+ assertionsSealed = true;
275
+ resolveExecutionTerminal();
276
+ };
277
+ let timeoutSealedAssertions;
278
+ // Effect.timeoutTo 的 onTimeout 是同步回调,在中断真正下发给 body fiber(从而触发下面的
279
+ // finalizer 链)之前就已经跑完并同步置位这个标记——下面新增的 finalizer 靠它判断本次 Sample
280
+ // release 是不是超时触发的,只在超时路径补折叠证据,正常收尾路径不重复做(见文件顶部
281
+ // Effect.timeoutTo 调用点的注释)。
282
+ let timedOut = false;
283
+ let deadlineError;
284
+ let timeoutDiff;
285
+ let fileChangesCapture;
286
+ let timeoutFileChangesCapture;
287
+ let timeoutSources;
288
+ const closeAuthoring = (reason) => {
289
+ adapterResources?.closeAuthoring();
290
+ liveAssertions?.closeAuthoring(reason);
291
+ };
292
+ const forwardParentAbort = () => {
293
+ closeAuthoring("attempt-interrupted");
294
+ cancellation.abort({ kind: "cancelled" });
295
+ };
296
+ if (parentSignal?.aborted === true)
297
+ forwardParentAbort();
298
+ else
299
+ parentSignal?.addEventListener("abort", forwardParentAbort, { once: true });
300
+ const enterPhase = (phase) => {
301
+ lastPhase = phase;
302
+ recorder.enter(phase);
303
+ onPhase?.(phase);
304
+ (0, sink_ts_1.reportAttemptLifecycle)({ type: "attempt:phase", at: Date.now(), identity, phase });
305
+ };
306
+ /**
307
+ * 终局失败的分类与回执 —— **必须在把错误折成纯数据 `AttemptError` 之前调**:折完之后原错误
308
+ * 对象(连同它携带的 `_tag`/`class` 与 cause 链)就不再是任何人的引用,空间轴声明再也读不出来。
309
+ * 走生命周期链的三道决议(抛出点声明 → 实验分类器 → 缺省不可重试,见
310
+ * src/shared/failure-class.ts 的 `resolveAttemptFailureClass`);send 失败的时间轴已由 send 重试
311
+ * 执行体在 context 层消费完,浮出到这里的一定是终局失败,这里只读空间轴。
312
+ * 缺省档(`scope` 省略或 `"attempt"`)不回执:那是「死因只属于本次执行」,没有闸可落。
313
+ */
314
+ const declareFailure = (phase, e) => {
315
+ if (!onFailureClass)
351
316
  return;
317
+ const declaration = attemptFailureDeclaration(run.classifyFailure, phase, e);
318
+ if (declaration) {
319
+ // 空间轴回执会先于 Attempt 结果封口进入 run.ts,并立即生成运行期反馈与 run.json 的
320
+ // dispatch-halted 诊断。它不能依赖下面的 EvalResult 最终扫描补救,否则同一条错误在
321
+ // result.json 已脱敏、Run 级诊断却仍保留原值。分类决策只消费原始错误;对外回执单独
322
+ // 复制并脱敏正文,scope / phase 等路由事实保持原样。
323
+ onFailureClass({
324
+ ...declaration,
325
+ text: (0, redaction_ts_1.redactSensitiveText)(declaration.text, sensitiveValues),
326
+ });
352
327
  }
353
- }
354
- const message = (0, redaction_ts_1.redactSensitiveText)(input.message, sensitiveValues);
355
- const context = input.data === undefined
356
- ? undefined
357
- : (0, redaction_ts_1.redactSensitiveEvidence)(input.data, sensitiveValues);
358
- const record = {
359
- code: input.code,
360
- level: input.level,
361
- detail: message,
362
- origin: (0, timing_ts_1.attemptOrigin)(phase),
363
- ...(context !== undefined ? { context } : {}),
364
328
  };
365
- if (input.dedupeKey !== undefined)
366
- dedupeIndex.set(input.dedupeKey, record);
367
- diagnostics.push(record);
368
- // 同时进运行级永久事件流(human 撤下 dashboard 后追加、JSON stdout 追加 warning,去重按 key)。
369
- // `key` 只管折叠到多细(作者没给 dedupeKey 时折到「这一条 attempt 的这种诊断」,身份因此
370
- // 编进去);对外稳定词法始终单独给 `code`,不让消费方从 key 反推(见 sink.ts 的
371
- // DiagnosticInput.code、docs/feature/experiments/cli.md 的 WarningEvent)。
372
- const { origin: _authorOrigin, phase: _authorPhase, ...diagnosticData } = context ?? {};
373
- (0, sink_ts_1.reportDiagnostic)({
374
- key: input.dedupeKey ?? `${input.code}:${(0, types_ts_1.encodeAttemptKey)(identity)}`,
375
- code: input.code,
376
- severity: input.level,
377
- message,
378
- identity,
379
- // phase 由运行器给,且**压过**作者 `data` 里的同名字段:`WarningEvent.phase` 是
380
- // `LifecyclePhase` 闭集,取值只能由「这条诊断报上来时运行器正处在哪一步」决定。
381
- // 作者的 `data` 是开放词表,让它盖住这个字段等于允许从 eval / adapter 代码里冒充一个
382
- // 别的(甚至不在闭集里的)阶段,消费方按 phase 分支就此失效——与 ScopedFeedback
383
- // 「两个方法都不接受 phase」是同一条纪律(见 src/shared/types.ts 的接口注释)。
384
- data: { ...diagnosticData, phase, origin: (0, timing_ts_1.attemptOrigin)(phase) },
329
+ // 本 attempt 累计的诊断(与 verdict 独立):ScopedFeedback.diagnostic 与 teardown 失败都落这里,
330
+ // 收尾时并入结果;dedupeKey 相同的并发诊断折叠成一条并累计 count。
331
+ const diagnostics = [];
332
+ const dedupeIndex = new Map();
333
+ // Sandbox 命令证据的累加器(与 diagnostics 同一种「共享容器」模式):
334
+ // withCommandTiming 在 CommandResult 交还调用方之前把每条命令(成功与非零退出都算)push 进来,
335
+ // runAttemptBody 的 finally 与本函数的超时/中断兜底分支都读同一个数组引用
336
+ // (见 docs/feature/record/architecture.md「commandsjson」「证据在 CommandResult 返回
337
+ // 调用方之前写入内存」)。
338
+ const commands = [];
339
+ // CommandOptions.sensitiveValues 的 Attempt 级内存集合。值只供记录边界精确替换;最终
340
+ // result 封口后集合随本次调用释放,不进入任何 artifact、指纹或 provider identity。
341
+ const sensitiveValues = new Set();
342
+ // Package-internal only: the final EvalResult is weakly associated with this
343
+ // capture after all Runner evidence is sealed. No public result field or
344
+ // legacy artifact is used as a transport.
345
+ const observabilityRuntime = (0, runtime_ts_3.createRunnerAttemptObservabilityRuntime)({
346
+ providerName: adapter.name,
347
+ sensitiveValues,
385
348
  });
386
- };
387
- // 作用域反馈:progress 只走 attempt:progress(短命状态,归因由 runner 的当前阶段决定),
388
- // 不进入 recentLogs / timeout stack;diagnostic 落 attempt diagnostics + 运行级永久事件。
389
- // 绑定见 docs/feature/experiments/library.md。
390
- const scopedFeedback = {
391
- progress: (u) => {
392
- const suffix = u.current !== undefined && u.total !== undefined ? ` (${u.current}/${u.total})` : "";
349
+ const recordDiagnostic = (input) => {
350
+ const phase = (sendActive ? "agent.run" : lastPhase) ?? "eval.run";
351
+ if (input.dedupeKey !== undefined) {
352
+ const existing = dedupeIndex.get(input.dedupeKey);
353
+ if (existing) {
354
+ existing.count = (existing.count ?? 1) + 1;
355
+ return;
356
+ }
357
+ }
358
+ const message = (0, redaction_ts_1.redactSensitiveText)(input.message, sensitiveValues);
359
+ const context = input.data === undefined
360
+ ? undefined
361
+ : (0, redaction_ts_1.redactSensitiveEvidence)(input.data, sensitiveValues);
362
+ const record = {
363
+ code: input.code,
364
+ level: input.level,
365
+ detail: message,
366
+ origin: (0, timing_ts_1.attemptOrigin)(phase),
367
+ ...(context !== undefined ? { context } : {}),
368
+ };
369
+ if (input.dedupeKey !== undefined)
370
+ dedupeIndex.set(input.dedupeKey, record);
371
+ diagnostics.push(record);
372
+ // 同时进运行级永久事件流(human 撤下 dashboard 后追加、JSON stdout 追加 warning,去重按 key)。
373
+ // `key` 只管折叠到多细(作者没给 dedupeKey 时折到「这一条 attempt 的这种诊断」,身份因此
374
+ // 编进去);对外稳定词法始终单独给 `code`,不让消费方从 key 反推(见 sink.ts 的
375
+ // DiagnosticInput.code、docs/feature/experiments/cli.md 的 WarningEvent)。
376
+ const { origin: _authorOrigin, phase: _authorPhase, ...diagnosticData } = context ?? {};
377
+ (0, sink_ts_1.reportDiagnostic)({
378
+ key: input.dedupeKey ?? `${input.code}:${(0, types_ts_1.encodeAttemptKey)(identity)}`,
379
+ code: input.code,
380
+ severity: input.level,
381
+ message,
382
+ identity,
383
+ // phase 由运行器给,且**压过**作者 `data` 里的同名字段:`WarningEvent.phase` 是
384
+ // `LifecyclePhase` 闭集,取值只能由「这条诊断报上来时运行器正处在哪一步」决定。
385
+ // 作者的 `data` 是开放词表,让它盖住这个字段等于允许从 eval / adapter 代码里冒充一个
386
+ // 别的(甚至不在闭集里的)阶段,消费方按 phase 分支就此失效——与 ScopedFeedback
387
+ // 「两个方法都不接受 phase」是同一条纪律(见 src/shared/types.ts 的接口注释)。
388
+ data: { ...diagnosticData, phase, origin: (0, timing_ts_1.attemptOrigin)(phase) },
389
+ });
390
+ };
391
+ // 作用域反馈:progress 只走 attempt:progress(短命状态,归因由 runner 的当前阶段决定),
392
+ // 不进入 recentLogs / timeout stack;diagnostic 落 attempt diagnostics + 运行级永久事件。
393
+ // 绑定见 docs/feature/experiments/library.md。
394
+ const scopedFeedback = {
395
+ progress: (u) => {
396
+ const suffix = u.current !== undefined && u.total !== undefined ? ` (${u.current}/${u.total})` : "";
397
+ (0, sink_ts_1.reportAttemptLifecycle)({
398
+ type: "attempt:progress",
399
+ at: Date.now(),
400
+ identity,
401
+ detail: activeDetailText(`${u.message}${suffix}`, sensitiveValues),
402
+ });
403
+ },
404
+ diagnostic: recordDiagnostic,
405
+ };
406
+ const recordSandboxCleanupFailure = (stage, cause) => {
407
+ if (onSandboxCleanupFailure === undefined)
408
+ return;
409
+ const rawError = cause instanceof Error ? cause : new Error(String(cause));
410
+ // The ledger is later surfaced as an Experiment diagnostic, outside this
411
+ // Attempt's normal diagnostic sink. Preserve the real cleanup outcome, but
412
+ // cross that boundary only after applying this Attempt's evidence redaction.
413
+ const error = new Error((0, redaction_ts_1.redactSensitiveText)(rawError.message, sensitiveValues));
414
+ // This receiver runs inside the Scope finalizer path. It must not let a
415
+ // bookkeeping failure replace the provider or lifecycle cleanup outcome.
416
+ try {
417
+ onSandboxCleanupFailure({ stage, error });
418
+ }
419
+ catch {
420
+ // The owning Experiment will still receive the original cleanup result.
421
+ }
422
+ };
423
+ /**
424
+ * Sandbox runtime deliberately keeps lifecycle cleanup diagnostic-only so it
425
+ * can continue other finalizers. Only this fresh Sandbox wrapper
426
+ * observes those runtime-owned diagnostics; Eval/Agent diagnostics with the
427
+ * same public sink never enter the physical cleanup ledger.
428
+ */
429
+ const freshSandboxLifecycleFeedback = {
430
+ progress: scopedFeedback.progress,
431
+ diagnostic: (input) => {
432
+ scopedFeedback.diagnostic(input);
433
+ if (input.code === "sandbox-teardown-failed" ||
434
+ input.code === "sandbox-stop-failed" ||
435
+ input.code === "plugin-lifecycle-teardown-failed") {
436
+ recordSandboxCleanupFailure("sandbox.lifecycle", new Error((0, redaction_ts_1.redactSensitiveText)(input.message, sensitiveValues)));
437
+ }
438
+ },
439
+ };
440
+ const judgeUsage = new judge_usage_ts_1.JudgeUsageCollector(config.pricing, signal, () => recordDiagnostic({
441
+ code: "judge-transport-cleanup-incomplete", level: "warning",
442
+ message: "Judge transport cleanup did not settle before its local deadline; remote completion is unknown.",
443
+ }));
444
+ const sealExecutionError = () => {
445
+ const runtime = liveAssertions ?? (evalDef.evaluationKind === "score"
446
+ ? (0, runtime_ts_2.createAssertionsRuntime)({ evaluationKind: "score", judgeUsage })
447
+ : (0, runtime_ts_2.createAssertionsRuntime)({ evaluationKind: "pass", judgeUsage }));
448
+ if (liveAssertions === undefined) {
449
+ // Sandbox acquire/prepare may fail before TestContext exists. It is still
450
+ // an executed Attempt, so seal its empty assertion set with the same
451
+ // execution-error terminal used by failures after Context construction.
452
+ liveAssertions = runtime;
453
+ sourceCapture.attachAssertions(runtime);
454
+ }
455
+ return (0, assertions_ts_1.sealAttemptAssertions)(runtime, { execution: "errored" }).pipe(effect_1.Effect.tap(() => effect_1.Effect.sync(() => {
456
+ markExecutionTerminal();
457
+ })), effect_1.Effect.tap((sealed) => adapter.kind === "custom"
458
+ ? effect_1.Effect.sync(() => { adapterSealedAssertions = sealed; })
459
+ : onSealedEvaluation?.(sealed) ?? effect_1.Effect.void));
460
+ };
461
+ // `log` 是显式 timeout breadcrumb;与短命 progress 分开,避免 user/tool ACTIVE 文本落盘。
462
+ const recentLogs = [];
463
+ const log = (m) => {
464
+ const detail = (0, redaction_ts_1.redactSensitiveText)(m, sensitiveValues);
465
+ recentLogs.push(detail);
466
+ if (recentLogs.length > 20)
467
+ recentLogs.shift();
468
+ // 附着在「当前阶段」上的次要文本(见 ActiveAttempt.detail)。所有调用点都晚于 Direct/reuse
469
+ // start,或 fresh provider 的 granted callback,active map 因而已经有这个 identity。这是唯一出口 ——
470
+ // 没有裸写 stderr 的兜底分支(那是给已删除的 Live reporter 用的旧接线,见
471
+ // docs/feature/experiments/cli.md「一个 run 内只有一个终端协调者」);由当前 renderer 决定
472
+ // 是否消费这条 detail(human 展示,JSON 不消费 lifecycle detail)。
393
473
  (0, sink_ts_1.reportAttemptLifecycle)({
394
474
  type: "attempt:progress",
395
475
  at: Date.now(),
396
476
  identity,
397
- detail: activeDetailText(`${u.message}${suffix}`, sensitiveValues),
477
+ detail: activeDetailText(m, sensitiveValues),
398
478
  });
399
- },
400
- diagnostic: recordDiagnostic,
401
- };
402
- const recordSandboxCleanupFailure = (stage, cause) => {
403
- if (onSandboxCleanupFailure === undefined)
404
- return;
405
- const rawError = cause instanceof Error ? cause : new Error(String(cause));
406
- // The ledger is later surfaced as an Experiment diagnostic, outside this
407
- // Attempt's normal diagnostic sink. Preserve the real cleanup outcome, but
408
- // cross that boundary only after applying this Attempt's evidence redaction.
409
- const error = new Error((0, redaction_ts_1.redactSensitiveText)(rawError.message, sensitiveValues));
410
- // This receiver runs inside the Scope finalizer path. It must not let a
411
- // bookkeeping failure replace the provider or lifecycle cleanup outcome.
412
- try {
413
- onSandboxCleanupFailure({ stage, error });
414
- }
415
- catch {
416
- // The owning Experiment will still receive the original cleanup result.
417
- }
418
- };
419
- /**
420
- * Sandbox runtime deliberately keeps lifecycle cleanup diagnostic-only so it
421
- * can continue other finalizers. Only this fresh Sandbox wrapper
422
- * observes those runtime-owned diagnostics; Eval/Agent diagnostics with the
423
- * same public sink never enter the physical cleanup ledger.
424
- */
425
- const freshSandboxLifecycleFeedback = {
426
- progress: scopedFeedback.progress,
427
- diagnostic: (input) => {
428
- scopedFeedback.diagnostic(input);
429
- if (input.code === "sandbox-teardown-failed" ||
430
- input.code === "sandbox-stop-failed" ||
431
- input.code === "plugin-lifecycle-teardown-failed") {
432
- recordSandboxCleanupFailure("sandbox.lifecycle", new Error((0, redaction_ts_1.redactSensitiveText)(input.message, sensitiveValues)));
433
- }
434
- },
435
- };
436
- const sealExecutionError = () => {
437
- const runtime = liveAssertions ?? (evalDef.evaluationKind === "score"
438
- ? (0, runtime_ts_2.createAssertionsRuntime)({ evaluationKind: "score" })
439
- : (0, runtime_ts_2.createAssertionsRuntime)({ evaluationKind: "pass" }));
440
- if (liveAssertions === undefined) {
441
- // Sandbox acquire/prepare may fail before TestContext exists. It is still
442
- // an executed Attempt, so seal its empty assertion set with the same
443
- // execution-error terminal used by failures after Context construction.
444
- liveAssertions = runtime;
445
- sourceCapture.attachAssertions(runtime);
446
- }
447
- return (0, assertions_ts_1.sealAttemptAssertions)(runtime, { execution: "errored" }).pipe(effect_1.Effect.tap(() => effect_1.Effect.sync(() => {
448
- markExecutionTerminal();
449
- })), effect_1.Effect.tap((sealed) => adapter.kind === "custom"
450
- ? effect_1.Effect.sync(() => { adapterSealedAssertions = sealed; })
451
- : onSealedEvaluation?.(sealed) ?? effect_1.Effect.void));
452
- };
453
- // `log` 是显式 timeout breadcrumb;与短命 progress 分开,避免 user/tool ACTIVE 文本落盘。
454
- const recentLogs = [];
455
- const log = (m) => {
456
- const detail = (0, redaction_ts_1.redactSensitiveText)(m, sensitiveValues);
457
- recentLogs.push(detail);
458
- if (recentLogs.length > 20)
459
- recentLogs.shift();
460
- // 附着在「当前阶段」上的次要文本(见 ActiveAttempt.detail)。所有调用点都晚于 Direct/reuse
461
- // start,或 fresh provider 的 granted callback,active map 因而已经有这个 identity。这是唯一出口 ——
462
- // 没有裸写 stderr 的兜底分支(那是给已删除的 Live reporter 用的旧接线,见
463
- // docs/feature/experiments/cli.md「一个 run 内只有一个终端协调者」);由当前 renderer 决定
464
- // 是否消费这条 detail(human 展示,JSON 不消费 lifecycle detail)。
465
- (0, sink_ts_1.reportAttemptLifecycle)({
466
- type: "attempt:progress",
467
- at: Date.now(),
468
- identity,
469
- detail: activeDetailText(m, sensitiveValues),
470
- });
471
- };
472
- /**
473
- * ── attempt 总超时的硬边界(P1)──
474
- * 上限是「整个 attempt(setup+agent+脚本+评分)」的,不是 docker 单条命令的。
475
- * 到点 → 中断整段 body → Sample 跑 release(停容器、关接收器)→ 产出一条 errored 结果。
476
- * 即便 adapter / test 完全无视 signal 挂死,这一层也能把它停下来并回收资源。
477
- *
478
- * 四层解析链都没声明上限时**这个算子整个不挂**——不是挂一条无穷大的 timer:没有线就没有
479
- * deadline,attempt 跑到自己结束为止(与携带判据的 `Infinity` 同一个语义)。
480
- */
481
- const applyAttemptDeadline = (self) => {
482
- if (attemptTimeout === undefined)
483
- return self;
484
- const { timeoutMs, source: timeoutSource } = attemptTimeout;
485
- const deadline = effect_1.Effect.raceFirst(effect_1.Effect.sleep(effect_1.Duration.millis(timeoutMs)).pipe(effect_1.Effect.as("deadline")), effect_1.Effect.promise(() => executionTerminal).pipe(effect_1.Effect.as("terminal"))).pipe(effect_1.Effect.flatMap((winner) => {
486
- // Assertion seal is the Attempt's terminal freeze. Once it wins, the
487
- // deadline competitor is permanently retired while publication,
488
- // source capture, and cleanup finish under their own ownership.
489
- if (winner === "terminal")
490
- return effect_1.Effect.never;
491
- return effect_1.Effect.suspend(() => {
492
- // Recheck in the same synchronous section that closes admission and
493
- // flips `timedOut`; seal may have won after the race selected its
494
- // timer but before this continuation was scheduled.
495
- if (assertionsSealed)
479
+ };
480
+ /**
481
+ * ── attempt 总超时的硬边界(P1)──
482
+ * 上限是「整个 attempt(setup+agent+脚本+评分)」的,不是 docker 单条命令的。
483
+ * 到点 → 中断整段 body → Sample 跑 release(停容器、关接收器)→ 产出一条 errored 结果。
484
+ * 即便 adapter / test 完全无视 signal 挂死,这一层也能把它停下来并回收资源。
485
+ *
486
+ * 四层解析链都没声明上限时**这个算子整个不挂**——不是挂一条无穷大的 timer:没有线就没有
487
+ * deadline,attempt 跑到自己结束为止(与携带判据的 `Infinity` 同一个语义)。
488
+ */
489
+ const applyAttemptDeadline = (self) => {
490
+ if (attemptTimeout === undefined)
491
+ return self;
492
+ const { timeoutMs, source: timeoutSource } = attemptTimeout;
493
+ const waitForDeadline = effect_1.Effect.suspend(() => {
494
+ const remaining = timeoutMs - Number(clock.monotonicTimeNanosUnsafe() - executionStartedAt) / 1000000;
495
+ // Native timers truncate fractional milliseconds. Recheck the same
496
+ // monotonic deadline after waking rather than cancelling early.
497
+ return remaining <= 0 ? effect_1.Effect.void
498
+ : effect_1.Effect.sleep(effect_1.Duration.millis(Math.ceil(remaining))).pipe(effect_1.Effect.andThen(waitForDeadline));
499
+ });
500
+ const deadline = effect_1.Effect.raceFirst(waitForDeadline.pipe(effect_1.Effect.as("deadline")), effect_1.Effect.promise(() => executionTerminal).pipe(effect_1.Effect.as("terminal"))).pipe(effect_1.Effect.flatMap((winner) => {
501
+ // Assertion seal is the Attempt's terminal freeze. Once it wins, the
502
+ // deadline competitor is permanently retired while publication,
503
+ // source capture, and cleanup finish under their own ownership.
504
+ if (winner === "terminal")
496
505
  return effect_1.Effect.never;
497
- // 超时:message 是一层原因(首行),recentLogs 明细放进 stack 供 show 展开「卡在哪一步」;
498
- // operation 取超时那一刻打开的 lifecycle operation。code 稳定为 "timeout"。
499
- const text = `attempt timed out (${timeoutMs}ms, from ${timeoutSource})
506
+ return effect_1.Effect.suspend(() => {
507
+ // Recheck in the same synchronous section that closes admission and
508
+ // flips `timedOut`; seal may have won after the race selected its
509
+ // timer but before this continuation was scheduled.
510
+ if (assertionsSealed || signal.aborted)
511
+ return effect_1.Effect.never;
512
+ // 超时:message 是一层原因(首行),recentLogs 明细放进 stack 供 show 展开「卡在哪一步」;
513
+ // operation 取超时那一刻打开的 lifecycle operation。code 稳定为 "timeout"。
514
+ const text = `attempt timed out (${timeoutMs}ms, from ${timeoutSource})
500
515
  Recent progress:
501
516
  ${recentLogs.map((l) => ` · ${l}`).join("\n")}`;
502
- const message = (0, util_ts_1.firstLine)(text);
503
- const rest = text.length > message.length ? text.slice(message.length + 1).replace(/\n+$/, "") : "";
504
- const error = {
505
- code: "timeout",
506
- message,
507
- origin: (0, timing_ts_1.attemptOrigin)((sendActive ? "agent.run" : lastPhase) ?? "eval.run"),
508
- // 归属三样一起落盘:撞的是哪层时限、值多少、值从四层解析链的哪层来。
509
- // 不打一个没有归属说明的 ✗(见 docs/feature/sandbox/architecture.md「时限归属」)。
510
- timeout: { trigger: "attempt-deadline", limitMs: timeoutMs, source: timeoutSource },
511
- ...(rest.trim() !== "" ? { stack: rest } : {}),
512
- };
513
- deadlineError = error;
514
- recorder.failCurrent();
515
- // `timeoutTo` 已经赢得 deadline:先同步关闭所有作者/Core 修改入口,
516
- // 再标记并 abort 协作式 adapter。同步 abort listener 因而也不能追加断言。
517
- // Scope release below seals the Attempt-local Assertions runtime as
518
- // producer-interrupted before resources are released.
519
- // 这个 controller 没有独立 timer;它只把已经裁定的 timeout 传给观察 signal 的
520
- // adapter。标记必须先于 abort,才能把本 fiber 的 timeout 中断与真正外层取消区分。
521
- closeAuthoring("attempt-interrupted");
522
- timedOut = true;
523
- deadlineAbort.abort();
524
- // 置位给下面的 finalizer 用(它在 Sample release 里跑,LIFO 早于 sandbox stop,
525
- // 补折叠 workspace.diff / sources——见该 finalizer 的注释)。events/usage 不必等它:
526
- // SessionManager 是外层已经登记过的活引用(见 liveEvents/liveUsage),截至这一刻已经
527
- // 归一化的事件与已累计的用量此刻就能直接读出,不随放弃的 body fiber 一起消失。
528
- const events = liveEvents?.();
529
- const usage = liveUsage?.();
530
- const retryAttempts = liveRetryAttempts?.();
531
- return effect_1.Effect.succeed({
532
- ...base,
533
- durationMs: recorder.offsetNow(),
534
- error,
535
- ...(evalDef.evaluationKind === "score" ? { scoreResult: scoreFactOutcomeForAttemptError(error) } : {}),
536
- ...(events !== undefined ? { events: [...events], o11y: (0, derive_ts_1.buildO11ySummary)(events) } : {}),
537
- ...(usage !== undefined ? { usage: { ...usage } } : {}),
538
- ...(retryAttempts !== undefined && retryAttempts.length > 0
539
- ? { retryAttempts: retryAttempts.map((attempt) => ({ ...attempt })) }
540
- : {}),
541
- // 超时前已经登记的命令证据(见 withCommandTiming)不因中断而丢失——它们在
542
- // CommandResult 返回调用方那一刻就已经写进了这个共享数组。
543
- ...(commands.length > 0 ? { commands: [...commands] } : {}),
517
+ const message = (0, util_ts_1.firstLine)(text);
518
+ const rest = text.length > message.length ? text.slice(message.length + 1).replace(/\n+$/, "") : "";
519
+ const error = {
520
+ code: "timeout",
521
+ message,
522
+ origin: (0, timing_ts_1.attemptOrigin)((sendActive ? "agent.run" : lastPhase) ?? "eval.run"),
523
+ // 归属三样一起落盘:撞的是哪层时限、值多少、值从四层解析链的哪层来。
524
+ // 不打一个没有归属说明的 ✗(见 docs/feature/sandbox/architecture.md「时限归属」)。
525
+ timeout: { trigger: "attempt-deadline", limitMs: timeoutMs, source: timeoutSource },
526
+ ...(rest.trim() !== "" ? { stack: rest } : {}),
527
+ };
528
+ deadlineError = error;
529
+ recorder.failCurrent();
530
+ // `timeoutTo` 已经赢得 deadline:先同步关闭所有作者/Core 修改入口,
531
+ // 再标记并 abort 协作式 adapter。同步 abort listener 因而也不能追加断言。
532
+ // Scope release below seals the Attempt-local Assertions runtime as
533
+ // producer-interrupted before resources are released.
534
+ // 这个 controller 没有独立 timer;它只把已经裁定的 timeout 传给观察 signal 的
535
+ // adapter。标记必须先于 abort,才能把本 fiber 的 timeout 中断与真正外层取消区分。
536
+ closeAuthoring("attempt-interrupted");
537
+ timedOut = true;
538
+ cancellation.abort({ kind: "timeout", timeoutMs, source: timeoutSource, deadlineAt: deadlineAt });
539
+ // 置位给下面的 finalizer 用(它在 Sample release 里跑,LIFO 早于 sandbox stop,
540
+ // 补折叠 workspace.diff / sources——见该 finalizer 的注释)。events/usage 不必等它:
541
+ // SessionManager 是外层已经登记过的活引用(见 liveEvents/liveUsage),截至这一刻已经
542
+ // 归一化的事件与已累计的用量此刻就能直接读出,不随放弃的 body fiber 一起消失。
543
+ const events = liveEvents?.();
544
+ const usage = liveUsage?.();
545
+ const retryAttempts = liveRetryAttempts?.();
546
+ return effect_1.Effect.succeed({
547
+ ...base,
548
+ durationMs: recorder.offsetNow(),
549
+ error,
550
+ ...(evalDef.evaluationKind === "score" ? { scoreResult: scoreFactOutcomeForAttemptError(error) } : {}),
551
+ ...(events !== undefined ? { events: [...events], o11y: (0, derive_ts_1.buildO11ySummary)(events) } : {}),
552
+ ...(usage !== undefined ? { usage: { ...usage } } : {}),
553
+ ...(retryAttempts !== undefined && retryAttempts.length > 0
554
+ ? { retryAttempts: retryAttempts.map((attempt) => ({ ...attempt })) }
555
+ : {}),
556
+ // 超时前已经登记的命令证据(见 withCommandTiming)不因中断而丢失——它们在
557
+ // CommandResult 返回调用方那一刻就已经写进了这个共享数组。
558
+ ...(commands.length > 0 ? { commands: [...commands] } : {}),
559
+ });
544
560
  });
545
- });
546
- }));
547
- return effect_1.Effect.raceFirst(self, deadline);
548
- };
549
- const layerCleanups = [];
550
- return effect_1.Effect.uninterruptibleMask((restore) => restore(effect_1.Effect.scoped(effect_1.Effect.gen(function* () {
551
- // Every Adapter kind enters through the same Attempt-owned bridge,
552
- // execution deadline, Assertion seal, feedback sink, and publication
553
- // callback. The driver below contributes only its own preparation,
554
- // author context, and observations.
555
- const assertFirst = yield* (0, assert_first_bridge_ts_1.makeAssertFirstAttemptBridge)();
556
- if (adapter.kind === "custom") {
557
- const resources = new attempt_resources_ts_1.AdapterAttemptResources(() => {
558
- adapterUsageSnapshot = adapterUsage.close();
559
- adapterAttachments.close();
560
- adapterExecutionTraces.close();
561
- });
562
- adapterResources = resources;
563
- adapterUsage = new adapter_usage_ts_1.AdapterUsageCollector(() => resources.assertCaptureOpen(), config.pricing);
564
- adapterAttachments = (0, adapter_attachments_ts_1.createAdapterAttachmentCollector)(`${(0, root_ts_1.recordRootPaths)(opts.recordRoot).portableRoot}/attachment-staging`);
565
- const ownedAttachments = adapterAttachments;
566
- yield* effect_1.Scope.addFinalizer(attachmentScope, effect_1.Effect.promise(() => ownedAttachments.dispose()));
567
- adapterExecutionTraces = (0, adapter_execution_trace_ts_1.createAdapterExecutionTraceCollector)(adapterAttachments.artifacts);
568
- // Scope LIFO freezes Assertions before releasing author resources.
569
- // The final execution outcome is handed off only after capture drains.
570
- yield* effect_1.Effect.addFinalizer(() => cleanupAdapterResources(resources, {
571
- enterPhase,
572
- recorder,
573
- feedback: scopedFeedback,
574
- onTimeout: (error) => { adapterCleanupError = error; },
575
- }));
576
- yield* effect_1.Effect.addFinalizer((exit) => effect_1.Effect.suspend(() => {
577
- const interrupted = timedOut || (effect_1.Exit.isFailure(exit) && effect_1.Cause.hasInterruptsOnly(exit.cause));
578
- if (!interrupted || liveAssertions === undefined || assertionsSealed) {
579
- return effect_1.Effect.void;
580
- }
581
- closeAuthoring("attempt-interrupted");
582
- sourceCapture.markInterrupted();
583
- return (0, assertions_ts_1.sealAttemptAssertions)(liveAssertions, {
584
- execution: "errored",
585
- interrupted: true,
586
- }).pipe(effect_1.Effect.tap((sealed) => effect_1.Effect.sync(() => {
587
- markExecutionTerminal();
588
- timeoutSealedAssertions = sealed;
589
- adapterSealedAssertions = sealed;
590
- })), effect_1.Effect.catchCause(() => effect_1.Effect.void));
591
561
  }));
592
- return yield* runAdapterAttemptBody({
593
- a,
594
- base,
595
- adapter,
596
- resources,
597
- usage: adapterUsage,
598
- attachments: adapterAttachments,
599
- executionTraces: adapterExecutionTraces,
600
- signal,
601
- sourceCapture,
602
- sourceRegistry,
603
- assertFirst,
604
- enterPhase,
605
- recorder,
606
- feedback: scopedFeedback,
607
- log,
608
- declareFailure,
609
- registerAssertions: (runtime) => {
610
- if (liveAssertions !== undefined && liveAssertions !== runtime) {
611
- throw new Error("Attempt tried to register a second Assertions runtime");
612
- }
613
- liveAssertions = runtime;
614
- },
615
- closeAuthoring,
616
- markAssertionsSealed: (sealed) => {
617
- markExecutionTerminal();
618
- adapterSealedAssertions = sealed;
619
- timeoutSealedAssertions = timedOut ? sealed : timeoutSealedAssertions;
620
- },
621
- });
622
- }
623
- if (agent === undefined) {
624
- throw new Error(`Agent Adapter ${JSON.stringify(adapter.name)} is missing its Agent runtime`);
625
- }
626
- const projectStateDatabase = yield* project_state_database_ts_1.ProjectStateDatabase;
627
- const sandboxPlan = a.plan._tag === "Sandbox" ? a.plan : undefined;
628
- if (agent.kind === "sandbox" && sandboxPlan === undefined) {
629
- throw new Error(`sandbox agent ${JSON.stringify(agent.name)} received a Direct plan`);
630
- }
631
- const runtimeCapabilities = sandboxPlan === undefined ? undefined : (0, runtime_ts_1.sandboxRuntimeCapabilities)(sandboxPlan);
632
- // 留存 disposition:只在本 attempt 内可变,初始 stop;只有留存提交成功才改成 keep
633
- // (Ctrl+C 中断外层 Sample 时仍是 stop,照常清理)。是否可留存只读 physical plan
634
- // 的中性 retention 能力;不在 runner 里按 provider 名或旧声明结构分支。
635
- let disposition = "stop";
636
- // The bridge owns its Queue worker inside this Attempt Scope. Promise
637
- // code only offers work; no internal module starts a second runtime.
638
- // 退避重试(runtime.ts → retry.ts)期间临时归还这个名额:被限流的 provider 只是在
639
- // setTimeout 里睡觉,不该攥着 sandboxSem 的槽位陪跑,不然一批 429 能把整体并发拖成个位数。
640
- const provisioningPermit = (0, provisioning_permit_ts_1.makeProvisioningPermitOwner)(sandboxSem);
641
- const provisionSlot = provisioningPermit;
642
- const admissionSlot = {
643
- // The ordinary provisioning permit is innermost and leaves first. The
644
- // scheduler permits then leave through their stateful owner handle.
645
- release: provisioningPermit.release.pipe(effect_1.Effect.andThen(concurrencySlot?.release ?? effect_1.Effect.void)),
646
- // Re-enter the scheduler in its original order before taking the
647
- // ordinary provisioning permit again. Each scheduler owner and the
648
- // provisioning owner is stateful; cancellation cleans only permits
649
- // actually reacquired and never issues a blind release.
650
- reacquire: (concurrencySlot?.reacquire ?? effect_1.Effect.void).pipe(effect_1.Effect.andThen(provisioningPermit.reacquire)),
651
- };
652
- const runtimeAdmission = {
653
- queued: (reason) => (providerAdmission?.queued(reason) ?? effect_1.Effect.void).pipe(effect_1.Effect.andThen(effect_1.Effect.sync(() => enterPhase("sandbox.queue")))),
654
- granted: (providerAdmission?.granted ?? effect_1.Effect.void).pipe(effect_1.Effect.andThen(effect_1.Effect.sync(() => {
655
- enterPhase("sandbox.create");
656
- log(`starting sandbox...`);
657
- }))),
658
- slot: admissionSlot,
562
+ return effect_1.Effect.raceFirst(self, deadline);
659
563
  };
660
- // Sample release(receiver close + provider stop)整段计成 sandbox.stop:先加的 finalizer
661
- // 后跑(LIFO),所以「先加的」在 release 链末尾打终点戳、「后加的」在 release 开始前打起点戳;
662
- // 结果封口(附 phases)发生在 Sample release 完成之后(见下方 Effect.map)。
663
- let releaseStartedAt;
664
- if (agent.kind === "sandbox" && !reusedSandbox) {
665
- yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.sync(() => {
666
- // 留存路径的 phases 以 sandbox.suspend 结尾,没有 sandbox.stop 条目(见 release)。
667
- if (releaseStartedAt !== undefined && disposition !== "keep") {
668
- recorder.record("sandbox.stop", recorder.offsetNow() - releaseStartedAt);
669
- }
670
- }));
671
- }
672
- let materializedCase;
673
- const sandbox = reusedSandbox?.sandbox ??
674
- (agent.kind === "sandbox"
675
- ? yield* effect_1.Effect.gen(function* () {
676
- // ── 沙箱:acquire=起,release=整组 stop(成功 / 失败 / 中断都跑)──
677
- // sandboxSem 只覆盖「容器创建」阶段;容器起好后立即释放,后续 npm install / agent 不占位。
678
- // provider 固有的会话上限不能静默充当默认值:deadline 超出它时,attempt
679
- // 会跑到一半被平台截断。在派发前就报环境约束,点名 provider 与上限值。
680
- if (sandboxPlan === undefined || runtimeCapabilities === undefined) {
681
- throw new Error("sandbox plan invariant violated before materialization");
682
- }
683
- assertDeadlineFitsPlan(runtimeCapabilities, attemptTimeout?.timeoutMs);
684
- if (opts.keepSandbox !== undefined && runtimeCapabilities.retention._tag !== "Suspendable") {
685
- throw new Error(`--keep-sandbox is unsupported by provider ${JSON.stringify(runtimeCapabilities.provider)}`);
686
- }
687
- const runtimeDeadline = attemptTimeout === undefined || deadlineAt === undefined
688
- ? { _tag: "Unlimited" }
689
- : { _tag: "Bounded", timeoutMs: attemptTimeout.timeoutMs, deadlineAt };
690
- const runtimeInput = {
691
- plan: sandboxPlan,
692
- evalId: evalDef.id,
693
- deadline: runtimeDeadline,
694
- feedback: freshSandboxLifecycleFeedback,
695
- signal,
696
- hookContext: {
697
- experimentId: sandboxPlan.pair.experimentId,
698
- signal,
699
- progress: freshSandboxLifecycleFeedback.progress,
700
- diagnostic: freshSandboxLifecycleFeedback.diagnostic,
701
- },
702
- buildLocators,
703
- ...(preparedSetupPrefix === undefined
704
- ? {}
705
- : { setupPrefixArtifact: preparedSetupPrefix.artifact.locator }),
706
- agent: agent.kind === "sandbox" ? agent : undefined,
707
- ...(runTiming !== undefined ? { runTiming } : {}),
708
- provisionSlot: { _tag: "Bound", value: provisionSlot },
709
- admission: { _tag: "Bound", value: runtimeAdmission },
710
- services: runtime_ts_1.liveSandboxRuntimeServices,
711
- // Provider runtime 自己用 acquireRelease 持有 Case;Attempt 只提交显式
712
- // Managed disposition,避免外层再包一层 acquireRelease 造成 double-stop。
713
- release: {
714
- _tag: "Managed",
715
- run: (owned) => effect_1.Effect.gen(function* () {
716
- const sb = owned.sandbox;
717
- if (disposition !== "keep") {
718
- return yield* tryPromiseAsDefect(() => owned.group.stop()).pipe(effect_1.Effect.onExit((exit) => effect_1.Exit.isFailure(exit)
719
- ? effect_1.Effect.sync(() => {
720
- recordSandboxCleanupFailure("sandbox.stop", effect_1.Cause.squash(exit.cause));
721
- })
722
- : effect_1.Effect.void));
723
- }
724
- yield* effect_1.Effect.sync(() => (0, registry_ts_1.unregisterSandbox)(sb));
725
- const providerName = runtimeCapabilities.provider;
726
- const suspendStart = recorder.offsetNow();
727
- return yield* tryPromiseAsDefect(() => (0, keep_ts_1.suspendSandbox)(sb)).pipe(effect_1.Effect.tap(() => effect_1.Effect.sync(() => {
728
- recorder.record("sandbox.suspend", recorder.offsetNow() - suspendStart);
729
- })), effect_1.Effect.andThen((0, keep_registry_ts_1.updateKeptEntryEffect)(coordinationRoot, (0, keep_registry_ts_1.keptEntryId)(providerName, sb.sandboxId), {
730
- state: "dormant",
731
- }).pipe(effect_1.Effect.provideService(project_state_database_ts_1.ProjectStateDatabase, projectStateDatabase), effect_1.Effect.ignore)), effect_1.Effect.catchCause((cause) => effect_1.Cause.hasInterruptsOnly(cause)
732
- ? effect_1.Effect.failCause(cause)
733
- : effect_1.Effect.sync(() => {
734
- const error = effect_1.Cause.squash(cause);
735
- recorder.record("sandbox.suspend", recorder.offsetNow() - suspendStart, true);
736
- recordDiagnostic({
737
- code: "sandbox-suspend-failed",
738
- level: "warning",
739
- message: `sandbox ${sb.sandboxId} kept but suspend failed; the instance is still running: ${error instanceof Error ? error.message : String(error)}`,
740
- dedupeKey: `sandbox-suspend-failed:${sb.sandboxId}`,
741
- });
742
- })));
743
- }),
744
- },
745
- };
746
- const materialized = yield* effect_1.Effect.acquireUseRelease(provisioningPermit.acquire, () => (0, runtime_ts_1.acquireSandboxRunPlan)(runtimeInput), () => provisioningPermit.release);
747
- // Legacy layer/plugin setup is the earliest opaque barrier and runs after
748
- // provider admission has been returned, while the same Attempt Scope still owns release.
749
- if (sandboxPlan.pair.setupHooks.length > 0 || sandboxPlan.pair.pluginLifecycles.length > 0) {
750
- enterPhase("sandbox.prepare");
564
+ const layerCleanups = [];
565
+ return effect_1.Effect.uninterruptibleMask((restore) => restore(effect_1.Effect.scoped(effect_1.Effect.gen(function* () {
566
+ // Every Adapter kind enters through the same Attempt-owned bridge,
567
+ // execution deadline, Assertion seal, feedback sink, and publication
568
+ // callback. The driver below contributes only its own preparation,
569
+ // author context, and observations.
570
+ yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.promise(() => judgeUsage.closeAndDrain()));
571
+ const assertFirst = yield* (0, assert_first_bridge_ts_1.makeAssertFirstAttemptBridge)();
572
+ if (adapter.kind === "custom") {
573
+ const resources = new attempt_resources_ts_1.AdapterAttemptResources(() => {
574
+ adapterUsageSnapshot = adapterUsage.close();
575
+ adapterExecutionTraces.close();
576
+ adapterAttachments.close();
577
+ });
578
+ adapterResources = resources;
579
+ adapterUsage = new adapter_usage_ts_1.AdapterUsageCollector(() => resources.assertCaptureOpen(), config.pricing, run.models ?? Object.freeze({}));
580
+ adapterAttachments = (0, adapter_attachments_ts_1.createAdapterAttachmentCollector)(`${(0, root_ts_1.recordRootPaths)(opts.recordRoot).portableRoot}/attachment-staging`);
581
+ const ownedAttachments = adapterAttachments;
582
+ yield* effect_1.Scope.addFinalizer(attachmentScope, effect_1.Effect.promise(() => ownedAttachments.dispose()));
583
+ adapterExecutionTraces = (0, adapter_execution_trace_ts_1.createAdapterExecutionTraceCollector)(adapterAttachments.artifacts);
584
+ // Scope LIFO freezes Assertions before releasing author resources.
585
+ // The final execution outcome is handed off only after capture drains.
586
+ yield* effect_1.Effect.addFinalizer(() => cleanupAdapterResources(resources, {
587
+ timeoutMs: adapter.cleanupTimeoutMs,
588
+ enterPhase,
589
+ recorder,
590
+ feedback: scopedFeedback,
591
+ onTimeout: (error) => { adapterCleanupError = error; },
592
+ }));
593
+ yield* effect_1.Effect.addFinalizer((exit) => effect_1.Effect.suspend(() => {
594
+ const interrupted = timedOut || (effect_1.Exit.isFailure(exit) && effect_1.Cause.hasInterruptsOnly(exit.cause));
595
+ if (!interrupted || liveAssertions === undefined || assertionsSealed) {
596
+ return effect_1.Effect.void;
751
597
  }
752
- yield* (0, runtime_ts_1.prepareMaterializedSandboxRunPlan)(runtimeInput, materialized);
753
- materializedCase = materialized;
754
- // fresh Attempt:author facade 在当前 Attempt Scope 内构建。executor 捕获本
755
- // Scope 的 Runtime,Scope 关闭时统一中断在飞请求并拒绝新调用;release closure
756
- // 继续用 materialized.sandbox(资源面 facade),两者生命周期互不借用。
757
- const executor = yield* (0, request_executor_ts_1.makeSandboxRequestExecutor)();
758
- return (0, paths_ts_1.makeSandboxAuthorFacade)(materialized.authorBackend, executor, runtimeCapabilities.provider);
759
- })
760
- : (0, direct_agent_sandbox_ts_1.createDirectAgentSandbox)());
761
- currentSandbox = sandbox;
762
- // 一次性沙箱的租借时刻:实例到手就定归属,后面无论走到哪一步终结都带着它。
763
- if (agent.kind === "sandbox" && !sandboxFacts) {
764
- sandboxFacts = { provider: runtimeCapabilities.provider, sandboxId: sandbox.sandboxId };
765
- }
766
- if (agent.kind !== "sandbox")
767
- log(`using Direct Agent (no Sandbox created)...`);
768
- const commandTarget = (0, operations_ts_1.createSandboxCommandTarget)(sandbox);
769
- // A fresh author facade forks requests into this Attempt Scope. Once Scope.close starts,
770
- // forkIn() interrupts new children immediately even before the executor's own finalizer flips
771
- // its closed flag. Cleanup therefore uses the provider-owned resource facade; before/Agent/test
772
- // continue to use the interruptible author facade above.
773
- const cleanupCommandTarget = (0, operations_ts_1.createSandboxCommandTarget)(reusedSandbox?.resourceSandbox ?? materializedCase?.sandbox ?? sandbox);
774
- // ── tracing ──────────────────────────────────────────────────────────────────
775
- // sandbox.otlpHost:
776
- // string → provider 承诺该 hostname 可访问宿主 receiver
777
- // null → provider 不承诺宿主回连,改在沙箱内起 attempt-scope collector
778
- // defineConfig({ telemetry: { host } }) 可强制覆盖(如配好 tunnel 时)。
779
- //
780
- // 非沙箱 agent(远程 / 进程内)不走 per-attempt receiver:被测应用是长驻进程,只有一条
781
- // 全局 OTel 管线(OTEL_* env 进程启动时读一次)—— per-attempt 端口会在第一个 attempt
782
- // 结束时关掉,后续 span 全丢。改走 run 级共享池,span 逐轮归属(traceparent / 窗口)。
783
- let receiver;
784
- let telemetry;
785
- let otelChannel;
786
- // 共享池仅限:config 配了 telemetry(固定端口,无侵入接入的长驻服务)或显式 tracing.scope === "run"。
787
- // 只声明 tracing 的进程内 adapter(如 aiSdkAgent)保持 per-attempt receiver,attempt 全并发。
788
- const wantsSharedOtel = config.telemetry !== undefined || agent.tracing?.scope === "run";
789
- if (agent.kind !== "sandbox" && wantsSharedOtel && opts.otelPool) {
790
- enterPhase("telemetry.configure");
791
- try {
792
- otelChannel = yield* opts.otelPool.channel(agent.name);
793
- const endpoint = otelChannel.receiver.endpoint(config.telemetry?.host ?? "127.0.0.1");
794
- const env = agent.tracing?.env?.(endpoint);
795
- telemetry = env ? { endpoint, env } : { endpoint };
796
- log(`OTLP shared receiver (run-scoped) -> ${endpoint}`);
797
- }
798
- catch (e) {
799
- if (isAttemptAborted(parentSignal))
800
- throw e;
801
- recordDiagnostic({
802
- code: "telemetry-configure-failed",
803
- level: "warning",
804
- message: `telemetry configure failed; continuing without OTLP trace: ${(0, util_ts_1.firstLine)((0, util_ts_1.formatThrown)(e))}`,
805
- data: evidenceDiagnosticData("configure", e),
598
+ closeAuthoring("attempt-interrupted");
599
+ sourceCapture.markInterrupted();
600
+ return (0, assertions_ts_1.sealAttemptAssertions)(liveAssertions, {
601
+ execution: "errored",
602
+ interrupted: true,
603
+ }).pipe(effect_1.Effect.tap((sealed) => effect_1.Effect.sync(() => {
604
+ markExecutionTerminal();
605
+ timeoutSealedAssertions = sealed;
606
+ adapterSealedAssertions = sealed;
607
+ })), effect_1.Effect.catchCause(() => effect_1.Effect.void));
608
+ }));
609
+ return yield* runAdapterAttemptBody({
610
+ a,
611
+ base,
612
+ adapter,
613
+ resources,
614
+ usage: adapterUsage,
615
+ judgeUsage,
616
+ attachments: adapterAttachments,
617
+ executionTraces: adapterExecutionTraces,
618
+ signal,
619
+ sourceCapture,
620
+ sourceRegistry,
621
+ assertFirst,
622
+ enterPhase,
623
+ recorder,
624
+ feedback: scopedFeedback,
625
+ log,
626
+ declareFailure,
627
+ registerAssertions: (runtime) => {
628
+ if (liveAssertions !== undefined && liveAssertions !== runtime) {
629
+ throw new Error("Attempt tried to register a second Assertions runtime");
630
+ }
631
+ liveAssertions = runtime;
632
+ },
633
+ closeAuthoring,
634
+ markAssertionsSealed: (sealed) => {
635
+ markExecutionTerminal();
636
+ adapterSealedAssertions = sealed;
637
+ timeoutSealedAssertions = timedOut ? sealed : timeoutSealedAssertions;
638
+ },
806
639
  });
807
- otelChannel = undefined;
808
- telemetry = undefined;
809
640
  }
810
- }
811
- else if (agent.tracing !== undefined) {
812
- enterPhase("telemetry.configure");
813
- try {
814
- const forcedHost = config.telemetry?.host;
815
- if (forcedHost) {
816
- // 显式覆盖:走本地接收器,把指定 host 交给 agent
817
- receiver = yield* (0, receiver_ts_1.createTraceReceiver)();
818
- const endpoint = receiver.endpoint(forcedHost);
641
+ if (agent === undefined) {
642
+ throw new Error(`Agent Adapter ${JSON.stringify(adapter.name)} is missing its Agent runtime`);
643
+ }
644
+ const projectStateDatabase = yield* project_state_database_ts_1.ProjectStateDatabase;
645
+ const sandboxPlan = a.plan._tag === "Sandbox" ? a.plan : undefined;
646
+ if (agent.kind === "sandbox" && sandboxPlan === undefined) {
647
+ throw new Error(`sandbox agent ${JSON.stringify(agent.name)} received a Direct plan`);
648
+ }
649
+ const runtimeCapabilities = sandboxPlan === undefined ? undefined : (0, runtime_ts_1.sandboxRuntimeCapabilities)(sandboxPlan);
650
+ // 留存 disposition:只在本 attempt 内可变,初始 stop;只有留存提交成功才改成 keep
651
+ // (Ctrl+C 中断外层 Sample 时仍是 stop,照常清理)。是否可留存只读 physical plan
652
+ // 的中性 retention 能力;不在 runner 里按 provider 名或旧声明结构分支。
653
+ let disposition = "stop";
654
+ // The bridge owns its Queue worker inside this Attempt Scope. Promise
655
+ // code only offers work; no internal module starts a second runtime.
656
+ // 退避重试(runtime.ts → retry.ts)期间临时归还这个名额:被限流的 provider 只是在
657
+ // setTimeout 里睡觉,不该攥着 sandboxSem 的槽位陪跑,不然一批 429 能把整体并发拖成个位数。
658
+ const provisioningPermit = (0, provisioning_permit_ts_1.makeProvisioningPermitOwner)(sandboxSem);
659
+ const provisionSlot = provisioningPermit;
660
+ const admissionSlot = {
661
+ // The ordinary provisioning permit is innermost and leaves first. The
662
+ // scheduler permits then leave through their stateful owner handle.
663
+ release: provisioningPermit.release.pipe(effect_1.Effect.andThen(concurrencySlot?.release ?? effect_1.Effect.void)),
664
+ // Re-enter the scheduler in its original order before taking the
665
+ // ordinary provisioning permit again. Each scheduler owner and the
666
+ // provisioning owner is stateful; cancellation cleans only permits
667
+ // actually reacquired and never issues a blind release.
668
+ reacquire: (concurrencySlot?.reacquire ?? effect_1.Effect.void).pipe(effect_1.Effect.andThen(provisioningPermit.reacquire)),
669
+ };
670
+ const runtimeAdmission = {
671
+ queued: (reason) => (providerAdmission?.queued(reason) ?? effect_1.Effect.void).pipe(effect_1.Effect.andThen(effect_1.Effect.sync(() => enterPhase("sandbox.queue")))),
672
+ granted: (providerAdmission?.granted ?? effect_1.Effect.void).pipe(effect_1.Effect.andThen(effect_1.Effect.sync(() => {
673
+ enterPhase("sandbox.create");
674
+ log(`starting sandbox...`);
675
+ }))),
676
+ slot: admissionSlot,
677
+ };
678
+ // Sample release(receiver close + provider stop)整段计成 sandbox.stop:先加的 finalizer
679
+ // 后跑(LIFO),所以「先加的」在 release 链末尾打终点戳、「后加的」在 release 开始前打起点戳;
680
+ // 结果封口(附 phases)发生在 Sample release 完成之后(见下方 Effect.map)。
681
+ let releaseStartedAt;
682
+ if (agent.kind === "sandbox" && !reusedSandbox) {
683
+ yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.sync(() => {
684
+ // 留存路径的 phases 以 sandbox.suspend 结尾,没有 sandbox.stop 条目(见 release)。
685
+ if (releaseStartedAt !== undefined && disposition !== "keep") {
686
+ recorder.record("sandbox.stop", recorder.offsetNow() - releaseStartedAt);
687
+ }
688
+ }));
689
+ }
690
+ let materializedCase;
691
+ const sandbox = reusedSandbox?.sandbox ??
692
+ (agent.kind === "sandbox"
693
+ ? yield* effect_1.Effect.gen(function* () {
694
+ // ── 沙箱:acquire=起,release=整组 stop(成功 / 失败 / 中断都跑)──
695
+ // sandboxSem 只覆盖「容器创建」阶段;容器起好后立即释放,后续 npm install / agent 不占位。
696
+ // provider 固有的会话上限不能静默充当默认值:deadline 超出它时,attempt
697
+ // 会跑到一半被平台截断。在派发前就报环境约束,点名 provider 与上限值。
698
+ if (sandboxPlan === undefined || runtimeCapabilities === undefined) {
699
+ throw new Error("sandbox plan invariant violated before materialization");
700
+ }
701
+ assertDeadlineFitsPlan(runtimeCapabilities, attemptTimeout?.timeoutMs);
702
+ if (opts.keepSandbox !== undefined && runtimeCapabilities.retention._tag !== "Suspendable") {
703
+ throw new Error(`--keep-sandbox is unsupported by provider ${JSON.stringify(runtimeCapabilities.provider)}`);
704
+ }
705
+ const runtimeDeadline = attemptTimeout === undefined || deadlineAt === undefined
706
+ ? { _tag: "Unlimited" }
707
+ : { _tag: "Bounded", timeoutMs: attemptTimeout.timeoutMs, deadlineAt };
708
+ const runtimeInput = {
709
+ plan: sandboxPlan,
710
+ evalId: evalDef.id,
711
+ deadline: runtimeDeadline,
712
+ feedback: freshSandboxLifecycleFeedback,
713
+ signal,
714
+ hookContext: {
715
+ experimentId: sandboxPlan.pair.experimentId,
716
+ signal,
717
+ progress: freshSandboxLifecycleFeedback.progress,
718
+ diagnostic: freshSandboxLifecycleFeedback.diagnostic,
719
+ },
720
+ buildLocators,
721
+ ...(preparedSetupPrefix === undefined
722
+ ? {}
723
+ : { setupPrefixArtifact: preparedSetupPrefix.artifact.locator }),
724
+ agent: agent.kind === "sandbox" ? agent : undefined,
725
+ ...(runTiming !== undefined ? { runTiming } : {}),
726
+ provisionSlot: { _tag: "Bound", value: provisionSlot },
727
+ admission: { _tag: "Bound", value: runtimeAdmission },
728
+ services: runtime_ts_1.liveSandboxRuntimeServices,
729
+ // Provider runtime 自己用 acquireRelease 持有 Case;Attempt 只提交显式
730
+ // Managed disposition,避免外层再包一层 acquireRelease 造成 double-stop。
731
+ release: {
732
+ _tag: "Managed",
733
+ run: (owned) => effect_1.Effect.gen(function* () {
734
+ const sb = owned.sandbox;
735
+ if (disposition !== "keep") {
736
+ return yield* tryPromiseAsDefect(() => owned.group.stop()).pipe(effect_1.Effect.onExit((exit) => effect_1.Exit.isFailure(exit)
737
+ ? effect_1.Effect.sync(() => {
738
+ recordSandboxCleanupFailure("sandbox.stop", effect_1.Cause.squash(exit.cause));
739
+ })
740
+ : effect_1.Effect.void));
741
+ }
742
+ yield* effect_1.Effect.sync(() => (0, registry_ts_1.unregisterSandbox)(sb));
743
+ const providerName = runtimeCapabilities.provider;
744
+ const suspendStart = recorder.offsetNow();
745
+ return yield* tryPromiseAsDefect(() => (0, keep_ts_1.suspendSandbox)(sb)).pipe(effect_1.Effect.tap(() => effect_1.Effect.sync(() => {
746
+ recorder.record("sandbox.suspend", recorder.offsetNow() - suspendStart);
747
+ })), effect_1.Effect.andThen((0, keep_registry_ts_1.updateKeptEntryEffect)(coordinationRoot, (0, keep_registry_ts_1.keptEntryId)(providerName, sb.sandboxId), {
748
+ state: "dormant",
749
+ }).pipe(effect_1.Effect.provideService(project_state_database_ts_1.ProjectStateDatabase, projectStateDatabase), effect_1.Effect.ignore)), effect_1.Effect.catchCause((cause) => effect_1.Cause.hasInterruptsOnly(cause)
750
+ ? effect_1.Effect.failCause(cause)
751
+ : effect_1.Effect.sync(() => {
752
+ const error = effect_1.Cause.squash(cause);
753
+ recorder.record("sandbox.suspend", recorder.offsetNow() - suspendStart, true);
754
+ recordDiagnostic({
755
+ code: "sandbox-suspend-failed",
756
+ level: "warning",
757
+ message: `sandbox ${sb.sandboxId} kept but suspend failed; the instance is still running: ${error instanceof Error ? error.message : String(error)}`,
758
+ dedupeKey: `sandbox-suspend-failed:${sb.sandboxId}`,
759
+ });
760
+ })));
761
+ }),
762
+ },
763
+ };
764
+ const materialized = yield* effect_1.Effect.acquireUseRelease(provisioningPermit.acquire, () => (0, runtime_ts_1.acquireSandboxRunPlan)(runtimeInput), () => provisioningPermit.release);
765
+ // Legacy layer/plugin setup is the earliest opaque barrier and runs after
766
+ // provider admission has been returned, while the same Attempt Scope still owns release.
767
+ if (sandboxPlan.pair.setupHooks.length > 0 || sandboxPlan.pair.pluginLifecycles.length > 0) {
768
+ enterPhase("sandbox.prepare");
769
+ }
770
+ yield* (0, runtime_ts_1.prepareMaterializedSandboxRunPlan)(runtimeInput, materialized);
771
+ materializedCase = materialized;
772
+ // fresh Attempt:author facade 在当前 Attempt Scope 内构建。executor 捕获本
773
+ // Scope 的 Runtime,Scope 关闭时统一中断在飞请求并拒绝新调用;release closure
774
+ // 继续用 materialized.sandbox(资源面 facade),两者生命周期互不借用。
775
+ const executor = yield* (0, request_executor_ts_1.makeSandboxRequestExecutor)();
776
+ return (0, paths_ts_1.makeSandboxAuthorFacade)(materialized.authorBackend, executor, runtimeCapabilities.provider);
777
+ })
778
+ : (0, direct_agent_sandbox_ts_1.createDirectAgentSandbox)());
779
+ currentSandbox = sandbox;
780
+ // 一次性沙箱的租借时刻:实例到手就定归属,后面无论走到哪一步终结都带着它。
781
+ if (agent.kind === "sandbox" && !sandboxFacts) {
782
+ sandboxFacts = { provider: runtimeCapabilities.provider, sandboxId: sandbox.sandboxId };
783
+ }
784
+ if (agent.kind !== "sandbox")
785
+ log(`using Direct Agent (no Sandbox created)...`);
786
+ const commandTarget = (0, operations_ts_1.createSandboxCommandTarget)(sandbox);
787
+ // A fresh author facade forks requests into this Attempt Scope. Once Scope.close starts,
788
+ // forkIn() interrupts new children immediately even before the executor's own finalizer flips
789
+ // its closed flag. Cleanup therefore uses the provider-owned resource facade; before/Agent/test
790
+ // continue to use the interruptible author facade above.
791
+ const cleanupCommandTarget = (0, operations_ts_1.createSandboxCommandTarget)(reusedSandbox?.resourceSandbox ?? materializedCase?.sandbox ?? sandbox);
792
+ // ── tracing ──────────────────────────────────────────────────────────────────
793
+ // sandbox.otlpHost:
794
+ // string → provider 承诺该 hostname 可访问宿主 receiver
795
+ // null → provider 不承诺宿主回连,改在沙箱内起 attempt-scope collector
796
+ // defineConfig({ telemetry: { host } }) 可强制覆盖(如配好 tunnel 时)。
797
+ //
798
+ // 非沙箱 agent(远程 / 进程内)不走 per-attempt receiver:被测应用是长驻进程,只有一条
799
+ // 全局 OTel 管线(OTEL_* env 进程启动时读一次)—— per-attempt 端口会在第一个 attempt
800
+ // 结束时关掉,后续 span 全丢。改走 run 级共享池,span 逐轮归属(traceparent / 窗口)。
801
+ let receiver;
802
+ let telemetry;
803
+ let otelChannel;
804
+ // 共享池仅限:config 配了 telemetry(固定端口,无侵入接入的长驻服务)或显式 tracing.scope === "run"。
805
+ // 只声明 tracing 的进程内 adapter(如 aiSdkAgent)保持 per-attempt receiver,attempt 全并发。
806
+ const wantsSharedOtel = config.telemetry !== undefined || agent.tracing?.scope === "run";
807
+ if (agent.kind !== "sandbox" && wantsSharedOtel && opts.otelPool) {
808
+ enterPhase("telemetry.configure");
809
+ try {
810
+ otelChannel = yield* opts.otelPool.channel(agent.name);
811
+ const endpoint = otelChannel.receiver.endpoint(config.telemetry?.host ?? "127.0.0.1");
819
812
  const env = agent.tracing?.env?.(endpoint);
820
813
  telemetry = env ? { endpoint, env } : { endpoint };
821
- log(`OTLP receiver (host override) -> ${endpoint}`);
814
+ log(`OTLP shared receiver (run-scoped) -> ${endpoint}`);
822
815
  }
823
- else if (sandbox.otlpHost !== null) {
824
- // provider 明确承诺宿主回连:宿主开接收器
825
- receiver = yield* (0, receiver_ts_1.createTraceReceiver)();
826
- const endpoint = receiver.endpoint(sandbox.otlpHost);
827
- const env = agent.tracing?.env?.(endpoint);
828
- telemetry = env ? { endpoint, env } : { endpoint };
829
- const proto = agent.tracing?.protocol;
830
- log(`OTLP receiver -> ${endpoint}${proto ? ` (${proto})` : ""}`);
816
+ catch (e) {
817
+ if (isAttemptAborted(parentSignal))
818
+ throw e;
819
+ recordDiagnostic({
820
+ code: "telemetry-configure-failed",
821
+ level: "warning",
822
+ message: `telemetry configure failed; continuing without OTLP trace: ${(0, util_ts_1.firstLine)((0, util_ts_1.formatThrown)(e))}`,
823
+ data: evidenceDiagnosticData("configure", e),
824
+ });
825
+ otelChannel = undefined;
826
+ telemetry = undefined;
831
827
  }
832
- else {
833
- // provider 不承诺宿主回连:在沙箱内起 collector,agent 往 localhost 端口发。
834
- receiver = yield* (0, sandbox_receiver_ts_1.createInSandboxTraceReceiver)(sandbox);
835
- const endpoint = receiver.endpoint("");
836
- const env = agent.tracing?.env?.(endpoint);
837
- telemetry = env ? { endpoint, env } : { endpoint };
838
- const proto = agent.tracing?.protocol;
839
- log(`OTLP in-sandbox collector -> ${endpoint}${proto ? ` (${proto})` : ""}`);
828
+ }
829
+ else if (agent.tracing !== undefined) {
830
+ enterPhase("telemetry.configure");
831
+ try {
832
+ const forcedHost = config.telemetry?.host;
833
+ if (forcedHost) {
834
+ // 显式覆盖:走本地接收器,把指定 host 交给 agent
835
+ receiver = yield* (0, receiver_ts_1.createTraceReceiver)();
836
+ const endpoint = receiver.endpoint(forcedHost);
837
+ const env = agent.tracing?.env?.(endpoint);
838
+ telemetry = env ? { endpoint, env } : { endpoint };
839
+ log(`OTLP receiver (host override) -> ${endpoint}`);
840
+ }
841
+ else if (sandbox.otlpHost !== null) {
842
+ // provider 明确承诺宿主回连:宿主开接收器
843
+ receiver = yield* (0, receiver_ts_1.createTraceReceiver)();
844
+ const endpoint = receiver.endpoint(sandbox.otlpHost);
845
+ const env = agent.tracing?.env?.(endpoint);
846
+ telemetry = env ? { endpoint, env } : { endpoint };
847
+ const proto = agent.tracing?.protocol;
848
+ log(`OTLP receiver -> ${endpoint}${proto ? ` (${proto})` : ""}`);
849
+ }
850
+ else {
851
+ // provider 不承诺宿主回连:在沙箱内起 collector,agent 往 localhost 端口发。
852
+ receiver = yield* (0, sandbox_receiver_ts_1.createInSandboxTraceReceiver)(sandbox);
853
+ const endpoint = receiver.endpoint("");
854
+ const env = agent.tracing?.env?.(endpoint);
855
+ telemetry = env ? { endpoint, env } : { endpoint };
856
+ const proto = agent.tracing?.protocol;
857
+ log(`OTLP in-sandbox collector -> ${endpoint}${proto ? ` (${proto})` : ""}`);
858
+ }
859
+ }
860
+ catch (e) {
861
+ if (isAttemptAborted(parentSignal))
862
+ throw e;
863
+ recordDiagnostic({
864
+ code: "telemetry-configure-failed",
865
+ level: "warning",
866
+ message: `telemetry configure failed; continuing without OTLP trace: ${(0, util_ts_1.firstLine)((0, util_ts_1.formatThrown)(e))}`,
867
+ data: evidenceDiagnosticData("configure", e, runtimeCapabilities?.provider),
868
+ });
869
+ receiver = undefined;
870
+ telemetry = undefined;
840
871
  }
841
872
  }
842
- catch (e) {
843
- if (isAttemptAborted(parentSignal))
844
- throw e;
845
- recordDiagnostic({
846
- code: "telemetry-configure-failed",
847
- level: "warning",
848
- message: `telemetry configure failed; continuing without OTLP trace: ${(0, util_ts_1.firstLine)((0, util_ts_1.formatThrown)(e))}`,
849
- data: evidenceDiagnosticData("configure", e, runtimeCapabilities?.provider),
850
- });
851
- receiver = undefined;
852
- telemetry = undefined;
873
+ // attempt-scope 的进程内 adapter(如 aiSdkAgent + aiSdkOtel)虽然拥有独立 receiver,
874
+ // 仍需要逐轮窗口归属来给 TimingActivity 写入真实 traceId。独立 receiver 不会与其它
875
+ // attempt 混流,因此直接复用同一个 AgentOtelChannel 算法,不必升级成 run 级共享池。
876
+ if (agent.kind !== "sandbox" && receiver !== undefined && otelChannel === undefined) {
877
+ otelChannel = new turn_otel_ts_1.AgentOtelChannel(receiver);
853
878
  }
854
- }
855
- // attempt-scope 的进程内 adapter(如 aiSdkAgent + aiSdkOtel)虽然拥有独立 receiver,
856
- // 仍需要逐轮窗口归属来给 TimingActivity 写入真实 traceId。独立 receiver 不会与其它
857
- // attempt 混流,因此直接复用同一个 AgentOtelChannel 算法,不必升级成 run 级共享池。
858
- if (agent.kind !== "sandbox" && receiver !== undefined && otelChannel === undefined) {
859
- otelChannel = new turn_otel_ts_1.AgentOtelChannel(receiver);
860
- }
861
- if (agent.kind === "sandbox" && !reusedSandbox) {
862
- // 后加先跑:release 链开始时打起点戳(与上面的终点戳配对,测出整段 sandbox.stop)。
863
- yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.sync(() => {
864
- releaseStartedAt = recorder.offsetNow();
865
- }));
866
- }
867
- // 超时收尾段的证据保全(docs/runner.md「超时:双层保护」超时不丢证据)。注册在这里,
868
- // LIFO 意味着 release 时它先于上面已注册的 sandbox stop/suspend finalizer 跑——此刻沙箱
869
- // (若有)仍然存活,赶在停之前抢一次 workspace.diff 折叠;`timedOut` 由 Effect.timeoutTo
870
- // 的 onTimeout 同步置位,正常收尾(body 走完)时这里直接跳过,不重复采一次已经采过的证据。
871
- // 沙箱此刻可能已经被 agent 搞挂——withCleanupTimeout 有界执行,导出挂起也不能拖住收尾,
872
- // 到点如实缺失。计时单独记一条 phases 条目,不入 durationMs(durationMs 已在 onTimeout
873
- // 按中断时刻定格)。sources 折叠是本地文件读取,与沙箱是否存活无关,一并在这里补。
874
- yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.gen(function* () {
875
- if (!timedOut)
876
- return;
877
- if (agent.kind === "sandbox" && liveLedger) {
878
- const startedAt = recorder.offsetNow();
879
- const diff = yield* effect_1.Effect.result((0, cleanup_timeout_ts_1.cleanupCallback)(() => liveLedger.exportWindows()));
880
- if (effect_1.Result.isSuccess(diff)) {
881
- timeoutDiff = diff.success;
882
- let document;
883
- try {
884
- document = (0, workspace_diff_ts_1.createAgentWorkspaceDiff)({
885
- windows: diff.success,
886
- policy: liveLedger.attribution,
887
- });
879
+ if (agent.kind === "sandbox" && !reusedSandbox) {
880
+ // 后加先跑:release 链开始时打起点戳(与上面的终点戳配对,测出整段 sandbox.stop)。
881
+ yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.sync(() => {
882
+ releaseStartedAt = recorder.offsetNow();
883
+ }));
884
+ }
885
+ // 超时收尾段的证据保全(docs/runner.md「超时:双层保护」超时不丢证据)。注册在这里,
886
+ // LIFO 意味着 release 时它先于上面已注册的 sandbox stop/suspend finalizer 跑——此刻沙箱
887
+ // (若有)仍然存活,赶在停之前抢一次 workspace.diff 折叠;`timedOut` 由 Effect.timeoutTo
888
+ // 的 onTimeout 同步置位,正常收尾(body 走完)时这里直接跳过,不重复采一次已经采过的证据。
889
+ // 沙箱此刻可能已经被 agent 搞挂——withCleanupTimeout 有界执行,导出挂起也不能拖住收尾,
890
+ // 到点如实缺失。计时单独记一条 phases 条目,不入 durationMs(durationMs 已在 onTimeout
891
+ // 按中断时刻定格)。sources 折叠是本地文件读取,与沙箱是否存活无关,一并在这里补。
892
+ yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.gen(function* () {
893
+ if (!timedOut)
894
+ return;
895
+ if (agent.kind === "sandbox" && liveLedger) {
896
+ const startedAt = recorder.offsetNow();
897
+ const diff = yield* effect_1.Effect.result((0, cleanup_timeout_ts_1.cleanupCallback)(() => liveLedger.exportWindows()));
898
+ if (effect_1.Result.isSuccess(diff)) {
899
+ timeoutDiff = diff.success;
900
+ let document;
901
+ try {
902
+ document = (0, workspace_diff_ts_1.createAgentWorkspaceDiff)({
903
+ windows: diff.success,
904
+ policy: liveLedger.attribution,
905
+ });
906
+ }
907
+ catch {
908
+ timeoutFileChangesCapture = (0, diff_ts_2.createEmptyFileChangesCapture)({
909
+ policy: liveLedger.attribution,
910
+ limitations: [Object.freeze({
911
+ code: "capture-failed",
912
+ stage: "normalize",
913
+ atWindowId: null,
914
+ })],
915
+ });
916
+ }
917
+ if (document !== undefined) {
918
+ timeoutFileChangesCapture = (0, diff_ts_2.createFileChangesCapture)({
919
+ document,
920
+ limitations: [Object.freeze({
921
+ code: "capture-interrupted",
922
+ stage: "finalizer-export",
923
+ atWindowId: null,
924
+ })],
925
+ });
926
+ }
888
927
  }
889
- catch {
928
+ else {
890
929
  timeoutFileChangesCapture = (0, diff_ts_2.createEmptyFileChangesCapture)({
891
930
  policy: liveLedger.attribution,
892
- limitations: [Object.freeze({
893
- code: "capture-failed",
894
- stage: "normalize",
895
- atWindowId: null,
896
- })],
897
- });
898
- }
899
- if (document !== undefined) {
900
- timeoutFileChangesCapture = (0, diff_ts_2.createFileChangesCapture)({
901
- document,
902
931
  limitations: [Object.freeze({
903
932
  code: "capture-interrupted",
904
933
  stage: "finalizer-export",
@@ -906,427 +935,425 @@ ${recentLogs.map((l) => ` · ${l}`).join("\n")}`;
906
935
  })],
907
936
  });
908
937
  }
938
+ if (timeoutFileChangesCapture !== undefined) {
939
+ (0, diff_ts_2.assertFileChangesCaptureRecordable)(timeoutFileChangesCapture);
940
+ }
941
+ // 沙箱不可用 / 导出挂起到点:如实缺失,不阻塞收尾。
942
+ recorder.record("workspace.diff", recorder.offsetNow() - startedAt);
909
943
  }
910
- else {
911
- timeoutFileChangesCapture = (0, diff_ts_2.createEmptyFileChangesCapture)({
912
- policy: liveLedger.attribution,
913
- limitations: [Object.freeze({
914
- code: "capture-interrupted",
915
- stage: "finalizer-export",
916
- atWindowId: null,
917
- })],
918
- });
944
+ const sources = yield* effect_1.Effect.result((0, cleanup_timeout_ts_1.cleanupCallback)(() => collectSources(evalDef.source, sourceRegistry)));
945
+ if (effect_1.Result.isSuccess(sources))
946
+ timeoutSources = sources.success;
947
+ // 源码读不到:如实缺失,不阻塞收尾。
948
+ }));
949
+ // Matcher-only source material must not outlive the Attempt. Register
950
+ // this before the interruption seal so LIFO runs that seal first, then
951
+ // clears the sidecar immediately afterwards.
952
+ yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.sync(() => liveAssertionState?.manager.releaseObservedEvaluationSegments()));
953
+ // Scope release owns the interruption seal. It runs before resource
954
+ // release, so entries already declared by the real Context survive a
955
+ // timeout or external cancellation in declaration order.
956
+ yield* effect_1.Effect.addFinalizer((exit) => effect_1.Effect.suspend(() => {
957
+ const interrupted = timedOut || (effect_1.Exit.isFailure(exit) && effect_1.Cause.hasInterruptsOnly(exit.cause));
958
+ if (interrupted)
959
+ sourceCapture.markInterrupted();
960
+ if (!interrupted || liveAssertions === undefined || assertionsSealed) {
961
+ return effect_1.Effect.void;
919
962
  }
920
- if (timeoutFileChangesCapture !== undefined) {
921
- (0, diff_ts_2.assertFileChangesCaptureRecordable)(timeoutFileChangesCapture);
963
+ if (liveAssertionState?.late.diff.state === "pending") {
964
+ liveAssertionState.late.diff = Object.freeze({
965
+ state: "unavailable",
966
+ reason: "producer-interrupted",
967
+ });
922
968
  }
923
- // 沙箱不可用 / 导出挂起到点:如实缺失,不阻塞收尾。
924
- recorder.record("workspace.diff", recorder.offsetNow() - startedAt);
925
- }
926
- const sources = yield* effect_1.Effect.result((0, cleanup_timeout_ts_1.cleanupCallback)(() => collectSources(evalDef.source, sourceRegistry)));
927
- if (effect_1.Result.isSuccess(sources))
928
- timeoutSources = sources.success;
929
- // 源码读不到:如实缺失,不阻塞收尾。
930
- }));
931
- // Matcher-only source material must not outlive the Attempt. Register
932
- // this before the interruption seal so LIFO runs that seal first, then
933
- // clears the sidecar immediately afterwards.
934
- yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.sync(() => liveAssertionState?.manager.releaseObservedEvaluationSegments()));
935
- // Scope release owns the interruption seal. It runs before resource
936
- // release, so entries already declared by the real Context survive a
937
- // timeout or external cancellation in declaration order.
938
- yield* effect_1.Effect.addFinalizer((exit) => effect_1.Effect.suspend(() => {
939
- const interrupted = timedOut || (effect_1.Exit.isFailure(exit) && effect_1.Cause.hasInterruptsOnly(exit.cause));
940
- if (interrupted)
941
- sourceCapture.markInterrupted();
942
- if (!interrupted || liveAssertions === undefined || assertionsSealed) {
943
- return effect_1.Effect.void;
944
- }
945
- if (liveAssertionState?.late.diff.state === "pending") {
946
- liveAssertionState.late.diff = Object.freeze({
947
- state: "unavailable",
948
- reason: "producer-interrupted",
949
- });
950
- }
951
- return (0, assertions_ts_1.sealAttemptAssertions)(liveAssertions, {
952
- execution: "errored",
953
- interrupted: true,
954
- }).pipe(effect_1.Effect.tap((sealed) => effect_1.Effect.sync(() => {
955
- markExecutionTerminal();
956
- timeoutSealedAssertions = sealed;
957
- })), effect_1.Effect.tap((sealed) => onSealedEvaluation?.(sealed) ?? effect_1.Effect.void),
958
- // Release must preserve the original interruption/failure. A
959
- // malformed attachment is still reflected by the sealed runtime,
960
- // but cannot mask the owning Attempt's terminal cause.
961
- effect_1.Effect.catchCause(() => effect_1.Effect.void));
962
- }));
963
- // Scope 的 LIFO 让作者 cleanup 在 agent teardown 后、Provider Case finalizer 前执行。
964
- // Cleanup must not fork back into this already-closing Attempt Scope, so it uses the resource
965
- // facade captured above while retaining the same physical Sandbox and provider release owner.
966
- if (agent.kind === "sandbox") {
967
- yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.suspend(() => {
968
- if (layerCleanups.length === 0)
969
- return effect_1.Effect.void;
970
- enterPhase("sandbox.cleanup");
971
- const phaseStartedAt = recorder.offsetNow();
972
- const before = diagnostics.length;
973
- return effect_1.Effect.forEach([...layerCleanups].reverse(), (cleanup) => {
974
- const startedAt = recorder.offsetNow();
975
- const node = recorder.child((0, timing_ts_1.sandboxPrepareActivity)({
976
- label: `${cleanup.label} cleanup`,
977
- startOffsetMs: startedAt,
978
- }));
979
- if (node)
980
- recorder.pushParent(node);
981
- return (0, cleanup_timeout_ts_1.cleanupCallback)((cleanupSignal) => cleanup.command(cleanupCommandTarget, {
982
- ...cleanup.context,
983
- signal: cleanupSignal,
984
- })).pipe(effect_1.Effect.catch((error) => effect_1.Effect.sync(() => {
969
+ return (0, assertions_ts_1.sealAttemptAssertions)(liveAssertions, {
970
+ execution: "errored",
971
+ interrupted: true,
972
+ }).pipe(effect_1.Effect.tap((sealed) => effect_1.Effect.sync(() => {
973
+ markExecutionTerminal();
974
+ timeoutSealedAssertions = sealed;
975
+ })), effect_1.Effect.tap((sealed) => onSealedEvaluation?.(sealed) ?? effect_1.Effect.void),
976
+ // Release must preserve the original interruption/failure. A
977
+ // malformed attachment is still reflected by the sealed runtime,
978
+ // but cannot mask the owning Attempt's terminal cause.
979
+ effect_1.Effect.catchCause(() => effect_1.Effect.void));
980
+ }));
981
+ // Scope 的 LIFO 让作者 cleanup 在 agent teardown 后、Provider Case finalizer 前执行。
982
+ // Cleanup must not fork back into this already-closing Attempt Scope, so it uses the resource
983
+ // facade captured above while retaining the same physical Sandbox and provider release owner.
984
+ if (agent.kind === "sandbox") {
985
+ yield* effect_1.Effect.addFinalizer(() => effect_1.Effect.suspend(() => {
986
+ if (layerCleanups.length === 0)
987
+ return effect_1.Effect.void;
988
+ enterPhase("sandbox.cleanup");
989
+ const phaseStartedAt = recorder.offsetNow();
990
+ const before = diagnostics.length;
991
+ return effect_1.Effect.forEach([...layerCleanups].reverse(), (cleanup) => {
992
+ const startedAt = recorder.offsetNow();
993
+ const node = recorder.child((0, timing_ts_1.sandboxPrepareActivity)({
994
+ label: `${cleanup.label} cleanup`,
995
+ startOffsetMs: startedAt,
996
+ }));
985
997
  if (node)
986
- node.failed = true;
987
- declareFailure("sandbox.cleanup", error);
988
- diagnostics.push(teardownDiagnostic("sandbox.cleanup", error));
989
- recordSandboxCleanupFailure("sandbox.cleanup", error);
990
- })), effect_1.Effect.ensuring(effect_1.Effect.sync(() => {
991
- if (node) {
992
- node.durationMs = Math.max(0, recorder.offsetNow() - startedAt);
993
- recorder.popParent();
994
- }
998
+ recorder.pushParent(node);
999
+ return (0, cleanup_timeout_ts_1.cleanupCallback)((cleanupSignal) => cleanup.command(cleanupCommandTarget, {
1000
+ ...cleanup.context,
1001
+ signal: cleanupSignal,
1002
+ })).pipe(effect_1.Effect.catch((error) => effect_1.Effect.sync(() => {
1003
+ if (node)
1004
+ node.failed = true;
1005
+ declareFailure("sandbox.cleanup", error);
1006
+ diagnostics.push(teardownDiagnostic("sandbox.cleanup", error));
1007
+ recordSandboxCleanupFailure("sandbox.cleanup", error);
1008
+ })), effect_1.Effect.ensuring(effect_1.Effect.sync(() => {
1009
+ if (node) {
1010
+ node.durationMs = Math.max(0, recorder.offsetNow() - startedAt);
1011
+ recorder.popParent();
1012
+ }
1013
+ })));
1014
+ }, { discard: true }).pipe(effect_1.Effect.ensuring(effect_1.Effect.sync(() => {
1015
+ recorder.record("sandbox.cleanup", Math.max(0, recorder.offsetNow() - phaseStartedAt), diagnostics.length > before);
995
1016
  })));
996
- }, { discard: true }).pipe(effect_1.Effect.ensuring(effect_1.Effect.sync(() => {
997
- recorder.record("sandbox.cleanup", Math.max(0, recorder.offsetNow() - phaseStartedAt), diagnostics.length > before);
998
- })));
1017
+ }));
1018
+ }
1019
+ // Cross-send adapter processes belong to the Attempt Scope, not to the
1020
+ // Promise-shaped body. On timeout the bridge is intentionally sealed
1021
+ // before that Promise's finally continuation can submit more work, so a
1022
+ // scope finalizer must remain the unconditional release owner.
1023
+ yield* effect_1.Effect.addFinalizer(() => (0, cleanup_timeout_ts_1.cleanupCallback)((cleanupSignal) => attemptResources.releaseAll(cleanupSignal)).pipe(effect_1.Effect.catch((error) => effect_1.Effect.sync(() => {
1024
+ reportAttemptResourceReleaseFailure(error);
1025
+ }))));
1026
+ // Register the cleanup join after every outer resource finalizer, then
1027
+ // register the forward close last. Scope LIFO therefore first rejects
1028
+ // outstanding Promise work, then lets the body finish Agent teardown →
1029
+ // managed Attempt release → Eval Plugin teardown, and only afterwards
1030
+ // releases the enclosing Sandbox / Provider owners. The join abandons
1031
+ // only a body that never enters cleanup; each cleanup callback owns its
1032
+ // deadline without sharing an aggregate deadline with later callbacks.
1033
+ yield* effect_1.Effect.addFinalizer(() => assertFirst.awaitCleanup());
1034
+ yield* effect_1.Effect.addFinalizer((exit) => effect_1.Effect.suspend(() => {
1035
+ const interrupted = timedOut || (effect_1.Exit.isFailure(exit) && effect_1.Cause.hasInterruptsOnly(exit.cause));
1036
+ return assertFirst.closeEffectRequests(new api_ts_1.AssertionAuthoringClosedError(interrupted ? "attempt-interrupted" : "attempt-sealed"));
999
1037
  }));
1000
- }
1001
- // Cross-send adapter processes belong to the Attempt Scope, not to the
1002
- // Promise-shaped body. On timeout the bridge is intentionally sealed
1003
- // before that Promise's finally continuation can submit more work, so a
1004
- // scope finalizer must remain the unconditional release owner.
1005
- yield* effect_1.Effect.addFinalizer(() => (0, cleanup_timeout_ts_1.cleanupCallback)((cleanupSignal) => attemptResources.releaseAll(cleanupSignal)).pipe(effect_1.Effect.catch((error) => effect_1.Effect.sync(() => {
1006
- reportAttemptResourceReleaseFailure(error);
1007
- }))));
1008
- // Register the cleanup join after every outer resource finalizer, then
1009
- // register the forward close last. Scope LIFO therefore first rejects
1010
- // outstanding Promise work, then lets the body finish Agent teardown →
1011
- // managed Attempt release → Eval Plugin teardown, and only afterwards
1012
- // releases the enclosing Sandbox / Provider owners. The join abandons
1013
- // only a body that never enters cleanup; each cleanup callback owns its
1014
- // deadline without sharing an aggregate deadline with later callbacks.
1015
- yield* effect_1.Effect.addFinalizer(() => assertFirst.awaitCleanup());
1016
- yield* effect_1.Effect.addFinalizer((exit) => effect_1.Effect.suspend(() => {
1017
- const interrupted = timedOut || (effect_1.Exit.isFailure(exit) && effect_1.Cause.hasInterruptsOnly(exit.cause));
1018
- return assertFirst.closeEffectRequests(new api_ts_1.AssertionAuthoringClosedError(interrupted ? "attempt-interrupted" : "attempt-sealed"));
1019
- }));
1020
- // The legacy setup/diff body remains Promise-shaped, while OTLP waits,
1021
- // author execution, and sealing run in this Attempt's Effect scope. No
1022
- // nested runtime is introduced.
1023
- const bodyFiber = yield* effect_1.Effect.forkChild(tryPromiseAsDefect((interruptSignal) => runAttemptBody(a, config, t0, base, {
1024
- sandbox,
1025
- sourceRegistry,
1026
- sourceCapture,
1027
- receiver,
1028
- telemetry,
1029
- otel: otelChannel,
1030
- signal: AbortSignal.any([signal, interruptSignal]),
1031
- log,
1032
- enterPhase,
1033
- // send 在飞时归因到嵌套的 agent.run(不切换顶层阶段,见 LifecyclePhase 注释)。
1034
- getPhase: () => (sendActive ? "agent.run" : lastPhase),
1035
- setSendActive: (active) => {
1036
- sendActive = active;
1037
- },
1038
- recorder,
1039
- ...(attemptTimeout ? { attemptTimeout } : {}),
1040
- ...(deadlineAt !== undefined ? { deadlineAt } : {}),
1041
- feedback: scopedFeedback,
1042
- diagnostics,
1043
- commands,
1044
- sensitiveValues,
1045
- observabilityRuntime,
1046
- ...(materializedCase === undefined ? {} : { materializedCase }),
1047
- ...(preparedSetupPrefix === undefined ? {} : { preparedSetupPrefix }),
1048
- concurrencySlot,
1049
- declareFailure,
1050
- onEnvironmentIncomplete,
1051
- isDeadlineTimedOut: () => timedOut,
1052
- layerCleanups,
1053
- attemptResources,
1054
- reportAttemptResourceReleaseFailure,
1055
- registerEvidence: (getEvents, getUsage, getRetryAttempts) => {
1056
- liveEvents = getEvents;
1057
- liveUsage = getUsage;
1058
- liveRetryAttempts = getRetryAttempts;
1059
- },
1060
- registerLedger: (ledger) => {
1061
- liveLedger = ledger;
1062
- },
1063
- retainFileChangesCapture: (capture) => {
1064
- fileChangesCapture = capture;
1065
- },
1066
- assertFirst,
1067
- registerAssertions: (runtime, state) => {
1068
- if (liveAssertions !== undefined && liveAssertions !== runtime) {
1069
- throw new Error("Attempt tried to register a second Assertions runtime");
1070
- }
1071
- if (liveAssertionState !== undefined && liveAssertionState !== state) {
1072
- throw new Error("Attempt tried to register a second Assertions state");
1038
+ // The legacy setup/diff body remains Promise-shaped, while OTLP waits,
1039
+ // author execution, and sealing run in this Attempt's Effect scope. No
1040
+ // nested runtime is introduced.
1041
+ const bodyFiber = yield* effect_1.Effect.forkChild(tryPromiseAsDefect((interruptSignal) => runAttemptBody(a, config, t0, base, {
1042
+ judgeUsage,
1043
+ sandbox,
1044
+ sourceRegistry,
1045
+ sourceCapture,
1046
+ receiver,
1047
+ telemetry,
1048
+ otel: otelChannel,
1049
+ signal: AbortSignal.any([signal, interruptSignal]),
1050
+ log,
1051
+ enterPhase,
1052
+ // send 在飞时归因到嵌套的 agent.run(不切换顶层阶段,见 LifecyclePhase 注释)。
1053
+ getPhase: () => (sendActive ? "agent.run" : lastPhase),
1054
+ setSendActive: (active) => {
1055
+ sendActive = active;
1056
+ },
1057
+ recorder,
1058
+ ...(attemptTimeout ? { attemptTimeout } : {}),
1059
+ ...(deadlineAt !== undefined ? { deadlineAt } : {}),
1060
+ feedback: scopedFeedback,
1061
+ diagnostics,
1062
+ commands,
1063
+ sensitiveValues,
1064
+ observabilityRuntime,
1065
+ ...(materializedCase === undefined ? {} : { materializedCase }),
1066
+ ...(preparedSetupPrefix === undefined ? {} : { preparedSetupPrefix }),
1067
+ concurrencySlot,
1068
+ declareFailure,
1069
+ onEnvironmentIncomplete,
1070
+ isDeadlineTimedOut: () => timedOut,
1071
+ layerCleanups,
1072
+ attemptResources,
1073
+ reportAttemptResourceReleaseFailure,
1074
+ registerEvidence: (getEvents, getUsage, getRetryAttempts) => {
1075
+ liveEvents = getEvents;
1076
+ liveUsage = getUsage;
1077
+ liveRetryAttempts = getRetryAttempts;
1078
+ },
1079
+ registerLedger: (ledger) => {
1080
+ liveLedger = ledger;
1081
+ },
1082
+ retainFileChangesCapture: (capture) => {
1083
+ fileChangesCapture = capture;
1084
+ },
1085
+ assertFirst,
1086
+ registerAssertions: (runtime, state) => {
1087
+ if (liveAssertions !== undefined && liveAssertions !== runtime) {
1088
+ throw new Error("Attempt tried to register a second Assertions runtime");
1089
+ }
1090
+ if (liveAssertionState !== undefined && liveAssertionState !== state) {
1091
+ throw new Error("Attempt tried to register a second Assertions state");
1092
+ }
1093
+ liveAssertions = runtime;
1094
+ liveAssertionState = state;
1095
+ },
1096
+ ...(opts.artifactPrepare !== undefined ? { prepareCoordinator: opts.artifactPrepare } : {}),
1097
+ }, parentSignal)));
1098
+ const contextExit = yield* effect_1.Effect.exit(assertFirst.awaitContext());
1099
+ if (effect_1.Exit.isFailure(contextExit)) {
1100
+ if (effect_1.Cause.hasInterruptsOnly(contextExit.cause))
1101
+ return yield* effect_1.Effect.interrupt;
1102
+ // Context construction failed before the Agent body could register its
1103
+ // runtime. Seal the single empty execution-error result now so the
1104
+ // execution deadline retires before the Promise body's existing
1105
+ // Agent/Plugin cleanup chain is joined.
1106
+ const sealed = yield* sealExecutionError();
1107
+ const bodyResult = yield* effect_1.Fiber.join(bodyFiber);
1108
+ return withSealedAssertions(bodyResult, sealed);
1109
+ }
1110
+ const author = effect_1.Effect.suspend(() => {
1111
+ // This is the original frozen author Context; the assertion only
1112
+ // resolves Eval-kind overloads and never substitutes a wrapper.
1113
+ // Both Promise and deferred Effect calls capture through explicit
1114
+ // Attempt-owned dependencies, never fiber-local ALS.
1115
+ const returned = evalDef.test(contextExit.value);
1116
+ if (effect_1.Effect.isEffect(returned)) {
1117
+ return returned;
1073
1118
  }
1074
- liveAssertions = runtime;
1075
- liveAssertionState = state;
1076
- },
1077
- ...(opts.artifactPrepare !== undefined ? { prepareCoordinator: opts.artifactPrepare } : {}),
1078
- }, parentSignal)));
1079
- const contextExit = yield* effect_1.Effect.exit(assertFirst.awaitContext());
1080
- if (effect_1.Exit.isFailure(contextExit)) {
1081
- if (effect_1.Cause.hasInterruptsOnly(contextExit.cause))
1082
- return yield* effect_1.Effect.interrupt;
1083
- // Context construction failed before the Agent body could register its
1084
- // runtime. Seal the single empty execution-error result now so the
1085
- // execution deadline retires before the Promise body's existing
1086
- // Agent/Plugin cleanup chain is joined.
1087
- const sealed = yield* sealExecutionError();
1088
- const bodyResult = yield* effect_1.Fiber.join(bodyFiber);
1089
- return withSealedAssertions(bodyResult, sealed);
1090
- }
1091
- const author = effect_1.Effect.suspend(() => {
1092
- // This is the original frozen author Context; the assertion only
1093
- // resolves Eval-kind overloads and never substitutes a wrapper.
1094
- // Both Promise and deferred Effect calls capture through explicit
1095
- // Attempt-owned dependencies, never fiber-local ALS.
1096
- const returned = evalDef.test(contextExit.value);
1097
- if (effect_1.Effect.isEffect(returned)) {
1098
- return returned;
1099
- }
1100
- return effect_1.Effect.tryPromise({
1101
- try: () => Promise.resolve(returned),
1102
- catch: (error) => error,
1119
+ return effect_1.Effect.tryPromise({
1120
+ try: () => Promise.resolve(returned),
1121
+ catch: (error) => error,
1122
+ });
1103
1123
  });
1104
- });
1105
- const authorExit = yield* effect_1.Effect.exit(author);
1106
- if (effect_1.Exit.isSuccess(authorExit)) {
1107
- yield* assertFirst.completeAuthor({ _tag: "succeeded" });
1108
- }
1109
- else {
1110
- if (effect_1.Cause.hasInterruptsOnly(authorExit.cause))
1111
- return yield* effect_1.Effect.interrupt;
1112
- const failure = effect_1.Cause.findErrorOption(authorExit.cause);
1113
- const defect = effect_1.Cause.findDefect(authorExit.cause);
1114
- const completion = effect_1.Option.isSome(failure)
1115
- ? isAssertionStopError(failure.value)
1116
- ? { _tag: "stopped" }
1117
- : { _tag: "failed", error: failure.value }
1118
- : { _tag: "defect", cause: effect_1.Result.isSuccess(defect) ? defect.success : authorExit.cause };
1119
- yield* assertFirst.completeAuthor(completion);
1120
- }
1121
- // The declared author callback has returned. A detached continuation may
1122
- // still hold a handle, but it cannot enqueue a new control barrier while
1123
- // this Attempt moves to its single ordered seal.
1124
- yield* assertFirst.closeAssertionRequests(new api_ts_1.AssertionAuthoringClosedError("attempt-sealing"));
1125
- const sealRequestExit = yield* effect_1.Effect.exit(assertFirst.awaitSealRequest());
1126
- if (effect_1.Exit.isFailure(sealRequestExit)) {
1127
- if (effect_1.Cause.hasInterruptsOnly(sealRequestExit.cause))
1128
- return yield* effect_1.Effect.interrupt;
1129
- return yield* effect_1.Fiber.join(bodyFiber);
1130
- }
1131
- const sealExit = yield* effect_1.Effect.exit((0, assertions_ts_1.sealAttemptAssertions)(sealRequestExit.value.runtime, sealRequestExit.value.options, sealRequestExit.value.workspaceDiff).pipe(effect_1.Effect.tap(() => effect_1.Effect.sync(() => {
1132
- markExecutionTerminal();
1133
- })), effect_1.Effect.tap((sealed) => onSealedEvaluation?.(sealed) ?? effect_1.Effect.void)));
1134
- liveAssertionState?.manager.releaseObservedEvaluationSegments();
1135
- if (effect_1.Exit.isSuccess(sealExit)) {
1136
- yield* assertFirst.completeSeal(sealExit.value);
1137
- }
1138
- else {
1139
- if (effect_1.Cause.hasInterruptsOnly(sealExit.cause))
1140
- return yield* effect_1.Effect.interrupt;
1141
- const failure = effect_1.Cause.findErrorOption(sealExit.cause);
1142
- const defect = effect_1.Cause.findDefect(sealExit.cause);
1143
- yield* assertFirst.failSeal(effect_1.Option.isSome(failure)
1144
- ? failure.value
1145
- : effect_1.Result.isSuccess(defect)
1146
- ? defect.success
1147
- : effect_1.Cause.squash(sealExit.cause));
1148
- }
1149
- const bodyResult = yield* effect_1.Fiber.join(bodyFiber);
1150
- // 留存提交:verdict 定稿、其余收尾(teardown 链、diff 采集)已在 body 内完成后,按档位
1151
- // 提交——failed 档留 failed/errored,all 档全部;顺序不可调换:先原子写登记项,写入成功
1152
- // 才把 disposition 改成 keep;写入失败保持 stop、记 diagnostic,`sandbox.kept` 不得为 true。
1153
- const keepMode = opts.keepSandbox;
1154
- if (agent.kind === "sandbox" &&
1155
- keepMode !== undefined &&
1156
- a.locator !== undefined &&
1157
- (keepMode === "all" || bodyResult.verdict === "failed" || bodyResult.verdict === "errored")) {
1158
- const providerName = runtimeCapabilities.provider;
1159
- if (runtimeCapabilities.retention._tag === "Suspendable") {
1160
- const enter = (0, keep_ts_1.nativeEnterCommand)(providerName, sandbox.sandboxId);
1161
- const keptAt = new Date().toISOString();
1162
- const expiresAt = (0, keep_ts_1.computeExpiresAt)(providerName, keptAt);
1163
- const registered = yield* effect_1.Effect.result((0, keep_registry_ts_1.writeKeptEntryEffect)(coordinationRoot, {
1164
- sandboxId: sandbox.sandboxId,
1165
- provider: providerName,
1166
- evalId: evalDef.id,
1167
- attempt,
1168
- ...(run.experimentId !== undefined ? { experimentId: run.experimentId } : {}),
1169
- locator: String(a.locator),
1170
- verdict: bodyResult.verdict,
1171
- keptAt,
1172
- workdir: sandbox.workdir,
1173
- ...(enter !== undefined ? { enter } : {}),
1174
- ...(expiresAt !== undefined ? { expiresAt } : {}),
1175
- state: "alive",
1176
- }));
1177
- if (effect_1.Result.isSuccess(registered)) {
1178
- disposition = "keep";
1179
- (0, sink_ts_1.reportKept)({
1180
- locator: a.locator,
1181
- identity,
1182
- who: (0, types_ts_1.runWho)({ agentName: agent.name, model: run.model, experimentId: run.experimentId }),
1183
- verdict: bodyResult.verdict,
1184
- provider: providerName,
1124
+ const authorExit = yield* effect_1.Effect.exit(author);
1125
+ if (effect_1.Exit.isSuccess(authorExit)) {
1126
+ yield* assertFirst.completeAuthor({ _tag: "succeeded" });
1127
+ }
1128
+ else {
1129
+ if (effect_1.Cause.hasInterruptsOnly(authorExit.cause))
1130
+ return yield* effect_1.Effect.interrupt;
1131
+ const failure = effect_1.Cause.findErrorOption(authorExit.cause);
1132
+ const defect = effect_1.Cause.findDefect(authorExit.cause);
1133
+ const completion = effect_1.Option.isSome(failure)
1134
+ ? isAssertionStopError(failure.value)
1135
+ ? { _tag: "stopped" }
1136
+ : { _tag: "failed", error: failure.value }
1137
+ : { _tag: "defect", cause: effect_1.Result.isSuccess(defect) ? defect.success : authorExit.cause };
1138
+ yield* assertFirst.completeAuthor(completion);
1139
+ }
1140
+ // The declared author callback has returned. A detached continuation may
1141
+ // still hold a handle, but it cannot enqueue a new control barrier while
1142
+ // this Attempt moves to its single ordered seal.
1143
+ yield* assertFirst.closeAssertionRequests(new api_ts_1.AssertionAuthoringClosedError("attempt-sealing"));
1144
+ const sealRequestExit = yield* effect_1.Effect.exit(assertFirst.awaitSealRequest());
1145
+ if (effect_1.Exit.isFailure(sealRequestExit)) {
1146
+ if (effect_1.Cause.hasInterruptsOnly(sealRequestExit.cause))
1147
+ return yield* effect_1.Effect.interrupt;
1148
+ return yield* effect_1.Fiber.join(bodyFiber);
1149
+ }
1150
+ const sealExit = yield* effect_1.Effect.exit((0, assertions_ts_1.sealAttemptAssertions)(sealRequestExit.value.runtime, sealRequestExit.value.options, sealRequestExit.value.workspaceDiff).pipe(effect_1.Effect.tap(() => effect_1.Effect.sync(() => {
1151
+ markExecutionTerminal();
1152
+ })), effect_1.Effect.tap((sealed) => onSealedEvaluation?.(sealed) ?? effect_1.Effect.void)));
1153
+ liveAssertionState?.manager.releaseObservedEvaluationSegments();
1154
+ if (effect_1.Exit.isSuccess(sealExit)) {
1155
+ yield* assertFirst.completeSeal(sealExit.value);
1156
+ }
1157
+ else {
1158
+ if (effect_1.Cause.hasInterruptsOnly(sealExit.cause))
1159
+ return yield* effect_1.Effect.interrupt;
1160
+ const failure = effect_1.Cause.findErrorOption(sealExit.cause);
1161
+ const defect = effect_1.Cause.findDefect(sealExit.cause);
1162
+ yield* assertFirst.failSeal(effect_1.Option.isSome(failure)
1163
+ ? failure.value
1164
+ : effect_1.Result.isSuccess(defect)
1165
+ ? defect.success
1166
+ : effect_1.Cause.squash(sealExit.cause));
1167
+ }
1168
+ const bodyResult = yield* effect_1.Fiber.join(bodyFiber);
1169
+ // 留存提交:verdict 定稿、其余收尾(teardown 链、diff 采集)已在 body 内完成后,按档位
1170
+ // 提交——failed 档留 failed/errored,all 档全部;顺序不可调换:先原子写登记项,写入成功
1171
+ // 才把 disposition 改成 keep;写入失败保持 stop、记 diagnostic,`sandbox.kept` 不得为 true。
1172
+ const keepMode = opts.keepSandbox;
1173
+ if (agent.kind === "sandbox" &&
1174
+ keepMode !== undefined &&
1175
+ a.locator !== undefined &&
1176
+ (keepMode === "all" || bodyResult.verdict === "failed" || bodyResult.verdict === "errored")) {
1177
+ const providerName = runtimeCapabilities.provider;
1178
+ if (runtimeCapabilities.retention._tag === "Suspendable") {
1179
+ const enter = (0, keep_ts_1.nativeEnterCommand)(providerName, sandbox.sandboxId);
1180
+ const keptAt = new Date().toISOString();
1181
+ const expiresAt = (0, keep_ts_1.computeExpiresAt)(providerName, keptAt);
1182
+ const registered = yield* effect_1.Effect.result((0, keep_registry_ts_1.writeKeptEntryEffect)(coordinationRoot, {
1185
1183
  sandboxId: sandbox.sandboxId,
1184
+ provider: providerName,
1185
+ evalId: evalDef.id,
1186
+ attempt,
1187
+ ...(run.experimentId !== undefined ? { experimentId: run.experimentId } : {}),
1188
+ locator: String(a.locator),
1189
+ verdict: bodyResult.verdict,
1190
+ keptAt,
1191
+ workdir: sandbox.workdir,
1186
1192
  ...(enter !== undefined ? { enter } : {}),
1193
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
1194
+ state: "alive",
1195
+ }));
1196
+ if (effect_1.Result.isSuccess(registered)) {
1197
+ disposition = "keep";
1198
+ (0, sink_ts_1.reportKept)({
1199
+ locator: a.locator,
1200
+ identity,
1201
+ who: (0, types_ts_1.runWho)({ agentName: agent.name, model: run.model, experimentId: run.experimentId }),
1202
+ verdict: bodyResult.verdict,
1203
+ provider: providerName,
1204
+ sandboxId: sandbox.sandboxId,
1205
+ ...(enter !== undefined ? { enter } : {}),
1206
+ });
1207
+ // 只有 `kept` 在收尾时点决定;provider / sandboxId 等归属键仍由租借时刻的
1208
+ // sandboxFacts 统一挂上(见下方 Effect.map)。
1209
+ kept = true;
1210
+ return bodyResult;
1211
+ }
1212
+ const error = registered.failure;
1213
+ recordDiagnostic({
1214
+ code: "sandbox-keep-failed",
1215
+ level: "warning",
1216
+ message: `failed to register kept sandbox ${sandbox.sandboxId}; it will be destroyed normally: ${error instanceof Error ? error.message : String(error)}`,
1217
+ dedupeKey: `sandbox-keep-failed:${sandbox.sandboxId}`,
1187
1218
  });
1188
- // 只有 `kept` 在收尾时点决定;provider / sandboxId 等归属键仍由租借时刻的
1189
- // sandboxFacts 统一挂上(见下方 Effect.map)。
1190
- kept = true;
1191
- return bodyResult;
1192
1219
  }
1193
- const error = registered.failure;
1194
- recordDiagnostic({
1195
- code: "sandbox-keep-failed",
1196
- level: "warning",
1197
- message: `failed to register kept sandbox ${sandbox.sandboxId}; it will be destroyed normally: ${error instanceof Error ? error.message : String(error)}`,
1198
- dedupeKey: `sandbox-keep-failed:${sandbox.sandboxId}`,
1220
+ }
1221
+ return bodyResult;
1222
+ }).pipe(applyAttemptDeadline,
1223
+ // Observe only interruption of the complete Attempt execution, never a
1224
+ // losing internal race fiber. The original Cause continues unchanged.
1225
+ effect_1.Effect.onInterrupt(() => effect_1.Effect.sync(() => {
1226
+ if (!assertionsSealed)
1227
+ forwardParentAbort();
1228
+ }))))).pipe(
1229
+ // The execution race ends before Scope finalizers start. Cleanup retains
1230
+ // its own budgets; custom capture failure participates in the final fold.
1231
+ // body 自己已兜了 agent 执行错;这里兜的是资源获取 / Sample 层的意外(起沙箱失败等)。
1232
+ // 中断【不】吞:此时 Sample 已跑完 release(容器已停),把中断继续上抛,让 forEach 整体停掉,
1233
+ // 否则会把中断「恢复」成一条 errored 结果、并让后续 attempt 继续起 —— 那就停不下来了。
1234
+ // `timeoutTo` 已在内层把自己的中断消费成 onTimeout 的成功值。这里不能仅凭 Cause 中含
1235
+ // interrupt 就升级成整批取消:earlyExit 也会中断 losing attempt 的 fiber。整个 Invocation
1236
+ // 是否取消只由 invocationSignal 裁决;其它中断在本 attempt 内封成 errored,随后由调度器
1237
+ // 的 earlyExit 去重分支丢弃。兄弟 fiber 的真实 defect 仍由它自己的 Cause 向外传播。
1238
+ effect_1.Effect.catchCause((cause) => isAttemptAborted(invocationSignal) && adapter.kind !== "custom"
1239
+ ? effect_1.Effect.interrupt
1240
+ : effect_1.Effect.suspend(() => {
1241
+ invocationInterrupted = isAttemptAborted(invocationSignal) && effect_1.Cause.hasInterruptsOnly(cause);
1242
+ // 资源获取 / Sample 层的意外(起沙箱失败、provisioning 的确定性配置死因)同样是终局
1243
+ // 失败:先读空间轴回执,再折成纯数据 AttemptError(顺序不可换,见 declareFailure)。
1244
+ const raw = effect_1.Cause.squash(cause);
1245
+ const phase = (sendActive ? "agent.run" : lastPhase) ?? "eval.run";
1246
+ declareFailure(phase, raw);
1247
+ const error = timedOut && deadlineError !== undefined
1248
+ ? deadlineError
1249
+ : errorFromThrown(raw, sendActive ? "agent.run" : lastPhase, attemptTimeout);
1250
+ const fallback = {
1251
+ ...base,
1252
+ durationMs: recorder.offsetNow(),
1253
+ error,
1254
+ ...(evalDef.evaluationKind === "score" ? { scoreResult: scoreFactOutcomeForAttemptError(error) } : {}),
1255
+ ...(commands.length > 0 ? { commands: [...commands] } : {}),
1256
+ };
1257
+ return effect_1.Effect.succeed(fallback);
1258
+ })),
1259
+ // A prepare/setup failure can be caught by runAttemptBody before a real
1260
+ // TestContext exists. In that case the scoped body returns an errored
1261
+ // value successfully, so catchAllCause above never runs. Enforce the
1262
+ // Record boundary as an output postcondition: every executed errored
1263
+ // Attempt owns one sealed (possibly empty) assertion result.
1264
+ effect_1.Effect.flatMap((result) => assertionsSealed || result.verdict !== "errored"
1265
+ ? effect_1.Effect.succeed(result)
1266
+ : sealExecutionError().pipe(effect_1.Effect.map((sealed) => withSealedAssertions(result, sealed)), effect_1.Effect.catch(() => effect_1.Effect.succeed(result)))), effect_1.Effect.map((result) => !timedOut || timeoutSealedAssertions === undefined
1267
+ ? result
1268
+ : withSealedAssertions(result, timeoutSealedAssertions)), effect_1.Effect.flatMap((result) => effect_1.Effect.gen(function* () {
1269
+ if (adapter.kind !== "custom")
1270
+ return result;
1271
+ const captureFailure = adapterUsage?.failure ?? adapterAttachments?.failure() ?? adapterExecutionTraces?.failure();
1272
+ // Cleanup must not erase the original execution failure or timeout attribution.
1273
+ // Its own timeout remains independently recorded as a teardown diagnostic.
1274
+ const error = result.error ?? adapterCleanupError ?? (captureFailure === undefined
1275
+ ? undefined
1276
+ : errorFromThrown(captureFailure, "attempt.teardown"));
1277
+ let sealed = adapterSealedAssertions;
1278
+ if (sealed === undefined)
1279
+ return result;
1280
+ if (error !== undefined || invocationInterrupted) {
1281
+ // Assertions were evaluated once before cleanup. Only the execution
1282
+ // outcome changes; the existing domain folds retain every earned point.
1283
+ const evaluation = Object.freeze({ ...sealed.evaluation, execution: "errored" });
1284
+ const score = evalDef.evaluationKind === "score" ? (0, score_ts_1.buildScorePayload)(evaluation) : undefined;
1285
+ if (score !== undefined && effect_1.Result.isFailure(score))
1286
+ return yield* effect_1.Effect.fail(score.failure);
1287
+ sealed = Object.freeze({
1288
+ ...sealed,
1289
+ evaluation,
1290
+ verdict: Object.freeze({ state: (0, verdict_ts_1.foldVerdict)(evaluation) }),
1291
+ ...(score === undefined ? {} : { score: score.success }),
1199
1292
  });
1200
1293
  }
1201
- }
1202
- return bodyResult;
1203
- }).pipe(applyAttemptDeadline))).pipe(
1204
- // The execution race ends before Scope finalizers start. Cleanup retains
1205
- // its own budgets; custom capture failure participates in the final fold.
1206
- // body 自己已兜了 agent 执行错;这里兜的是资源获取 / Sample 层的意外(起沙箱失败等)。
1207
- // 中断【不】吞:此时 Sample 已跑完 release(容器已停),把中断继续上抛,让 forEach 整体停掉,
1208
- // 否则会把中断「恢复」成一条 errored 结果、并让后续 attempt 继续起 —— 那就停不下来了。
1209
- // `timeoutTo` 已在内层把自己的中断消费成 onTimeout 的成功值。这里不能仅凭 Cause 中含
1210
- // interrupt 就升级成整批取消:earlyExit 也会中断 losing attempt 的 fiber。整个 Invocation
1211
- // 是否取消只由 invocationSignal 裁决;其它中断在本 attempt 内封成 errored,随后由调度器
1212
- // 的 earlyExit 去重分支丢弃。兄弟 fiber 的真实 defect 仍由它自己的 Cause 向外传播。
1213
- effect_1.Effect.catchCause((cause) => isAttemptAborted(invocationSignal) && adapter.kind !== "custom"
1214
- ? effect_1.Effect.interrupt
1215
- : effect_1.Effect.suspend(() => {
1216
- invocationInterrupted = isAttemptAborted(invocationSignal) && effect_1.Cause.hasInterruptsOnly(cause);
1217
- // 资源获取 / Sample 层的意外(起沙箱失败、provisioning 的确定性配置死因)同样是终局
1218
- // 失败:先读空间轴回执,再折成纯数据 AttemptError(顺序不可换,见 declareFailure)。
1219
- const raw = effect_1.Cause.squash(cause);
1220
- const phase = (sendActive ? "agent.run" : lastPhase) ?? "eval.run";
1221
- declareFailure(phase, raw);
1222
- const error = timedOut && deadlineError !== undefined
1223
- ? deadlineError
1224
- : errorFromThrown(raw, sendActive ? "agent.run" : lastPhase, attemptTimeout);
1225
- const fallback = {
1226
- ...base,
1227
- durationMs: recorder.offsetNow(),
1228
- error,
1229
- ...(evalDef.evaluationKind === "score" ? { scoreResult: scoreFactOutcomeForAttemptError(error) } : {}),
1294
+ yield* onSealedEvaluation?.(sealed) ?? effect_1.Effect.void;
1295
+ return withSealedAssertions({ ...result, ...(error === undefined ? {} : { error }) }, sealed);
1296
+ })),
1297
+ // 结果封口在 Sample release 完成之后:sandbox.stop 已由 finalizer 写进 recorder,
1298
+ // 这里把完整的阶段计时挂到即将交还的结果上(timeout / scope 兜底分支同样带上)。超时路径
1299
+ // 额外把上面那个 finalizer 折叠出的 workspace.diff / sources 并进来——它俩是异步产出,
1300
+ // 必须等 Sample release(本 map 之前的所有 finalizer)跑完才有值,不能在 onTimeout 的同步
1301
+ // 回调里就地给,原理与 phases 完全一致(见该 finalizer 与 onTimeout 的注释)。
1302
+ effect_1.Effect.map((r) => {
1303
+ const withEvidence = {
1304
+ ...r,
1305
+ ...(diagnostics.length > 0 ? { diagnostics: [...diagnostics] } : {}),
1230
1306
  ...(commands.length > 0 ? { commands: [...commands] } : {}),
1231
1307
  };
1232
- return effect_1.Effect.succeed(fallback);
1233
- })),
1234
- // A prepare/setup failure can be caught by runAttemptBody before a real
1235
- // TestContext exists. In that case the scoped body returns an errored
1236
- // value successfully, so catchAllCause above never runs. Enforce the
1237
- // Record boundary as an output postcondition: every executed errored
1238
- // Attempt owns one sealed (possibly empty) assertion result.
1239
- effect_1.Effect.flatMap((result) => assertionsSealed || result.verdict !== "errored"
1240
- ? effect_1.Effect.succeed(result)
1241
- : sealExecutionError().pipe(effect_1.Effect.map((sealed) => withSealedAssertions(result, sealed)), effect_1.Effect.catch(() => effect_1.Effect.succeed(result)))), effect_1.Effect.map((result) => !timedOut || timeoutSealedAssertions === undefined
1242
- ? result
1243
- : withSealedAssertions(result, timeoutSealedAssertions)), effect_1.Effect.flatMap((result) => effect_1.Effect.gen(function* () {
1244
- if (adapter.kind !== "custom")
1245
- return result;
1246
- const captureFailure = adapterUsage?.failure ?? adapterAttachments?.failure() ?? adapterExecutionTraces?.failure();
1247
- // Cleanup must not erase the original execution failure or timeout attribution.
1248
- // Its own timeout remains independently recorded as a teardown diagnostic.
1249
- const error = result.error ?? adapterCleanupError ?? (captureFailure === undefined
1250
- ? undefined
1251
- : errorFromThrown(captureFailure, "attempt.teardown"));
1252
- let sealed = adapterSealedAssertions;
1253
- if (sealed === undefined)
1254
- return result;
1255
- if (error !== undefined || invocationInterrupted) {
1256
- // Assertions were evaluated once before cleanup. Only the execution
1257
- // outcome changes; the existing domain folds retain every earned point.
1258
- const evaluation = Object.freeze({ ...sealed.evaluation, execution: "errored" });
1259
- const score = evalDef.evaluationKind === "score" ? (0, score_ts_1.buildScorePayload)(evaluation) : undefined;
1260
- if (score !== undefined && effect_1.Result.isFailure(score))
1261
- return yield* effect_1.Effect.fail(score.failure);
1262
- sealed = Object.freeze({
1263
- ...sealed,
1264
- evaluation,
1265
- verdict: Object.freeze({ state: (0, verdict_ts_1.foldVerdict)(evaluation) }),
1266
- ...(score === undefined ? {} : { score: score.success }),
1267
- });
1268
- }
1269
- yield* onSealedEvaluation?.(sealed) ?? effect_1.Effect.void;
1270
- return withSealedAssertions({ ...result, ...(error === undefined ? {} : { error }) }, sealed);
1271
- })),
1272
- // 结果封口在 Sample release 完成之后:sandbox.stop 已由 finalizer 写进 recorder,
1273
- // 这里把完整的阶段计时挂到即将交还的结果上(timeout / scope 兜底分支同样带上)。超时路径
1274
- // 额外把上面那个 finalizer 折叠出的 workspace.diff / sources 并进来——它俩是异步产出,
1275
- // 必须等 Sample release(本 map 之前的所有 finalizer)跑完才有值,不能在 onTimeout 的同步
1276
- // 回调里就地给,原理与 phases 完全一致(见该 finalizer 与 onTimeout 的注释)。
1277
- effect_1.Effect.map((r) => {
1278
- const withEvidence = {
1279
- ...r,
1280
- ...(diagnostics.length > 0 ? { diagnostics: [...diagnostics] } : {}),
1281
- ...(commands.length > 0 ? { commands: [...commands] } : {}),
1282
- };
1283
- const phases = recorder.finalize();
1284
- // 现有阶段计时尚未把 sandbox.create 起点单独持久化;在它可用前落同口径的保守值,
1285
- // 读取旧记录也按 durationMs 回退,绝不因缺值错误携带。
1286
- const withPhases = {
1287
- ...(phases ? { ...withEvidence, phases } : withEvidence),
1288
- executionMs: withEvidence.executionMs ?? withEvidence.durationMs,
1289
- };
1290
- // 调度事实统一在这里挂:每条出口(正常、body 抛错、超时、Sample 层失败)都经过本 map,
1291
- // 归属因此不随「走到了哪一步」丢失(见 sandboxFacts 的声明)。
1292
- const withSandbox = sandboxFacts
1293
- ? {
1294
- ...withPhases,
1295
- sandbox: {
1296
- ...sandboxFacts,
1297
- // SetupPrefix restore/capture rebases the stable backend onto a new private clone.
1298
- // Read the final dynamic facade here instead of retaining the acquisition id.
1299
- sandboxId: currentSandbox?.sandboxId ?? sandboxFacts.sandboxId,
1300
- ...(kept ? { kept: true } : {}),
1301
- },
1302
- }
1303
- : withPhases;
1304
- const completed = timedOut
1305
- ? {
1306
- ...withSandbox,
1307
- ...(timeoutDiff !== undefined ? { diff: timeoutDiff } : {}),
1308
- sources: timeoutSources ?? [],
1309
- }
1310
- : withSandbox;
1311
- const finalResult = (0, source_producer_ts_1.retainRunnerAttemptSourceCapture)(redactEvalResultEvidence(completed, sensitiveValues), sourceCapture.snapshot());
1312
- const capture = timedOut
1313
- ? timeoutFileChangesCapture ?? fileChangesCapture
1314
- : fileChangesCapture;
1315
- if (capture !== undefined) {
1316
- (0, sandbox_record_producer_ts_1.retainRunnerAttemptFileChangesCapture)(finalResult, capture);
1317
- }
1318
- if (adapterUsageSnapshot !== undefined)
1319
- (0, adapter_usage_ts_1.retainAdapterUsage)(finalResult, adapterUsageSnapshot);
1320
- if (adapterAttachments !== undefined)
1321
- (0, adapter_attachments_ts_1.retainAdapterAttachments)(finalResult, adapterAttachments.snapshot());
1322
- if (adapterExecutionTraces !== undefined)
1323
- (0, adapter_execution_trace_ts_1.retainAdapterExecutionTraces)(finalResult, adapterExecutionTraces.snapshot());
1324
- return finalResult;
1325
- }), effect_1.Effect.tap((finalResult) => (0, runtime_ts_3.bindRunnerAttemptObservabilityCapture)(finalResult, observabilityRuntime)), effect_1.Effect.flatMap((finalResult) => invocationInterrupted
1326
- ? (onInterruptedResult?.(finalResult) ?? effect_1.Effect.void).pipe(effect_1.Effect.andThen(effect_1.Effect.interrupt))
1327
- : effect_1.Effect.succeed(finalResult)), effect_1.Effect.ensuring(effect_1.Effect.sync(() => {
1328
- parentSignal?.removeEventListener("abort", forwardParentAbort);
1329
- }))));
1308
+ const phases = recorder.finalize();
1309
+ // 现有阶段计时尚未把 sandbox.create 起点单独持久化;在它可用前落同口径的保守值,
1310
+ // 读取旧记录也按 durationMs 回退,绝不因缺值错误携带。
1311
+ const withPhases = {
1312
+ ...(phases ? { ...withEvidence, phases } : withEvidence),
1313
+ executionMs: withEvidence.executionMs ?? withEvidence.durationMs,
1314
+ };
1315
+ // 调度事实统一在这里挂:每条出口(正常、body 抛错、超时、Sample 层失败)都经过本 map,
1316
+ // 归属因此不随「走到了哪一步」丢失(见 sandboxFacts 的声明)。
1317
+ const withSandbox = sandboxFacts
1318
+ ? {
1319
+ ...withPhases,
1320
+ sandbox: {
1321
+ ...sandboxFacts,
1322
+ // SetupPrefix restore/capture rebases the stable backend onto a new private clone.
1323
+ // Read the final dynamic facade here instead of retaining the acquisition id.
1324
+ sandboxId: currentSandbox?.sandboxId ?? sandboxFacts.sandboxId,
1325
+ ...(kept ? { kept: true } : {}),
1326
+ },
1327
+ }
1328
+ : withPhases;
1329
+ const completed = timedOut
1330
+ ? {
1331
+ ...withSandbox,
1332
+ ...(timeoutDiff !== undefined ? { diff: timeoutDiff } : {}),
1333
+ sources: timeoutSources ?? [],
1334
+ }
1335
+ : withSandbox;
1336
+ const finalResult = (0, source_producer_ts_1.retainRunnerAttemptSourceCapture)(redactEvalResultEvidence(completed, sensitiveValues), sourceCapture.snapshot());
1337
+ const capture = timedOut
1338
+ ? timeoutFileChangesCapture ?? fileChangesCapture
1339
+ : fileChangesCapture;
1340
+ if (capture !== undefined) {
1341
+ (0, sandbox_record_producer_ts_1.retainRunnerAttemptFileChangesCapture)(finalResult, capture);
1342
+ }
1343
+ (0, judge_usage_ts_2.retainJudgeUsage)(finalResult, judgeUsage.snapshot());
1344
+ if (adapterUsageSnapshot !== undefined)
1345
+ (0, adapter_usage_ts_1.retainAdapterUsage)(finalResult, adapterUsageSnapshot);
1346
+ if (adapterAttachments !== undefined)
1347
+ (0, adapter_attachments_ts_1.retainAdapterAttachments)(finalResult, adapterAttachments.snapshot());
1348
+ if (adapterExecutionTraces !== undefined)
1349
+ (0, adapter_execution_trace_ts_1.retainAdapterExecutionTraces)(finalResult, adapterExecutionTraces.snapshot());
1350
+ return finalResult;
1351
+ }), effect_1.Effect.tap((finalResult) => (0, runtime_ts_3.bindRunnerAttemptObservabilityCapture)(finalResult, observabilityRuntime)), effect_1.Effect.flatMap((finalResult) => invocationInterrupted
1352
+ ? (onInterruptedResult?.(finalResult) ?? effect_1.Effect.void).pipe(effect_1.Effect.andThen(effect_1.Effect.interrupt))
1353
+ : effect_1.Effect.succeed(finalResult)), effect_1.Effect.ensuring(effect_1.Effect.sync(() => {
1354
+ parentSignal?.removeEventListener("abort", forwardParentAbort);
1355
+ }))));
1356
+ }));
1330
1357
  }
1331
1358
  function adapterAuthorCallbackEffect(resources, signal, callback) {
1332
1359
  const author = effect_1.Effect.tryPromise({
@@ -1349,6 +1376,9 @@ function runAdapterAttemptBody(input) {
1349
1376
  const { a, base, adapter, resources, usage, attachments, executionTraces, signal, sourceCapture, sourceRegistry, assertFirst, enterPhase, recorder, feedback, log, declareFailure, registerAssertions, closeAuthoring, markAssertionsSealed, } = input;
1350
1377
  return effect_1.Effect.gen(function* () {
1351
1378
  const { context: core, state } = (0, assert_first_ts_1.createAssertFirstCoreContext)({
1379
+ readUsage: () => usage.snapshot(),
1380
+ judgeUsage: input.judgeUsage,
1381
+ elapsedMs: recorder.offsetNow,
1352
1382
  evaluationKind: a.evalDef.evaluationKind ?? "pass",
1353
1383
  model: a.run.model,
1354
1384
  reasoningEffort: a.run.reasoningEffort,
@@ -1372,6 +1402,7 @@ function runAdapterAttemptBody(input) {
1372
1402
  experimentId: a.run.experimentId,
1373
1403
  attempt: a.attempt,
1374
1404
  signal,
1405
+ models: a.run.models ?? Object.freeze({}),
1375
1406
  model: a.run.model,
1376
1407
  reasoningEffort: a.run.reasoningEffort,
1377
1408
  flags: a.run.flags,
@@ -1380,6 +1411,7 @@ function runAdapterAttemptBody(input) {
1380
1411
  log,
1381
1412
  onCleanup: (cleanup) => resources.onCleanup(cleanup),
1382
1413
  recordUsage: usage.record,
1414
+ sealUsage: usage.seal,
1383
1415
  attach: (input) => resources.trackHandoff(attachments.attach(input)),
1384
1416
  recordTrace: executionTraces.recordTrace,
1385
1417
  });
@@ -1457,12 +1489,15 @@ function runAdapterAttemptBody(input) {
1457
1489
  });
1458
1490
  }
1459
1491
  function cleanupAdapterResources(resources, input) {
1460
- return effect_1.Effect.suspend(() => {
1492
+ return effect_1.Effect.clockWith((clock) => effect_1.Effect.suspend(() => {
1493
+ const timeoutMs = input.timeoutMs;
1494
+ const deadlineAt = clock.currentTimeMillisUnsafe() + timeoutMs;
1495
+ const cleanupStartedAt = clock.monotonicTimeNanosUnsafe();
1461
1496
  resources.beginCleanup();
1462
1497
  input.enterPhase("attempt.teardown");
1463
1498
  const startedAt = input.recorder.offsetNow();
1464
1499
  let failed = false;
1465
- return (0, cleanup_timeout_ts_1.cleanupCallback)((signal) => resources.cleanup(signal), cleanup_timeout_ts_1.CLEANUP_TIMEOUT_MS).pipe(effect_1.Effect.tap((result) => effect_1.Effect.sync(() => {
1500
+ return (0, cleanup_timeout_ts_1.cleanupCallback)((signal) => resources.cleanup(Object.freeze({ signal, timeoutMs, deadlineAt })), Math.max(0, Math.ceil(timeoutMs - Number(clock.monotonicTimeNanosUnsafe() - cleanupStartedAt) / 1000000))).pipe(effect_1.Effect.tap((result) => effect_1.Effect.sync(() => {
1466
1501
  for (const failure of result.failures) {
1467
1502
  failed = true;
1468
1503
  input.feedback.diagnostic({
@@ -1475,13 +1510,13 @@ function cleanupAdapterResources(resources, input) {
1475
1510
  failed = true;
1476
1511
  input.onTimeout({
1477
1512
  code: "adapter-cleanup-timeout",
1478
- message: `Adapter cleanup timed out after ${cleanup_timeout_ts_1.CLEANUP_TIMEOUT_MS}ms`,
1513
+ message: `Adapter cleanup timed out after ${timeoutMs}ms`,
1479
1514
  origin: (0, timing_ts_1.attemptOrigin)("attempt.teardown"),
1480
1515
  });
1481
1516
  input.feedback.diagnostic({
1482
1517
  code: "adapter-cleanup-timeout",
1483
1518
  level: "warning",
1484
- message: `Adapter cleanup timed out after ${cleanup_timeout_ts_1.CLEANUP_TIMEOUT_MS}ms`,
1519
+ message: `Adapter cleanup timed out after ${timeoutMs}ms`,
1485
1520
  });
1486
1521
  }
1487
1522
  })), effect_1.Effect.catch((failure) => effect_1.Effect.sync(() => {
@@ -1496,7 +1531,7 @@ function cleanupAdapterResources(resources, input) {
1496
1531
  resources.close();
1497
1532
  input.recorder.record("attempt.teardown", input.recorder.offsetNow() - startedAt, failed || undefined);
1498
1533
  })));
1499
- });
1534
+ }));
1500
1535
  }
1501
1536
  /**
1502
1537
  * `EvalResult` still has historical renderer fields while its replacement
@@ -2470,6 +2505,7 @@ async function runAttemptBody(a, config, t0, base, res, parentSignal) {
2470
2505
  } }),
2471
2506
  model: run.model,
2472
2507
  reasoningEffort: run.reasoningEffort,
2508
+ judgeUsage: res.judgeUsage,
2473
2509
  // maxCost 断言的估算价目表;与 observed usage.costUSD 互不兜底。
2474
2510
  pricing: config.pricing,
2475
2511
  flags: run.flags,
@@ -2503,6 +2539,7 @@ async function runAttemptBody(a, config, t0, base, res, parentSignal) {
2503
2539
  }
2504
2540
  : undefined,
2505
2541
  onSendActive: setSendActive,
2542
+ elapsedMs: recorder.offsetNow,
2506
2543
  timingNow: recorder.offsetNow,
2507
2544
  // 每次 send 一个 turn 节点:本地单调时钟测得的端到端包络 + 与 diff/source 共用的
2508
2545
  // session/turn 身份 token;
@@ -3263,6 +3300,7 @@ async function collectSources(evalSource, registry) {
3263
3300
  function experimentRunInfo(run, plan, sandboxPlansByEval, config, judge) {
3264
3301
  const runLevelTimeoutMs = run.timeoutMs ?? config?.timeoutMs;
3265
3302
  return {
3303
+ ...(run.models === undefined ? {} : { models: run.models }),
3266
3304
  ...(run.description !== undefined ? { description: run.description } : {}),
3267
3305
  ...(run.reasoningEffort !== undefined ? { reasoningEffort: run.reasoningEffort } : {}),
3268
3306
  ...(Object.keys(run.flags).length > 0 ? { flags: run.flags } : {}),