stageflow 0.39.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/README.md +46 -19
  2. package/dist/agent/claudeAdapter.js +15 -1
  3. package/dist/agent/claudeTools.d.ts +13 -0
  4. package/dist/agent/claudeTools.js +98 -0
  5. package/dist/agent/piAdapter.js +38 -16
  6. package/dist/agent/sandboxPiTools.d.ts +99 -0
  7. package/dist/agent/sandboxPiTools.js +109 -0
  8. package/dist/agents/authorChat.d.ts +4 -0
  9. package/dist/agents/authorChat.js +113 -12
  10. package/dist/agents/authorSessionStore.d.ts +35 -0
  11. package/dist/agents/authorSessionStore.js +152 -0
  12. package/dist/agents/chatTurn.d.ts +2 -0
  13. package/dist/agents/chatTurn.js +17 -1
  14. package/dist/agents/errors.d.ts +1 -1
  15. package/dist/agents/errors.js +1 -0
  16. package/dist/agents/evalGate.d.ts +61 -0
  17. package/dist/agents/evalGate.js +103 -0
  18. package/dist/agents/evalSession.d.ts +26 -0
  19. package/dist/agents/evalSession.js +29 -0
  20. package/dist/agents/hostContext.d.ts +3 -0
  21. package/dist/agents/hostContext.js +4 -1
  22. package/dist/agents/runtime/piRuntime.d.ts +1 -1
  23. package/dist/agents/runtime/piRuntime.js +4 -4
  24. package/dist/agents/runtime/types.d.ts +3 -1
  25. package/dist/agents/service.d.ts +27 -2
  26. package/dist/agents/service.js +88 -13
  27. package/dist/agents/sessions.d.ts +8 -0
  28. package/dist/agents/sessions.js +50 -10
  29. package/dist/agents/store.d.ts +14 -3
  30. package/dist/agents/store.js +45 -7
  31. package/dist/agents/toolRisk.d.ts +21 -0
  32. package/dist/agents/toolRisk.js +50 -0
  33. package/dist/cli/doctorCommand.js +26 -0
  34. package/dist/cli/evalCommand.d.ts +20 -0
  35. package/dist/cli/evalCommand.js +720 -0
  36. package/dist/cli/exportAllCommand.js +1 -1
  37. package/dist/cli/hostClient.d.ts +60 -0
  38. package/dist/cli/hostClient.js +71 -0
  39. package/dist/cli/validateOutput.d.ts +2 -2
  40. package/dist/cli/validateOutput.js +2 -2
  41. package/dist/cli.js +10 -0
  42. package/dist/config/atomicWrite.d.ts +15 -0
  43. package/dist/config/atomicWrite.js +43 -0
  44. package/dist/config/browseCatalog.d.ts +2 -0
  45. package/dist/config/browseCatalog.js +4 -2
  46. package/dist/config/csv.d.ts +9 -0
  47. package/dist/config/csv.js +81 -0
  48. package/dist/config/datasetAgentCheck.d.ts +18 -0
  49. package/dist/config/datasetAgentCheck.js +109 -0
  50. package/dist/config/datasetCandidates.d.ts +22 -0
  51. package/dist/config/datasetCandidates.js +86 -0
  52. package/dist/config/datasetFromSessions.d.ts +34 -0
  53. package/dist/config/datasetFromSessions.js +105 -0
  54. package/dist/config/datasetHash.d.ts +1 -0
  55. package/dist/config/datasetHash.js +4 -0
  56. package/dist/config/datasetImport.d.ts +21 -0
  57. package/dist/config/datasetImport.js +120 -0
  58. package/dist/config/datasetStageCheck.d.ts +9 -0
  59. package/dist/config/datasetStageCheck.js +17 -0
  60. package/dist/config/datasetTypes.d.ts +27 -0
  61. package/dist/config/datasetTypes.js +1 -0
  62. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  63. package/dist/config/ensureCatalogScanRoot.js +2 -6
  64. package/dist/config/evalCatalog.d.ts +22 -0
  65. package/dist/config/evalCatalog.js +67 -0
  66. package/dist/config/evalWriteRoots.d.ts +17 -0
  67. package/dist/config/evalWriteRoots.js +109 -0
  68. package/dist/config/hostConfig.js +3 -0
  69. package/dist/config/installEvalSample.d.ts +34 -0
  70. package/dist/config/installEvalSample.js +182 -0
  71. package/dist/config/loadDataset.d.ts +16 -0
  72. package/dist/config/loadDataset.js +318 -0
  73. package/dist/config/loadEval.d.ts +32 -0
  74. package/dist/config/loadEval.js +1085 -0
  75. package/dist/config/loadOutcome.d.ts +5 -0
  76. package/dist/config/loadStageflowManifest.js +64 -0
  77. package/dist/config/loadTask.js +34 -1
  78. package/dist/config/resolveAgentTarget.d.ts +32 -0
  79. package/dist/config/resolveAgentTarget.js +118 -0
  80. package/dist/config/resolveEvalTarget.d.ts +21 -0
  81. package/dist/config/resolveEvalTarget.js +89 -0
  82. package/dist/config/scanCatalogPaths.d.ts +1 -1
  83. package/dist/config/scanCatalogPaths.js +17 -5
  84. package/dist/config/validateCatalog.d.ts +1 -1
  85. package/dist/config/validateCatalog.js +9 -1
  86. package/dist/config/validateEval.d.ts +36 -0
  87. package/dist/config/validateEval.js +405 -0
  88. package/dist/config/writeDataset.d.ts +54 -0
  89. package/dist/config/writeDataset.js +186 -0
  90. package/dist/config/writeEval.d.ts +25 -0
  91. package/dist/config/writeEval.js +398 -0
  92. package/dist/diagnostics/checks.js +1 -1
  93. package/dist/evals/agentDiff.d.ts +13 -0
  94. package/dist/evals/agentDiff.js +11 -0
  95. package/dist/evals/canonical.d.ts +3 -0
  96. package/dist/evals/canonical.js +24 -0
  97. package/dist/evals/caseTask.d.ts +3 -0
  98. package/dist/evals/caseTask.js +9 -0
  99. package/dist/evals/compare.d.ts +94 -0
  100. package/dist/evals/compare.js +201 -0
  101. package/dist/evals/constants.d.ts +60 -0
  102. package/dist/evals/constants.js +95 -0
  103. package/dist/evals/engine/FakeEngine.d.ts +33 -0
  104. package/dist/evals/engine/FakeEngine.js +176 -0
  105. package/dist/evals/engine/index.d.ts +9 -0
  106. package/dist/evals/engine/index.js +19 -0
  107. package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +52 -0
  108. package/dist/evals/engine/promptfoo/PromptfooEngine.js +311 -0
  109. package/dist/evals/engine/promptfoo/errors.d.ts +5 -0
  110. package/dist/evals/engine/promptfoo/errors.js +8 -0
  111. package/dist/evals/engine/promptfoo/generate.d.ts +30 -0
  112. package/dist/evals/engine/promptfoo/generate.js +342 -0
  113. package/dist/evals/engine/promptfoo/guard.d.ts +10 -0
  114. package/dist/evals/engine/promptfoo/guard.js +116 -0
  115. package/dist/evals/engine/promptfoo/install.d.ts +17 -0
  116. package/dist/evals/engine/promptfoo/install.js +73 -0
  117. package/dist/evals/engine/promptfoo/judgeProviders.d.ts +2 -0
  118. package/dist/evals/engine/promptfoo/judgeProviders.js +9 -0
  119. package/dist/evals/engine/promptfoo/judgeServices.d.ts +6 -0
  120. package/dist/evals/engine/promptfoo/judgeServices.js +41 -0
  121. package/dist/evals/engine/promptfoo/locate.d.ts +21 -0
  122. package/dist/evals/engine/promptfoo/locate.js +68 -0
  123. package/dist/evals/engine/promptfoo/nodeVersion.d.ts +7 -0
  124. package/dist/evals/engine/promptfoo/nodeVersion.js +14 -0
  125. package/dist/evals/engine/promptfoo/normalize.d.ts +66 -0
  126. package/dist/evals/engine/promptfoo/normalize.js +261 -0
  127. package/dist/evals/engine/promptfoo/pinned.d.ts +5 -0
  128. package/dist/evals/engine/promptfoo/pinned.js +5 -0
  129. package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +14 -0
  130. package/dist/evals/engine/promptfoo/rubricPrompt.js +103 -0
  131. package/dist/evals/engine/promptfoo/runTokens.d.ts +18 -0
  132. package/dist/evals/engine/promptfoo/runTokens.js +31 -0
  133. package/dist/evals/engine/promptfoo/status.d.ts +13 -0
  134. package/dist/evals/engine/promptfoo/status.js +16 -0
  135. package/dist/evals/estimate.d.ts +56 -0
  136. package/dist/evals/estimate.js +66 -0
  137. package/dist/evals/graders/codeGrader.d.ts +6 -0
  138. package/dist/evals/graders/codeGrader.js +85 -0
  139. package/dist/evals/graders/conversationCheckGrader.d.ts +6 -0
  140. package/dist/evals/graders/conversationCheckGrader.js +62 -0
  141. package/dist/evals/graders/fieldGrader.d.ts +11 -0
  142. package/dist/evals/graders/fieldGrader.js +117 -0
  143. package/dist/evals/graders/goal.d.ts +3 -0
  144. package/dist/evals/graders/goal.js +14 -0
  145. package/dist/evals/graders/index.d.ts +18 -0
  146. package/dist/evals/graders/index.js +34 -0
  147. package/dist/evals/graders/notApplicable.d.ts +5 -0
  148. package/dist/evals/graders/notApplicable.js +9 -0
  149. package/dist/evals/graders/path.d.ts +26 -0
  150. package/dist/evals/graders/path.js +75 -0
  151. package/dist/evals/graders/referenceGrader.d.ts +5 -0
  152. package/dist/evals/graders/referenceGrader.js +59 -0
  153. package/dist/evals/graders/schemaGrader.d.ts +5 -0
  154. package/dist/evals/graders/schemaGrader.js +29 -0
  155. package/dist/evals/graders/subject.d.ts +6 -0
  156. package/dist/evals/graders/subject.js +33 -0
  157. package/dist/evals/graders/toolCallsGrader.d.ts +6 -0
  158. package/dist/evals/graders/toolCallsGrader.js +100 -0
  159. package/dist/evals/graders/transcript.d.ts +12 -0
  160. package/dist/evals/graders/transcript.js +57 -0
  161. package/dist/evals/graders/types.d.ts +20 -0
  162. package/dist/evals/graders/types.js +1 -0
  163. package/dist/evals/judgeProviders.d.ts +16 -0
  164. package/dist/evals/judgeProviders.js +27 -0
  165. package/dist/evals/lineDiff.d.ts +5 -0
  166. package/dist/evals/lineDiff.js +34 -0
  167. package/dist/evals/listStatus.d.ts +31 -0
  168. package/dist/evals/listStatus.js +48 -0
  169. package/dist/evals/manager.d.ts +174 -0
  170. package/dist/evals/manager.js +787 -0
  171. package/dist/evals/plan.d.ts +58 -0
  172. package/dist/evals/plan.js +602 -0
  173. package/dist/evals/providerKeys.d.ts +1 -0
  174. package/dist/evals/providerKeys.js +21 -0
  175. package/dist/evals/read/agentTargets.d.ts +37 -0
  176. package/dist/evals/read/agentTargets.js +101 -0
  177. package/dist/evals/read/api.d.ts +196 -0
  178. package/dist/evals/read/api.js +358 -0
  179. package/dist/evals/read/compare.d.ts +4 -0
  180. package/dist/evals/read/compare.js +39 -0
  181. package/dist/evals/read/datasets.d.ts +105 -0
  182. package/dist/evals/read/datasets.js +318 -0
  183. package/dist/evals/read/list.d.ts +66 -0
  184. package/dist/evals/read/list.js +173 -0
  185. package/dist/evals/read/results.d.ts +46 -0
  186. package/dist/evals/read/results.js +107 -0
  187. package/dist/evals/read/runs.d.ts +149 -0
  188. package/dist/evals/read/runs.js +289 -0
  189. package/dist/evals/read/suggestions.d.ts +43 -0
  190. package/dist/evals/read/suggestions.js +73 -0
  191. package/dist/evals/runner/FakeAgentBackend.d.ts +40 -0
  192. package/dist/evals/runner/FakeAgentBackend.js +82 -0
  193. package/dist/evals/runner/agentCaseRunner.d.ts +24 -0
  194. package/dist/evals/runner/agentCaseRunner.js +388 -0
  195. package/dist/evals/runner/caseRunner.d.ts +7 -0
  196. package/dist/evals/runner/caseRunner.js +17 -0
  197. package/dist/evals/runner/conversationSubject.d.ts +18 -0
  198. package/dist/evals/runner/conversationSubject.js +123 -0
  199. package/dist/evals/runner/simulatorModel.d.ts +51 -0
  200. package/dist/evals/runner/simulatorModel.js +58 -0
  201. package/dist/evals/runner/simulatorPrompt.d.ts +29 -0
  202. package/dist/evals/runner/simulatorPrompt.js +59 -0
  203. package/dist/evals/runner/stageCaseRunner.d.ts +30 -0
  204. package/dist/evals/runner/stageCaseRunner.js +279 -0
  205. package/dist/evals/runner/userDriver.d.ts +71 -0
  206. package/dist/evals/runner/userDriver.js +104 -0
  207. package/dist/evals/scoring.d.ts +34 -0
  208. package/dist/evals/scoring.js +67 -0
  209. package/dist/evals/snapshot.d.ts +183 -0
  210. package/dist/evals/snapshot.js +65 -0
  211. package/dist/evals/stageDiff.d.ts +8 -0
  212. package/dist/evals/stageDiff.js +48 -0
  213. package/dist/evals/stats.d.ts +95 -0
  214. package/dist/evals/stats.js +220 -0
  215. package/dist/evals/statsAdapter.d.ts +4 -0
  216. package/dist/evals/statsAdapter.js +30 -0
  217. package/dist/evals/statsTypes.d.ts +51 -0
  218. package/dist/evals/statsTypes.js +18 -0
  219. package/dist/evals/storeError.d.ts +5 -0
  220. package/dist/evals/storeError.js +8 -0
  221. package/dist/evals/storeTypes.d.ts +207 -0
  222. package/dist/evals/storeTypes.js +1 -0
  223. package/dist/evals/tTable.d.ts +3 -0
  224. package/dist/evals/tTable.js +15 -0
  225. package/dist/evals/triage/constants.d.ts +26 -0
  226. package/dist/evals/triage/constants.js +33 -0
  227. package/dist/evals/triage/credentials.d.ts +13 -0
  228. package/dist/evals/triage/credentials.js +20 -0
  229. package/dist/evals/triage/jevClassifier.d.ts +14 -0
  230. package/dist/evals/triage/jevClassifier.js +124 -0
  231. package/dist/evals/triage/judgeClassifier.d.ts +10 -0
  232. package/dist/evals/triage/judgeClassifier.js +76 -0
  233. package/dist/evals/triage/prompts.d.ts +16 -0
  234. package/dist/evals/triage/prompts.js +64 -0
  235. package/dist/evals/triage/service.d.ts +79 -0
  236. package/dist/evals/triage/service.js +217 -0
  237. package/dist/evals/triage/stateBuilder.d.ts +19 -0
  238. package/dist/evals/triage/stateBuilder.js +125 -0
  239. package/dist/evals/triage/types.d.ts +60 -0
  240. package/dist/evals/triage/types.js +8 -0
  241. package/dist/evals/types.d.ts +347 -0
  242. package/dist/evals/types.js +1 -0
  243. package/dist/mcp/catalogTools.js +11 -1
  244. package/dist/mcp/deps.d.ts +7 -0
  245. package/dist/mcp/evalTools.d.ts +3 -0
  246. package/dist/mcp/evalTools.js +280 -0
  247. package/dist/mcp/server.js +1 -0
  248. package/dist/mcp/tools.js +2 -0
  249. package/dist/models/response.d.ts +7 -2
  250. package/dist/models/response.js +6 -1
  251. package/dist/operatorAgent/agentAuthorContext.d.ts +1 -1
  252. package/dist/operatorAgent/agentAuthorContext.js +1 -1
  253. package/dist/operatorAgent/piModel.js +17 -4
  254. package/dist/operatorAgent/profiles/agentAuthor.d.ts +3 -1
  255. package/dist/operatorAgent/profiles/agentAuthor.js +145 -20
  256. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
  257. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +48 -15
  258. package/dist/operatorAgent/profiles/authorReadable.d.ts +2 -0
  259. package/dist/operatorAgent/profiles/authorReadable.js +26 -0
  260. package/dist/package-meta.d.ts +1 -1
  261. package/dist/package-meta.js +1 -1
  262. package/dist/runstore/port.d.ts +43 -0
  263. package/dist/runstore/sqlite/SqliteRunStore.d.ts +28 -0
  264. package/dist/runstore/sqlite/SqliteRunStore.js +99 -5
  265. package/dist/runstore/sqlite/evalStore.d.ts +20 -0
  266. package/dist/runstore/sqlite/evalStore.js +541 -0
  267. package/dist/runstore/sqlite/migrations/015-evals.d.ts +12 -0
  268. package/dist/runstore/sqlite/migrations/015-evals.js +157 -0
  269. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.d.ts +12 -0
  270. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.js +20 -0
  271. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.d.ts +11 -0
  272. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.js +36 -0
  273. package/dist/runstore/sqlite/migrations/index.js +6 -0
  274. package/dist/runstore/sqlite/suggestionStore.d.ts +14 -0
  275. package/dist/runstore/sqlite/suggestionStore.js +132 -0
  276. package/dist/runtime/gitCheckoutCapability.d.ts +1 -0
  277. package/dist/runtime/gitCheckoutCapability.js +1 -1
  278. package/dist/runtime/gitWorktreeCheckout.d.ts +22 -0
  279. package/dist/runtime/gitWorktreeCheckout.js +120 -0
  280. package/dist/runtime/pipelineRunner.d.ts +2 -1
  281. package/dist/runtime/pipelineRunner.js +2 -0
  282. package/dist/runtime/runManager.d.ts +2 -1
  283. package/dist/runtime/runManager.js +8 -6
  284. package/dist/runtime/runRetentionSweep.js +2 -2
  285. package/dist/runtime/sandboxContainer.d.ts +62 -0
  286. package/dist/runtime/sandboxContainer.js +151 -0
  287. package/dist/runtime/sandboxDesk.d.ts +28 -0
  288. package/dist/runtime/sandboxDesk.js +242 -0
  289. package/dist/runtime/sandboxFiles.d.ts +20 -0
  290. package/dist/runtime/sandboxFiles.js +150 -0
  291. package/dist/runtime/stageEnvironment.js +2 -0
  292. package/dist/runtime/stageRoots.d.ts +9 -0
  293. package/dist/runtime/stageRoots.js +14 -0
  294. package/dist/runtime/stageRunner.d.ts +5 -0
  295. package/dist/runtime/stageRunner.js +38 -4
  296. package/dist/runtime/stageSandbox.d.ts +16 -0
  297. package/dist/runtime/stageSandbox.js +54 -0
  298. package/dist/runtime/stageWorker.js +138 -105
  299. package/dist/runtime/workspaceBinding.d.ts +2 -1
  300. package/dist/runtime/workspaceBinding.js +20 -4
  301. package/dist/server/agentAuthorRoutes.js +50 -1
  302. package/dist/server/bootstrap.d.ts +8 -0
  303. package/dist/server/bootstrap.js +56 -0
  304. package/dist/server/controlToken.js +3 -0
  305. package/dist/server/evalInternalRoutes.d.ts +14 -0
  306. package/dist/server/evalInternalRoutes.js +87 -0
  307. package/dist/server/evalRouteContext.d.ts +46 -0
  308. package/dist/server/evalRouteContext.js +43 -0
  309. package/dist/server/evalRoutes.d.ts +5 -0
  310. package/dist/server/evalRoutes.js +1427 -0
  311. package/dist/server/evalTriageRoutes.d.ts +2 -0
  312. package/dist/server/evalTriageRoutes.js +95 -0
  313. package/dist/server/http.d.ts +5 -0
  314. package/dist/server/http.js +32 -2
  315. package/dist/server/mcpHost.js +3 -0
  316. package/dist/toolkit/meta.d.ts +1 -1
  317. package/dist/toolkit/meta.js +15 -0
  318. package/dist/toolkit/profiles.js +1 -1
  319. package/dist/tools/readEmail.d.ts +3 -3
  320. package/dist/types/stageflowManifest.d.ts +6 -0
  321. package/dist/types/task.d.ts +11 -1
  322. package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +9 -0
  323. package/dist/ui/assets/{MarkdownText-D7tYaIFO.js → MarkdownText-BTrOqu5J.js} +1 -1
  324. package/dist/ui/assets/chat-Df_wCpak.js +5 -0
  325. package/dist/ui/assets/chat-P7KQznHm.css +2 -0
  326. package/dist/ui/assets/{main-BFo79krC.css → main-CHlmTNJe.css} +1 -1
  327. package/dist/ui/assets/main-CvVfxmMd.js +271 -0
  328. package/dist/ui/chat.html +3 -3
  329. package/dist/ui/index.html +3 -3
  330. package/examples/README.md +2 -0
  331. package/examples/document-policy-review/README.md +35 -0
  332. package/examples/document-policy-review/agents/compliance.agent.yaml +42 -0
  333. package/examples/document-policy-review/documents/acme-vendor-sow.md +15 -0
  334. package/examples/document-policy-review/documents/support-runbook.md +13 -0
  335. package/examples/document-policy-review/policies/acceptable-use.md +13 -0
  336. package/examples/document-policy-review/policies/data-handling.md +17 -0
  337. package/examples/document-policy-review/policies/vendor-security.md +17 -0
  338. package/examples/evals/README.md +123 -0
  339. package/examples/evals/critique-sample.dataset.jsonl +8 -0
  340. package/examples/evals/critique-sample.eval.yaml +34 -0
  341. package/examples/evals/critique-sample.task.yaml +4 -0
  342. package/examples/evals/eval-sample.pipeline.yaml +35 -0
  343. package/examples/evals/support-sample.agent.yaml +20 -0
  344. package/examples/evals/support-sample.dataset.jsonl +6 -0
  345. package/examples/evals/support-sample.eval.yaml +38 -0
  346. package/examples/memory/README.md +2 -0
  347. package/examples/memory/probe-bash.task.yaml +3 -0
  348. package/examples/memory/sandbox-memory-bash.pipeline.yaml +28 -0
  349. package/examples/ship-feature/README.md +15 -0
  350. package/examples/stageflow.yaml +6 -0
  351. package/package.json +1 -1
  352. package/skills/stageflow/SKILL.md +5 -2
  353. package/skills/stageflow/references/control-surface.md +4 -2
  354. package/skills/stageflow/references/evals.md +68 -0
  355. package/skills/stageflow-author/SKILL.md +4 -0
  356. package/skills/stageflow-run/SKILL.md +4 -0
  357. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +0 -9
  358. package/dist/ui/assets/chat-BbLukq9W.js +0 -5
  359. package/dist/ui/assets/chat-zIZU_bP1.css +0 -2
  360. package/dist/ui/assets/main-CijYMeM0.js +0 -259
@@ -0,0 +1,787 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { rm } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { stringify as stringifyYaml } from "yaml";
5
+ import { loadEvalOutcome } from "../config/loadEval.js";
6
+ import { EVAL_RUN_ID_PREFIX, } from "./constants.js";
7
+ import { getEngine } from "./engine/index.js";
8
+ import { buildEvalPlan } from "./plan.js";
9
+ import { scoreResult } from "./scoring.js";
10
+ import { computeStats } from "./stats.js";
11
+ import { toGradeRecords, toResultRecord } from "./statsAdapter.js";
12
+ export const EVAL_HOST_STOPPED_REASON = "Host stopped while the run was in progress";
13
+ export const EVAL_DEFAULT_ENGINE = "promptfoo";
14
+ export const EVAL_ORPHAN_SESSION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
15
+ export class EvalRunError extends Error {
16
+ code;
17
+ findings;
18
+ constructor(code, message, findings) {
19
+ super(message);
20
+ this.code = code;
21
+ this.findings = findings;
22
+ this.name = "EvalRunError";
23
+ }
24
+ }
25
+ const key = (caseId, repeatIndex) => `${caseId}:${repeatIndex}`;
26
+ function isRecord(value) {
27
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28
+ }
29
+ function safeMessage(err) {
30
+ const raw = err instanceof Error ? err.message : typeof err === "string" ? err : "";
31
+ const first = raw.split("\n")[0]?.trim() ?? "";
32
+ return first === "" ? "Eval engine failed" : first.slice(0, 300);
33
+ }
34
+ const RESULT_STATUS_BY_CODE = {
35
+ EVAL_STAGE_FAILED: "stage_failed",
36
+ EVAL_STAGE_TIMEOUT: "stage_failed",
37
+ EVAL_STAGE_NEEDS_INPUT: "needs_input",
38
+ EVAL_BUDGET_EXCEEDED: "skipped_budget",
39
+ EVAL_CANCELLED: "cancelled",
40
+ EVAL_ENGINE_ERROR: "error",
41
+ EVAL_AGENT_FAILED: "stage_failed",
42
+ EVAL_AGENT_EMPTY_REPLY: "error",
43
+ EVAL_AGENT_TIMEOUT: "stage_failed",
44
+ EVAL_SIMULATOR_ERROR: "error",
45
+ };
46
+ function conversationFields(output) {
47
+ const { transcript: _transport, ...stored } = output;
48
+ return {
49
+ output: stored,
50
+ turns: output.turns,
51
+ toolCallCount: output.tool_calls.length,
52
+ };
53
+ }
54
+ const EXECUTED = ["completed", "stage_failed", "needs_input", "error"];
55
+ function scoringGraders(plan) {
56
+ return plan.graders.map((g) => ({
57
+ id: g.id,
58
+ type: g.type,
59
+ weight: g.weight,
60
+ mustPass: g.mustPass,
61
+ ...(g.type === "rubric" ? { scale: g.scale } : {}),
62
+ ...(g.type === "rubric" || g.type === "reference" ? { passAt: g.passAt } : {}),
63
+ }));
64
+ }
65
+ function detailsOf(details) {
66
+ if (details === undefined || details === null)
67
+ return null;
68
+ return isRecord(details) ? details : { value: details };
69
+ }
70
+ function gradeInput(g) {
71
+ return {
72
+ graderId: g.graderId,
73
+ graderType: g.graderType,
74
+ status: g.status,
75
+ passed: g.passed ?? null,
76
+ score: g.score ?? null,
77
+ rawValue: g.rawValue ?? null,
78
+ reason: g.reason ?? null,
79
+ details: detailsOf(g.details),
80
+ errorCode: g.errorCode ?? null,
81
+ judgeModel: g.judgeModel ?? null,
82
+ judgeCostUsd: g.judgeCostUsd ?? null,
83
+ judgeTokensIn: g.judgeTokensIn ?? null,
84
+ judgeTokensOut: g.judgeTokensOut ?? null,
85
+ };
86
+ }
87
+ export class EvalRunManager {
88
+ deps;
89
+ store;
90
+ runner;
91
+ engines;
92
+ engineName;
93
+ now;
94
+ live;
95
+ reserved = false;
96
+ trying = false;
97
+ tryingRunId = null;
98
+ constructor(deps) {
99
+ this.deps = deps;
100
+ this.store = deps.store;
101
+ this.runner = deps.stageRunner;
102
+ this.engines = deps.engines ?? getEngine;
103
+ this.engineName = deps.engineName ?? EVAL_DEFAULT_ENGINE;
104
+ this.now = deps.now ?? (() => new Date());
105
+ }
106
+ activeRunId() {
107
+ return this.live?.evalRunId ?? null;
108
+ }
109
+ isExecuting(evalRunId) {
110
+ return this.live?.evalRunId === evalRunId || this.tryingRunId === evalRunId;
111
+ }
112
+ getLive(evalRunId) {
113
+ const live = this.live;
114
+ if (live === undefined || live.evalRunId !== evalRunId)
115
+ return null;
116
+ return { evalRunId, phases: [...live.phases.values()] };
117
+ }
118
+ claim() {
119
+ if (this.live !== undefined || this.reserved || this.trying) {
120
+ throw new EvalRunError("EVAL_RUN_ACTIVE", this.trying ? "A case is being tried; wait for it to finish" : "An eval run is already active");
121
+ }
122
+ this.reserved = true;
123
+ }
124
+ async start(input) {
125
+ this.claim();
126
+ let handedOff = false;
127
+ try {
128
+ const engine = this.engines(this.engineName);
129
+ const engineInfo = {
130
+ name: engine.name,
131
+ version: await engine.version(),
132
+ nodeVersion: process.versions.node,
133
+ };
134
+ const base = {
135
+ projectRoot: input.projectRoot,
136
+ evalPath: input.evalPath,
137
+ ...(input.overrides !== undefined ? { overrides: input.overrides } : {}),
138
+ evalRunId: "er_pending",
139
+ engine: engineInfo,
140
+ failedCaseIdsOf: (id) => this.failedCaseIds(input.projectRoot, input.evalPath, id),
141
+ ...(this.deps.isKnownModel ? { isKnownModel: this.deps.isKnownModel } : {}),
142
+ ...this.agentsInput(),
143
+ };
144
+ const first = await buildEvalPlan(base);
145
+ if (!first.ok)
146
+ throw new EvalRunError("EVAL_INVALID", "Eval is invalid", first.findings);
147
+ const ordinals = await this.store.recordGraderVersions(input.projectRoot, first.plan.evalId, first.plan.graders.map((g) => ({ graderId: g.id, graderHash: g.hash })));
148
+ const built = await buildEvalPlan({ ...base, graderOrdinals: ordinals });
149
+ if (!built.ok)
150
+ throw new EvalRunError("EVAL_INVALID", "Eval is invalid", built.findings);
151
+ const { plan: draft, snapshot, fingerprint, components } = built;
152
+ try {
153
+ engine.validatePlan?.(draft);
154
+ }
155
+ catch (err) {
156
+ throw new EvalRunError("EVAL_INVALID", err instanceof Error ? err.message : String(err));
157
+ }
158
+ this.requireRealToolsConsent(draft, input.overrides);
159
+ const stageTarget = snapshot.target.kind === "stage" ? snapshot.target : null;
160
+ const agentTarget = snapshot.target.kind === "agent" ? snapshot.target : null;
161
+ const row = await this.store.createEvalRun({
162
+ projectRoot: input.projectRoot,
163
+ evalId: draft.evalId,
164
+ evalPath: snapshot.eval.path,
165
+ evalHash: snapshot.eval.hash,
166
+ targetKind: agentTarget !== null ? "agent" : "stage",
167
+ targetRef: agentTarget !== null
168
+ ? { agent_id: agentTarget.agent_id, revision: agentTarget.revision }
169
+ : {
170
+ ...(stageTarget?.pipeline_path != null ? { pipeline_path: stageTarget.pipeline_path } : {}),
171
+ ...(stageTarget?.stage_id != null ? { stage_id: stageTarget.stage_id } : {}),
172
+ ...(stageTarget?.file != null ? { file: stageTarget.file } : {}),
173
+ },
174
+ targetVersionHash: draft.targetVersionHash,
175
+ datasetPath: snapshot.dataset.path,
176
+ datasetHash: snapshot.dataset.hash,
177
+ config: snapshot,
178
+ configFingerprint: fingerprint,
179
+ configComponents: components,
180
+ status: "queued",
181
+ repeats: draft.repeats,
182
+ passThreshold: draft.passThreshold,
183
+ maxCostUsd: draft.maxCostUsd,
184
+ casesTotal: draft.cases.length,
185
+ resultsTotal: draft.cases.length * draft.repeats,
186
+ engineName: engineInfo.name,
187
+ engineVersion: engineInfo.version,
188
+ stageflowVersion: this.deps.stageflowVersion,
189
+ startedVia: input.startedVia,
190
+ ...(input.label !== undefined ? { label: input.label } : {}),
191
+ ...(input.callerId !== undefined ? { callerId: input.callerId } : {}),
192
+ });
193
+ const plan = { ...draft, evalRunId: row.evalRunId };
194
+ await this.store.insertEvalCases(row.evalRunId, plan.cases.map((c) => {
195
+ const { position, ...rest } = c;
196
+ return { caseId: c.id, position, case: rest };
197
+ }));
198
+ const live = {
199
+ evalRunId: row.evalRunId,
200
+ plan,
201
+ abort: new AbortController(),
202
+ phases: new Map(),
203
+ emitted: new Set(),
204
+ writes: Promise.resolve(),
205
+ cancelRequested: false,
206
+ skippedBudget: false,
207
+ resultsDone: 0,
208
+ finished: undefined,
209
+ };
210
+ this.live = live;
211
+ this.reserved = false;
212
+ handedOff = true;
213
+ live.finished = this.execute(live, engine);
214
+ live.finished.catch(() => undefined);
215
+ return { evalRunId: row.evalRunId, runNumber: row.runNumber };
216
+ }
217
+ finally {
218
+ if (!handedOff)
219
+ this.reserved = false;
220
+ }
221
+ }
222
+ async failedCaseIds(projectRoot, evalPath, evalRunId) {
223
+ const run = await this.store.getEvalRun(evalRunId);
224
+ if (run === null || run.projectRoot !== projectRoot)
225
+ return undefined;
226
+ const evalRel = path.relative(projectRoot, path.resolve(projectRoot, evalPath)).split(path.sep).join("/");
227
+ if (run.evalPath !== evalRel)
228
+ return undefined;
229
+ const results = await this.store.listEvalResults(evalRunId);
230
+ const failed = results.filter((r) => EXECUTED.includes(r.status) && !r.passed);
231
+ return [...new Set(failed.map((r) => r.caseId))];
232
+ }
233
+ makeIo(plan, onResult, onPhase) {
234
+ const { logger } = this.deps;
235
+ return {
236
+ runCase: (req) => this.runner.runCase(plan, req),
237
+ onResult,
238
+ reportJudgeCost: (usd) => {
239
+ if (Number.isFinite(usd) && usd > 0)
240
+ this.runner.addSpend(plan.evalRunId, usd);
241
+ },
242
+ onPhase,
243
+ log: (level, message) => {
244
+ if (level === "info")
245
+ logger?.info?.("eval.engine", message);
246
+ else
247
+ logger?.[level]("eval.engine", message);
248
+ },
249
+ };
250
+ }
251
+ async runHook(name, hook) {
252
+ try {
253
+ await hook();
254
+ }
255
+ catch (err) {
256
+ this.deps.logger?.warn("eval.hook_failed", `${name}: ${safeMessage(err)}`);
257
+ }
258
+ }
259
+ async execute(live, engine) {
260
+ const { plan, evalRunId } = live;
261
+ try {
262
+ await this.store.updateEvalRunProgress(evalRunId, {
263
+ status: "running",
264
+ startedAt: this.now().toISOString(),
265
+ });
266
+ const io = this.makeIo(plan, (result) => this.enqueuePersist(live, result), (caseId, repeatIndex, phase) => {
267
+ const k = key(caseId, repeatIndex);
268
+ if (phase === "done")
269
+ live.phases.delete(k);
270
+ else
271
+ live.phases.set(k, { caseId, repeatIndex, phase });
272
+ });
273
+ const { onRunStarted } = this.deps;
274
+ if (onRunStarted) {
275
+ await this.runHook("onRunStarted", () => onRunStarted(evalRunId, {
276
+ plan,
277
+ engineName: engine.name,
278
+ runCase: io.runCase,
279
+ signal: live.abort.signal,
280
+ }));
281
+ }
282
+ let crashed = false;
283
+ let crash;
284
+ try {
285
+ await engine.run(plan, io, live.abort.signal);
286
+ }
287
+ catch (err) {
288
+ crashed = true;
289
+ crash = err;
290
+ this.deps.logger?.error("eval.engine_failed", safeMessage(err));
291
+ }
292
+ await live.writes;
293
+ return await this.finish(live, crashed, crash);
294
+ }
295
+ catch (err) {
296
+ this.deps.logger?.error("eval.finish_failed", safeMessage(err));
297
+ await live.writes.catch(() => undefined);
298
+ return await this.finishFailed(live, safeMessage(err));
299
+ }
300
+ finally {
301
+ live.phases.clear();
302
+ await this.cleanupWorkDir(evalRunId);
303
+ const { onRunFinished } = this.deps;
304
+ if (onRunFinished)
305
+ await this.runHook("onRunFinished", () => onRunFinished(evalRunId));
306
+ if (this.live === live)
307
+ this.live = undefined;
308
+ }
309
+ }
310
+ enqueuePersist(live, result) {
311
+ const k = key(result.caseId, result.repeatIndex);
312
+ if (live.emitted.has(k))
313
+ return;
314
+ live.emitted.add(k);
315
+ live.phases.delete(k);
316
+ const input = this.resultInput(live.plan, result);
317
+ if (input.status === "skipped_budget")
318
+ live.skippedBudget = true;
319
+ const stopReason = this.runner.stopReason?.(live.evalRunId);
320
+ if (stopReason !== undefined && live.providerStop === undefined && !live.cancelRequested) {
321
+ live.providerStop = stopReason;
322
+ live.abort.abort();
323
+ void this.runner.cancelAll(live.evalRunId).catch(() => undefined);
324
+ }
325
+ live.writes = live.writes.then(async () => {
326
+ if (live.persistError !== undefined)
327
+ return;
328
+ try {
329
+ await this.store.upsertEvalResult(input);
330
+ live.resultsDone += 1;
331
+ await this.store.updateEvalRunProgress(live.evalRunId, {
332
+ resultsDone: live.resultsDone,
333
+ costUsd: this.runner.spend(live.evalRunId),
334
+ });
335
+ }
336
+ catch (err) {
337
+ live.persistError = err;
338
+ live.abort.abort();
339
+ this.deps.logger?.error("eval.persist_failed", safeMessage(err));
340
+ }
341
+ });
342
+ }
343
+ resultInput(plan, nr) {
344
+ const finishedAt = this.now();
345
+ const { run } = nr;
346
+ const base = { evalRunId: plan.evalRunId, caseId: nr.caseId, repeatIndex: nr.repeatIndex };
347
+ const timing = (latencyMs) => ({
348
+ startedAt: latencyMs !== undefined ? new Date(finishedAt.getTime() - latencyMs).toISOString() : null,
349
+ finishedAt: finishedAt.toISOString(),
350
+ });
351
+ if (!run.ok) {
352
+ const status = RESULT_STATUS_BY_CODE[run.code] ?? "error";
353
+ const scored = scoreResult({
354
+ graders: [],
355
+ grades: [],
356
+ passThreshold: plan.passThreshold,
357
+ status,
358
+ latencyMs: run.latencyMs ?? null,
359
+ costUsd: run.costUsd ?? null,
360
+ budget: plan.budget,
361
+ });
362
+ return {
363
+ ...base,
364
+ status,
365
+ errorCode: run.code,
366
+ errorMessage: run.message,
367
+ stageRunId: run.stageRunId ?? null,
368
+ ...(run.subjectRef !== undefined || run.stageRunId !== undefined
369
+ ? { subjectRef: run.subjectRef ?? run.stageRunId ?? null }
370
+ : {}),
371
+ ...(run.output !== undefined ? conversationFields(run.output) : {}),
372
+ latencyMs: run.latencyMs ?? null,
373
+ costUsd: run.costUsd ?? null,
374
+ score: null,
375
+ passed: false,
376
+ hasGraderErrors: false,
377
+ budgetWarnings: scored.budgetWarnings,
378
+ ...timing(run.latencyMs),
379
+ };
380
+ }
381
+ const scored = scoreResult({
382
+ graders: scoringGraders(plan),
383
+ grades: nr.grades.map((g) => ({
384
+ graderId: g.graderId,
385
+ status: g.status,
386
+ passed: g.passed ?? null,
387
+ score: g.score ?? null,
388
+ rawValue: g.rawValue ?? null,
389
+ })),
390
+ passThreshold: plan.passThreshold,
391
+ status: "completed",
392
+ latencyMs: run.latencyMs,
393
+ costUsd: run.costUsd,
394
+ budget: plan.budget,
395
+ });
396
+ const { output } = run;
397
+ return {
398
+ ...base,
399
+ status: "completed",
400
+ stageRunId: run.stageRunId ?? null,
401
+ subjectRef: run.subjectRef ?? run.stageRunId ?? null,
402
+ ...(output.kind === "conversation"
403
+ ? conversationFields(output)
404
+ : {
405
+ output: {
406
+ status: output.status,
407
+ summary: output.summary,
408
+ ...(output.payload !== undefined ? { payload: output.payload } : {}),
409
+ artifacts: output.artifacts,
410
+ },
411
+ }),
412
+ latencyMs: run.latencyMs,
413
+ costUsd: run.costUsd,
414
+ tokensIn: run.tokensIn,
415
+ tokensOut: run.tokensOut,
416
+ score: scored.score,
417
+ passed: scored.passed,
418
+ hasGraderErrors: scored.hasGraderErrors,
419
+ budgetWarnings: scored.budgetWarnings,
420
+ ...timing(run.latencyMs),
421
+ grades: nr.grades.map(gradeInput),
422
+ };
423
+ }
424
+ statsFor(plan, results) {
425
+ return computeStats({
426
+ ...(plan.target?.kind === "agent" ? { targetKind: "agent" } : {}),
427
+ results: results.map(toResultRecord),
428
+ grades: results.flatMap(toGradeRecords),
429
+ graders: plan.graders.map((g) => ({ id: g.id, type: g.type, hash: g.hash })),
430
+ repeats: plan.repeats,
431
+ passThreshold: plan.passThreshold,
432
+ });
433
+ }
434
+ summaryOf(stats) {
435
+ return {
436
+ pass_rate: stats.pass_rate,
437
+ pass_rate_ci: stats.pass_rate_ci,
438
+ results_executed: stats.results_executed,
439
+ results_passed: stats.results_passed,
440
+ errored: stats.errored,
441
+ reliability: stats.reliability,
442
+ mean_score: stats.mean_score,
443
+ cost_total: stats.cost_total,
444
+ latency_p50: stats.latency_p50,
445
+ flaky_count: stats.flaky_cases.length,
446
+ ...(stats.mean_turns !== undefined ? { mean_turns: stats.mean_turns } : {}),
447
+ ...(stats.mean_tool_calls !== undefined ? { mean_tool_calls: stats.mean_tool_calls } : {}),
448
+ };
449
+ }
450
+ async fillRemainder(live, kind, message) {
451
+ const { plan } = live;
452
+ const finishedAt = this.now().toISOString();
453
+ const detail = kind === "cancelled"
454
+ ? { errorCode: "EVAL_CANCELLED", errorMessage: message ?? "Eval run was cancelled" }
455
+ : kind === "skipped_budget"
456
+ ? { errorCode: "EVAL_BUDGET_EXCEEDED", errorMessage: `Eval spend reached the $${plan.maxCostUsd} limit` }
457
+ : { errorCode: "EVAL_ENGINE_ERROR", errorMessage: "Engine finished without a result for this case" };
458
+ for (const c of plan.cases) {
459
+ for (let repeatIndex = 0; repeatIndex < plan.repeats; repeatIndex++) {
460
+ const k = key(c.id, repeatIndex);
461
+ if (live.emitted.has(k))
462
+ continue;
463
+ live.emitted.add(k);
464
+ await this.store.upsertEvalResult({
465
+ evalRunId: plan.evalRunId,
466
+ caseId: c.id,
467
+ repeatIndex,
468
+ status: kind,
469
+ ...detail,
470
+ score: null,
471
+ passed: false,
472
+ finishedAt,
473
+ });
474
+ live.resultsDone += 1;
475
+ }
476
+ }
477
+ }
478
+ async finish(live, crashed, crash) {
479
+ const { plan, evalRunId } = live;
480
+ const total = plan.cases.length * plan.repeats;
481
+ let status = "completed";
482
+ let reason = null;
483
+ if (live.persistError !== undefined) {
484
+ status = "failed";
485
+ reason = "Could not save eval results";
486
+ }
487
+ else if (crashed) {
488
+ status = "failed";
489
+ reason = safeMessage(crash);
490
+ }
491
+ else if (live.providerStop !== undefined && !live.cancelRequested) {
492
+ status = "failed";
493
+ reason = live.providerStop;
494
+ await this.fillRemainder(live, "cancelled", live.providerStop);
495
+ }
496
+ else if (live.cancelRequested) {
497
+ status = "cancelled";
498
+ reason = "Cancelled";
499
+ await this.fillRemainder(live, "cancelled");
500
+ }
501
+ else if (live.skippedBudget || (live.emitted.size < total && this.runner.spend(evalRunId) >= plan.maxCostUsd)) {
502
+ status = "budget_stopped";
503
+ reason = `Reached the $${plan.maxCostUsd} cost limit`;
504
+ await this.fillRemainder(live, "skipped_budget");
505
+ }
506
+ else if (live.emitted.size < total) {
507
+ await this.fillRemainder(live, "error");
508
+ }
509
+ const results = await this.store.listEvalResults(evalRunId);
510
+ const stats = this.statsFor(plan, results);
511
+ const passed = status === "completed" && stats.pass_rate !== null && stats.pass_rate + 1e-9 >= plan.passThreshold;
512
+ await this.store.updateEvalRunProgress(evalRunId, { resultsDone: live.resultsDone, costUsd: stats.cost_total });
513
+ await this.store.finishEvalRun(evalRunId, {
514
+ status,
515
+ statusReason: reason,
516
+ passed,
517
+ summary: this.summaryOf(stats),
518
+ finishedAt: this.now().toISOString(),
519
+ });
520
+ return (await this.store.getEvalRun(evalRunId));
521
+ }
522
+ async finishFailed(live, reason) {
523
+ const results = await this.store.listEvalResults(live.evalRunId);
524
+ await this.store.finishEvalRun(live.evalRunId, {
525
+ status: "failed",
526
+ statusReason: reason,
527
+ passed: false,
528
+ summary: this.summaryOf(this.statsFor(live.plan, results)),
529
+ finishedAt: this.now().toISOString(),
530
+ });
531
+ return (await this.store.getEvalRun(live.evalRunId));
532
+ }
533
+ async cleanupWorkDir(evalRunId) {
534
+ if (process.env.STAGEFLOW_EVALS_KEEP_WORKDIR === "1")
535
+ return;
536
+ try {
537
+ await rm(path.join(this.deps.workDirRoot, evalRunId), { recursive: true, force: true });
538
+ }
539
+ catch (err) {
540
+ this.deps.logger?.warn("eval.workdir_cleanup_failed", safeMessage(err));
541
+ }
542
+ }
543
+ async cancel(evalRunId) {
544
+ const live = this.live;
545
+ if (live === undefined || live.evalRunId !== evalRunId) {
546
+ if ((await this.store.getEvalRun(evalRunId)) === null) {
547
+ throw new EvalRunError("EVAL_RUN_NOT_FOUND", `Eval run not found: ${evalRunId}`);
548
+ }
549
+ return false;
550
+ }
551
+ if (live.cancelRequested)
552
+ return true;
553
+ live.cancelRequested = true;
554
+ live.abort.abort();
555
+ await this.runner.cancelAll(evalRunId);
556
+ return true;
557
+ }
558
+ async awaitFinished(evalRunId, timeoutMs) {
559
+ const live = this.live;
560
+ if (live !== undefined && live.evalRunId === evalRunId) {
561
+ let timer;
562
+ const timeout = new Promise((resolve) => {
563
+ timer = setTimeout(() => resolve("timeout"), Math.max(0, timeoutMs));
564
+ });
565
+ try {
566
+ const winner = await Promise.race([live.finished, timeout]);
567
+ if (winner !== "timeout")
568
+ return { finished: true, run: winner };
569
+ }
570
+ finally {
571
+ if (timer !== undefined)
572
+ clearTimeout(timer);
573
+ }
574
+ }
575
+ const run = await this.store.getEvalRun(evalRunId);
576
+ if (run === null)
577
+ throw new EvalRunError("EVAL_RUN_NOT_FOUND", `Eval run not found: ${evalRunId}`);
578
+ return { finished: run.status !== "queued" && run.status !== "running", run };
579
+ }
580
+ requireRealToolsConsent(plan, overrides) {
581
+ if (plan.target.kind === "agent" && plan.conversation?.tools === "real" && overrides?.allow_real_tools !== true) {
582
+ throw new EvalRunError("EVAL_REAL_TOOLS_NOT_CONFIRMED", "This eval uses real tools, which can change files and call services from this machine. Confirm with allow_real_tools to run it.");
583
+ }
584
+ }
585
+ /**
586
+ * Validation and the real-tools consent for an agent eval, without claiming the run slot, so a transport can answer
587
+ * them before it checks the engine. Does nothing for stage evals and unreadable files.
588
+ */
589
+ async preflightAgentStart(input) {
590
+ const outcome = await loadEvalOutcome(path.resolve(input.projectRoot, input.evalPath), input.projectRoot);
591
+ if (!outcome.ok || outcome.value.file.target.kind !== "agent")
592
+ return;
593
+ const built = await buildEvalPlan({
594
+ projectRoot: input.projectRoot,
595
+ evalPath: input.evalPath,
596
+ ...(input.overrides !== undefined ? { overrides: input.overrides } : {}),
597
+ evalRunId: "er_preflight",
598
+ engine: { name: this.engineName, version: "preflight", nodeVersion: process.versions.node },
599
+ failedCaseIdsOf: (id) => this.failedCaseIds(input.projectRoot, input.evalPath, id),
600
+ ...(this.deps.isKnownModel ? { isKnownModel: this.deps.isKnownModel } : {}),
601
+ ...this.agentsInput(),
602
+ });
603
+ if (!built.ok)
604
+ throw new EvalRunError("EVAL_INVALID", "Eval is invalid", built.findings);
605
+ this.requireRealToolsConsent(built.plan, input.overrides);
606
+ }
607
+ agentsInput() {
608
+ const agents = this.deps.agents?.();
609
+ return agents ? { agents } : {};
610
+ }
611
+ /** Delete the agent sessions of recovered runs and eval sessions older than 7 days that no result references. */
612
+ async sweepAgentSessions(recoveredRunIds) {
613
+ const agents = this.deps.agents?.();
614
+ if (!agents)
615
+ return 0;
616
+ try {
617
+ return await agents.sweepEvalSessions({
618
+ runIds: recoveredRunIds,
619
+ olderThanMs: EVAL_ORPHAN_SESSION_MAX_AGE_MS,
620
+ isReferenced: async (sessionId) => (await this.store.listEvalRuns({ subjectRef: sessionId, limit: 1 })).items.length > 0,
621
+ });
622
+ }
623
+ catch (err) {
624
+ this.deps.logger?.warn("eval.session_sweep_failed", safeMessage(err));
625
+ return 0;
626
+ }
627
+ }
628
+ async recoverOnBoot() {
629
+ const recovered = [];
630
+ for (const status of ["queued", "running"]) {
631
+ let cursor;
632
+ do {
633
+ const page = await this.store.listEvalRuns({
634
+ status,
635
+ limit: 200,
636
+ ...(cursor !== undefined ? { cursor } : {}),
637
+ });
638
+ for (const run of page.items) {
639
+ let summary = {};
640
+ try {
641
+ const results = await this.store.listEvalResults(run.evalRunId);
642
+ summary = this.summaryOf(this.statsFor({ graders: [], repeats: run.repeats, passThreshold: run.passThreshold, target: { kind: run.targetKind } }, results));
643
+ }
644
+ catch (err) {
645
+ this.deps.logger?.warn("eval.recover_summary_failed", safeMessage(err));
646
+ }
647
+ await this.store.finishEvalRun(run.evalRunId, {
648
+ status: "failed",
649
+ statusReason: EVAL_HOST_STOPPED_REASON,
650
+ passed: false,
651
+ summary,
652
+ finishedAt: this.now().toISOString(),
653
+ });
654
+ recovered.push(run.evalRunId);
655
+ }
656
+ cursor = page.nextCursor ?? undefined;
657
+ } while (cursor !== undefined);
658
+ }
659
+ return recovered;
660
+ }
661
+ async dropTrySession(tried) {
662
+ if (!tried)
663
+ return;
664
+ try {
665
+ await this.deps.agents?.()?.deleteEvalSessions(tried.projectRoot, [tried.subjectRef]);
666
+ }
667
+ catch (err) {
668
+ this.deps.logger?.warn("eval.try_session_cleanup_failed", safeMessage(err));
669
+ }
670
+ }
671
+ async tryOne(input) {
672
+ this.claim();
673
+ this.reserved = false;
674
+ this.trying = true;
675
+ const evalRunId = `${EVAL_RUN_ID_PREFIX}${randomBytes(6).toString("hex")}`;
676
+ this.tryingRunId = evalRunId;
677
+ const abort = new AbortController();
678
+ let hooked = false;
679
+ let tried;
680
+ try {
681
+ const engine = this.engines(this.engineName);
682
+ const engineInfo = { name: engine.name, version: await engine.version(), nodeVersion: process.versions.node };
683
+ const evalText = input.definition === undefined
684
+ ? undefined
685
+ : typeof input.definition === "string"
686
+ ? input.definition
687
+ : stringifyYaml(input.definition);
688
+ const built = await buildEvalPlan({
689
+ projectRoot: input.projectRoot,
690
+ evalPath: input.evalPath,
691
+ ...(evalText !== undefined ? { evalText } : {}),
692
+ ...(input.datasetCases !== undefined ? { datasetCases: input.datasetCases } : {}),
693
+ overrides: {
694
+ repeats: 1,
695
+ ...(input.caseId !== undefined ? { case_ids: [input.caseId] } : {}),
696
+ ...(input.allowRealTools === true ? { allow_real_tools: true } : {}),
697
+ },
698
+ evalRunId,
699
+ engine: engineInfo,
700
+ ...(this.deps.isKnownModel ? { isKnownModel: this.deps.isKnownModel } : {}),
701
+ ...this.agentsInput(),
702
+ });
703
+ if (!built.ok)
704
+ throw new EvalRunError("EVAL_INVALID", "Eval is invalid", built.findings);
705
+ this.requireRealToolsConsent(built.plan, { allow_real_tools: input.allowRealTools === true });
706
+ let plan = built.plan;
707
+ if (input.case !== undefined) {
708
+ const c = input.case;
709
+ const planCase = {
710
+ id: c.id ?? "try",
711
+ position: 0,
712
+ input: c.input,
713
+ tags: c.tags ?? [],
714
+ ...(c.expected !== undefined ? { expected: c.expected } : {}),
715
+ ...(c.title !== undefined ? { title: c.title } : {}),
716
+ ...(c.goal !== undefined ? { goal: c.goal } : {}),
717
+ ...(c.metadata !== undefined ? { metadata: c.metadata } : {}),
718
+ };
719
+ plan = { ...plan, cases: [planCase] };
720
+ }
721
+ else if (input.caseId === undefined || plan.cases.length !== 1) {
722
+ throw new EvalRunError("EVAL_INVALID", "Try one case needs a case id or an inline case");
723
+ }
724
+ plan = { ...plan, repeats: 1 };
725
+ let captured;
726
+ const io = this.makeIo(plan, (r) => {
727
+ captured ??= r;
728
+ }, () => undefined);
729
+ const { onRunStarted } = this.deps;
730
+ if (onRunStarted) {
731
+ hooked = true;
732
+ await this.runHook("onRunStarted", () => onRunStarted(evalRunId, { plan, engineName: engine.name, runCase: io.runCase, signal: abort.signal }));
733
+ }
734
+ await engine.run(plan, io, abort.signal);
735
+ if (captured === undefined) {
736
+ throw new EvalRunError("EVAL_ENGINE_ERROR", "Engine finished without a result for this case");
737
+ }
738
+ const row = this.resultInput(plan, captured);
739
+ if (plan.target.kind === "agent" && row.subjectRef)
740
+ tried = { projectRoot: input.projectRoot, subjectRef: row.subjectRef };
741
+ const grades = (row.grades ?? []).map((g) => ({
742
+ graderId: g.graderId,
743
+ graderType: g.graderType,
744
+ status: g.status,
745
+ passed: g.passed ?? null,
746
+ score: g.score ?? null,
747
+ rawValue: g.rawValue ?? null,
748
+ reason: g.reason ?? null,
749
+ errorCode: g.errorCode ?? null,
750
+ details: g.details ?? null,
751
+ judgeCostUsd: g.judgeCostUsd ?? null,
752
+ }));
753
+ return {
754
+ result: {
755
+ caseId: row.caseId,
756
+ status: row.status,
757
+ errorCode: row.errorCode ?? null,
758
+ errorMessage: row.errorMessage ?? null,
759
+ stageRunId: row.stageRunId ?? null,
760
+ subjectRef: row.subjectRef ?? null,
761
+ output: row.output ?? null,
762
+ latencyMs: row.latencyMs ?? null,
763
+ costUsd: row.costUsd ?? null,
764
+ tokensIn: row.tokensIn ?? null,
765
+ tokensOut: row.tokensOut ?? null,
766
+ score: row.score ?? null,
767
+ passed: row.passed ?? false,
768
+ hasGraderErrors: row.hasGraderErrors ?? false,
769
+ budgetWarnings: row.budgetWarnings ?? [],
770
+ grades,
771
+ },
772
+ grades,
773
+ stageRunId: row.stageRunId ?? null,
774
+ costUsd: this.runner.spend(evalRunId),
775
+ };
776
+ }
777
+ finally {
778
+ await this.dropTrySession(tried);
779
+ this.trying = false;
780
+ this.tryingRunId = null;
781
+ const { onRunFinished } = this.deps;
782
+ if (hooked && onRunFinished)
783
+ await this.runHook("onRunFinished", () => onRunFinished(evalRunId));
784
+ await this.cleanupWorkDir(evalRunId);
785
+ }
786
+ }
787
+ }