stageflow 0.40.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 (326) hide show
  1. package/README.md +3 -1
  2. package/dist/agents/authorChat.d.ts +4 -0
  3. package/dist/agents/authorChat.js +113 -12
  4. package/dist/agents/authorSessionStore.d.ts +35 -0
  5. package/dist/agents/authorSessionStore.js +152 -0
  6. package/dist/agents/chatTurn.d.ts +2 -0
  7. package/dist/agents/chatTurn.js +17 -1
  8. package/dist/agents/errors.d.ts +1 -1
  9. package/dist/agents/errors.js +1 -0
  10. package/dist/agents/evalGate.d.ts +61 -0
  11. package/dist/agents/evalGate.js +103 -0
  12. package/dist/agents/evalSession.d.ts +26 -0
  13. package/dist/agents/evalSession.js +29 -0
  14. package/dist/agents/hostContext.d.ts +3 -0
  15. package/dist/agents/hostContext.js +4 -1
  16. package/dist/agents/runtime/piRuntime.d.ts +1 -1
  17. package/dist/agents/runtime/piRuntime.js +4 -4
  18. package/dist/agents/runtime/types.d.ts +3 -1
  19. package/dist/agents/service.d.ts +27 -2
  20. package/dist/agents/service.js +88 -13
  21. package/dist/agents/sessions.d.ts +8 -0
  22. package/dist/agents/sessions.js +50 -10
  23. package/dist/agents/store.d.ts +14 -3
  24. package/dist/agents/store.js +45 -7
  25. package/dist/agents/toolRisk.d.ts +21 -0
  26. package/dist/agents/toolRisk.js +50 -0
  27. package/dist/cli/doctorCommand.js +26 -0
  28. package/dist/cli/evalCommand.d.ts +20 -0
  29. package/dist/cli/evalCommand.js +720 -0
  30. package/dist/cli/exportAllCommand.js +1 -1
  31. package/dist/cli/hostClient.d.ts +60 -0
  32. package/dist/cli/hostClient.js +71 -0
  33. package/dist/cli/validateOutput.d.ts +2 -2
  34. package/dist/cli/validateOutput.js +2 -2
  35. package/dist/cli.js +10 -0
  36. package/dist/config/atomicWrite.d.ts +15 -0
  37. package/dist/config/atomicWrite.js +43 -0
  38. package/dist/config/browseCatalog.d.ts +2 -0
  39. package/dist/config/browseCatalog.js +4 -2
  40. package/dist/config/csv.d.ts +9 -0
  41. package/dist/config/csv.js +81 -0
  42. package/dist/config/datasetAgentCheck.d.ts +18 -0
  43. package/dist/config/datasetAgentCheck.js +109 -0
  44. package/dist/config/datasetCandidates.d.ts +22 -0
  45. package/dist/config/datasetCandidates.js +86 -0
  46. package/dist/config/datasetFromSessions.d.ts +34 -0
  47. package/dist/config/datasetFromSessions.js +105 -0
  48. package/dist/config/datasetHash.d.ts +1 -0
  49. package/dist/config/datasetHash.js +4 -0
  50. package/dist/config/datasetImport.d.ts +21 -0
  51. package/dist/config/datasetImport.js +120 -0
  52. package/dist/config/datasetStageCheck.d.ts +9 -0
  53. package/dist/config/datasetStageCheck.js +17 -0
  54. package/dist/config/datasetTypes.d.ts +27 -0
  55. package/dist/config/datasetTypes.js +1 -0
  56. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  57. package/dist/config/ensureCatalogScanRoot.js +2 -6
  58. package/dist/config/evalCatalog.d.ts +22 -0
  59. package/dist/config/evalCatalog.js +67 -0
  60. package/dist/config/evalWriteRoots.d.ts +17 -0
  61. package/dist/config/evalWriteRoots.js +109 -0
  62. package/dist/config/hostConfig.js +1 -0
  63. package/dist/config/installEvalSample.d.ts +34 -0
  64. package/dist/config/installEvalSample.js +182 -0
  65. package/dist/config/loadDataset.d.ts +16 -0
  66. package/dist/config/loadDataset.js +318 -0
  67. package/dist/config/loadEval.d.ts +32 -0
  68. package/dist/config/loadEval.js +1085 -0
  69. package/dist/config/loadOutcome.d.ts +5 -0
  70. package/dist/config/loadStageflowManifest.js +64 -0
  71. package/dist/config/resolveAgentTarget.d.ts +32 -0
  72. package/dist/config/resolveAgentTarget.js +118 -0
  73. package/dist/config/resolveEvalTarget.d.ts +21 -0
  74. package/dist/config/resolveEvalTarget.js +89 -0
  75. package/dist/config/scanCatalogPaths.d.ts +1 -1
  76. package/dist/config/scanCatalogPaths.js +17 -5
  77. package/dist/config/validateCatalog.d.ts +1 -1
  78. package/dist/config/validateCatalog.js +9 -1
  79. package/dist/config/validateEval.d.ts +36 -0
  80. package/dist/config/validateEval.js +405 -0
  81. package/dist/config/writeDataset.d.ts +54 -0
  82. package/dist/config/writeDataset.js +186 -0
  83. package/dist/config/writeEval.d.ts +25 -0
  84. package/dist/config/writeEval.js +398 -0
  85. package/dist/diagnostics/checks.js +1 -1
  86. package/dist/evals/agentDiff.d.ts +13 -0
  87. package/dist/evals/agentDiff.js +11 -0
  88. package/dist/evals/canonical.d.ts +3 -0
  89. package/dist/evals/canonical.js +24 -0
  90. package/dist/evals/caseTask.d.ts +3 -0
  91. package/dist/evals/caseTask.js +9 -0
  92. package/dist/evals/compare.d.ts +94 -0
  93. package/dist/evals/compare.js +201 -0
  94. package/dist/evals/constants.d.ts +60 -0
  95. package/dist/evals/constants.js +95 -0
  96. package/dist/evals/engine/FakeEngine.d.ts +33 -0
  97. package/dist/evals/engine/FakeEngine.js +176 -0
  98. package/dist/evals/engine/index.d.ts +9 -0
  99. package/dist/evals/engine/index.js +19 -0
  100. package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +52 -0
  101. package/dist/evals/engine/promptfoo/PromptfooEngine.js +311 -0
  102. package/dist/evals/engine/promptfoo/errors.d.ts +5 -0
  103. package/dist/evals/engine/promptfoo/errors.js +8 -0
  104. package/dist/evals/engine/promptfoo/generate.d.ts +30 -0
  105. package/dist/evals/engine/promptfoo/generate.js +342 -0
  106. package/dist/evals/engine/promptfoo/guard.d.ts +10 -0
  107. package/dist/evals/engine/promptfoo/guard.js +116 -0
  108. package/dist/evals/engine/promptfoo/install.d.ts +17 -0
  109. package/dist/evals/engine/promptfoo/install.js +73 -0
  110. package/dist/evals/engine/promptfoo/judgeProviders.d.ts +2 -0
  111. package/dist/evals/engine/promptfoo/judgeProviders.js +9 -0
  112. package/dist/evals/engine/promptfoo/judgeServices.d.ts +6 -0
  113. package/dist/evals/engine/promptfoo/judgeServices.js +41 -0
  114. package/dist/evals/engine/promptfoo/locate.d.ts +21 -0
  115. package/dist/evals/engine/promptfoo/locate.js +68 -0
  116. package/dist/evals/engine/promptfoo/nodeVersion.d.ts +7 -0
  117. package/dist/evals/engine/promptfoo/nodeVersion.js +14 -0
  118. package/dist/evals/engine/promptfoo/normalize.d.ts +66 -0
  119. package/dist/evals/engine/promptfoo/normalize.js +261 -0
  120. package/dist/evals/engine/promptfoo/pinned.d.ts +5 -0
  121. package/dist/evals/engine/promptfoo/pinned.js +5 -0
  122. package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +14 -0
  123. package/dist/evals/engine/promptfoo/rubricPrompt.js +103 -0
  124. package/dist/evals/engine/promptfoo/runTokens.d.ts +18 -0
  125. package/dist/evals/engine/promptfoo/runTokens.js +31 -0
  126. package/dist/evals/engine/promptfoo/status.d.ts +13 -0
  127. package/dist/evals/engine/promptfoo/status.js +16 -0
  128. package/dist/evals/estimate.d.ts +56 -0
  129. package/dist/evals/estimate.js +66 -0
  130. package/dist/evals/graders/codeGrader.d.ts +6 -0
  131. package/dist/evals/graders/codeGrader.js +85 -0
  132. package/dist/evals/graders/conversationCheckGrader.d.ts +6 -0
  133. package/dist/evals/graders/conversationCheckGrader.js +62 -0
  134. package/dist/evals/graders/fieldGrader.d.ts +11 -0
  135. package/dist/evals/graders/fieldGrader.js +117 -0
  136. package/dist/evals/graders/goal.d.ts +3 -0
  137. package/dist/evals/graders/goal.js +14 -0
  138. package/dist/evals/graders/index.d.ts +18 -0
  139. package/dist/evals/graders/index.js +34 -0
  140. package/dist/evals/graders/notApplicable.d.ts +5 -0
  141. package/dist/evals/graders/notApplicable.js +9 -0
  142. package/dist/evals/graders/path.d.ts +26 -0
  143. package/dist/evals/graders/path.js +75 -0
  144. package/dist/evals/graders/referenceGrader.d.ts +5 -0
  145. package/dist/evals/graders/referenceGrader.js +59 -0
  146. package/dist/evals/graders/schemaGrader.d.ts +5 -0
  147. package/dist/evals/graders/schemaGrader.js +29 -0
  148. package/dist/evals/graders/subject.d.ts +6 -0
  149. package/dist/evals/graders/subject.js +33 -0
  150. package/dist/evals/graders/toolCallsGrader.d.ts +6 -0
  151. package/dist/evals/graders/toolCallsGrader.js +100 -0
  152. package/dist/evals/graders/transcript.d.ts +12 -0
  153. package/dist/evals/graders/transcript.js +57 -0
  154. package/dist/evals/graders/types.d.ts +20 -0
  155. package/dist/evals/graders/types.js +1 -0
  156. package/dist/evals/judgeProviders.d.ts +16 -0
  157. package/dist/evals/judgeProviders.js +27 -0
  158. package/dist/evals/lineDiff.d.ts +5 -0
  159. package/dist/evals/lineDiff.js +34 -0
  160. package/dist/evals/listStatus.d.ts +31 -0
  161. package/dist/evals/listStatus.js +48 -0
  162. package/dist/evals/manager.d.ts +174 -0
  163. package/dist/evals/manager.js +787 -0
  164. package/dist/evals/plan.d.ts +58 -0
  165. package/dist/evals/plan.js +602 -0
  166. package/dist/evals/providerKeys.d.ts +1 -0
  167. package/dist/evals/providerKeys.js +21 -0
  168. package/dist/evals/read/agentTargets.d.ts +37 -0
  169. package/dist/evals/read/agentTargets.js +101 -0
  170. package/dist/evals/read/api.d.ts +196 -0
  171. package/dist/evals/read/api.js +358 -0
  172. package/dist/evals/read/compare.d.ts +4 -0
  173. package/dist/evals/read/compare.js +39 -0
  174. package/dist/evals/read/datasets.d.ts +105 -0
  175. package/dist/evals/read/datasets.js +318 -0
  176. package/dist/evals/read/list.d.ts +66 -0
  177. package/dist/evals/read/list.js +173 -0
  178. package/dist/evals/read/results.d.ts +46 -0
  179. package/dist/evals/read/results.js +107 -0
  180. package/dist/evals/read/runs.d.ts +149 -0
  181. package/dist/evals/read/runs.js +289 -0
  182. package/dist/evals/read/suggestions.d.ts +43 -0
  183. package/dist/evals/read/suggestions.js +73 -0
  184. package/dist/evals/runner/FakeAgentBackend.d.ts +40 -0
  185. package/dist/evals/runner/FakeAgentBackend.js +82 -0
  186. package/dist/evals/runner/agentCaseRunner.d.ts +24 -0
  187. package/dist/evals/runner/agentCaseRunner.js +388 -0
  188. package/dist/evals/runner/caseRunner.d.ts +7 -0
  189. package/dist/evals/runner/caseRunner.js +17 -0
  190. package/dist/evals/runner/conversationSubject.d.ts +18 -0
  191. package/dist/evals/runner/conversationSubject.js +123 -0
  192. package/dist/evals/runner/simulatorModel.d.ts +51 -0
  193. package/dist/evals/runner/simulatorModel.js +58 -0
  194. package/dist/evals/runner/simulatorPrompt.d.ts +29 -0
  195. package/dist/evals/runner/simulatorPrompt.js +59 -0
  196. package/dist/evals/runner/stageCaseRunner.d.ts +30 -0
  197. package/dist/evals/runner/stageCaseRunner.js +279 -0
  198. package/dist/evals/runner/userDriver.d.ts +71 -0
  199. package/dist/evals/runner/userDriver.js +104 -0
  200. package/dist/evals/scoring.d.ts +34 -0
  201. package/dist/evals/scoring.js +67 -0
  202. package/dist/evals/snapshot.d.ts +183 -0
  203. package/dist/evals/snapshot.js +65 -0
  204. package/dist/evals/stageDiff.d.ts +8 -0
  205. package/dist/evals/stageDiff.js +48 -0
  206. package/dist/evals/stats.d.ts +95 -0
  207. package/dist/evals/stats.js +220 -0
  208. package/dist/evals/statsAdapter.d.ts +4 -0
  209. package/dist/evals/statsAdapter.js +30 -0
  210. package/dist/evals/statsTypes.d.ts +51 -0
  211. package/dist/evals/statsTypes.js +18 -0
  212. package/dist/evals/storeError.d.ts +5 -0
  213. package/dist/evals/storeError.js +8 -0
  214. package/dist/evals/storeTypes.d.ts +207 -0
  215. package/dist/evals/storeTypes.js +1 -0
  216. package/dist/evals/tTable.d.ts +3 -0
  217. package/dist/evals/tTable.js +15 -0
  218. package/dist/evals/triage/constants.d.ts +26 -0
  219. package/dist/evals/triage/constants.js +33 -0
  220. package/dist/evals/triage/credentials.d.ts +13 -0
  221. package/dist/evals/triage/credentials.js +20 -0
  222. package/dist/evals/triage/jevClassifier.d.ts +14 -0
  223. package/dist/evals/triage/jevClassifier.js +124 -0
  224. package/dist/evals/triage/judgeClassifier.d.ts +10 -0
  225. package/dist/evals/triage/judgeClassifier.js +76 -0
  226. package/dist/evals/triage/prompts.d.ts +16 -0
  227. package/dist/evals/triage/prompts.js +64 -0
  228. package/dist/evals/triage/service.d.ts +79 -0
  229. package/dist/evals/triage/service.js +217 -0
  230. package/dist/evals/triage/stateBuilder.d.ts +19 -0
  231. package/dist/evals/triage/stateBuilder.js +125 -0
  232. package/dist/evals/triage/types.d.ts +60 -0
  233. package/dist/evals/triage/types.js +8 -0
  234. package/dist/evals/types.d.ts +347 -0
  235. package/dist/evals/types.js +1 -0
  236. package/dist/mcp/deps.d.ts +7 -0
  237. package/dist/mcp/evalTools.d.ts +3 -0
  238. package/dist/mcp/evalTools.js +280 -0
  239. package/dist/mcp/server.js +1 -0
  240. package/dist/mcp/tools.js +2 -0
  241. package/dist/models/response.d.ts +7 -2
  242. package/dist/models/response.js +6 -1
  243. package/dist/operatorAgent/agentAuthorContext.d.ts +1 -1
  244. package/dist/operatorAgent/agentAuthorContext.js +1 -1
  245. package/dist/operatorAgent/piModel.js +17 -4
  246. package/dist/operatorAgent/profiles/agentAuthor.d.ts +3 -1
  247. package/dist/operatorAgent/profiles/agentAuthor.js +145 -20
  248. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
  249. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +48 -15
  250. package/dist/operatorAgent/profiles/authorReadable.d.ts +2 -0
  251. package/dist/operatorAgent/profiles/authorReadable.js +26 -0
  252. package/dist/package-meta.d.ts +1 -1
  253. package/dist/package-meta.js +1 -1
  254. package/dist/runstore/port.d.ts +43 -0
  255. package/dist/runstore/sqlite/SqliteRunStore.d.ts +28 -0
  256. package/dist/runstore/sqlite/SqliteRunStore.js +99 -5
  257. package/dist/runstore/sqlite/evalStore.d.ts +20 -0
  258. package/dist/runstore/sqlite/evalStore.js +541 -0
  259. package/dist/runstore/sqlite/migrations/015-evals.d.ts +12 -0
  260. package/dist/runstore/sqlite/migrations/015-evals.js +157 -0
  261. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.d.ts +12 -0
  262. package/dist/runstore/sqlite/migrations/016-eval-subject-ref.js +20 -0
  263. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.d.ts +11 -0
  264. package/dist/runstore/sqlite/migrations/017-eval-result-suggestions.js +36 -0
  265. package/dist/runstore/sqlite/migrations/index.js +6 -0
  266. package/dist/runstore/sqlite/suggestionStore.d.ts +14 -0
  267. package/dist/runstore/sqlite/suggestionStore.js +132 -0
  268. package/dist/runtime/pipelineRunner.d.ts +2 -1
  269. package/dist/runtime/pipelineRunner.js +2 -0
  270. package/dist/runtime/runManager.d.ts +2 -1
  271. package/dist/runtime/runManager.js +8 -6
  272. package/dist/runtime/runRetentionSweep.js +2 -2
  273. package/dist/server/agentAuthorRoutes.js +50 -1
  274. package/dist/server/bootstrap.d.ts +8 -0
  275. package/dist/server/bootstrap.js +56 -0
  276. package/dist/server/controlToken.js +3 -0
  277. package/dist/server/evalInternalRoutes.d.ts +14 -0
  278. package/dist/server/evalInternalRoutes.js +87 -0
  279. package/dist/server/evalRouteContext.d.ts +46 -0
  280. package/dist/server/evalRouteContext.js +43 -0
  281. package/dist/server/evalRoutes.d.ts +5 -0
  282. package/dist/server/evalRoutes.js +1427 -0
  283. package/dist/server/evalTriageRoutes.d.ts +2 -0
  284. package/dist/server/evalTriageRoutes.js +95 -0
  285. package/dist/server/http.d.ts +5 -0
  286. package/dist/server/http.js +32 -2
  287. package/dist/server/mcpHost.js +3 -0
  288. package/dist/toolkit/meta.d.ts +1 -1
  289. package/dist/toolkit/meta.js +15 -0
  290. package/dist/toolkit/profiles.js +1 -1
  291. package/dist/types/stageflowManifest.d.ts +6 -0
  292. package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +9 -0
  293. package/dist/ui/assets/{MarkdownText-D7tYaIFO.js → MarkdownText-BTrOqu5J.js} +1 -1
  294. package/dist/ui/assets/chat-Df_wCpak.js +5 -0
  295. package/dist/ui/assets/chat-P7KQznHm.css +2 -0
  296. package/dist/ui/assets/{main-BFo79krC.css → main-CHlmTNJe.css} +1 -1
  297. package/dist/ui/assets/main-CvVfxmMd.js +271 -0
  298. package/dist/ui/chat.html +3 -3
  299. package/dist/ui/index.html +3 -3
  300. package/examples/README.md +2 -0
  301. package/examples/document-policy-review/README.md +35 -0
  302. package/examples/document-policy-review/agents/compliance.agent.yaml +42 -0
  303. package/examples/document-policy-review/documents/acme-vendor-sow.md +15 -0
  304. package/examples/document-policy-review/documents/support-runbook.md +13 -0
  305. package/examples/document-policy-review/policies/acceptable-use.md +13 -0
  306. package/examples/document-policy-review/policies/data-handling.md +17 -0
  307. package/examples/document-policy-review/policies/vendor-security.md +17 -0
  308. package/examples/evals/README.md +123 -0
  309. package/examples/evals/critique-sample.dataset.jsonl +8 -0
  310. package/examples/evals/critique-sample.eval.yaml +34 -0
  311. package/examples/evals/critique-sample.task.yaml +4 -0
  312. package/examples/evals/eval-sample.pipeline.yaml +35 -0
  313. package/examples/evals/support-sample.agent.yaml +20 -0
  314. package/examples/evals/support-sample.dataset.jsonl +6 -0
  315. package/examples/evals/support-sample.eval.yaml +38 -0
  316. package/examples/stageflow.yaml +6 -0
  317. package/package.json +1 -1
  318. package/skills/stageflow/SKILL.md +5 -2
  319. package/skills/stageflow/references/control-surface.md +4 -2
  320. package/skills/stageflow/references/evals.md +68 -0
  321. package/skills/stageflow-author/SKILL.md +4 -0
  322. package/skills/stageflow-run/SKILL.md +4 -0
  323. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +0 -9
  324. package/dist/ui/assets/chat-BbLukq9W.js +0 -5
  325. package/dist/ui/assets/chat-zIZU_bP1.css +0 -2
  326. package/dist/ui/assets/main-CijYMeM0.js +0 -259
@@ -0,0 +1,720 @@
1
+ import { stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { buildValidationResult, } from "../config/validateCatalog.js";
4
+ import { loadEvalCatalog } from "../config/evalCatalog.js";
5
+ import { evalStem, findEvalFile, manifestForWrite } from "../config/evalWriteRoots.js";
6
+ import { loadEvalOutcome } from "../config/loadEval.js";
7
+ import { EngineError } from "../evals/engine/promptfoo/errors.js";
8
+ import { installPromptfoo } from "../evals/engine/promptfoo/install.js";
9
+ import { engineStatus } from "../evals/engine/promptfoo/status.js";
10
+ import { globalStageflowHome } from "../project/globalHome.js";
11
+ import { ensureGlobalService, hostBaseUrl, } from "../server/ensureGlobalService.js";
12
+ import { httpCancelEvalRun, httpCompareEvalRuns, httpDeleteEvalRun, httpEnsureProject, httpGetEval, httpGetEvalRun, httpListDatasets, httpListEvalRuns, httpListEvalSuggestions, httpSortEvalFailures, httpListEvals, httpStartEvalRun, } from "./hostClient.js";
13
+ import { resolveCliProjectRootLocal } from "./resolveCliProjectRoot.js";
14
+ import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
15
+ export const EVAL_USAGE = `Usage:
16
+ sf eval list [--json]
17
+ sf eval show <id|path> [--json]
18
+ sf eval validate [<id|path>] [--json] [--strict]
19
+ sf eval run <id|path> [--repeats <n>] [--judge <model>] [--model <id>] [--user-model <model>] [--allow-real-tools] [--max-cost <usd>] [--case <id> ...] [--tag <tag> ...] [--label <text>] [--no-wait] [--json]
20
+ sf eval runs [<evalId>] [--json]
21
+ sf eval runs show <evalRunId> [--json]
22
+ sf eval runs cancel <evalRunId>
23
+ sf eval runs delete <evalRunId>
24
+ sf eval sort <evalRunId> [--classifier auto|jev|judge] [--limit <n>] [--resort] [--json]
25
+ sf eval suggestions <evalRunId> [--status pending|accepted|dismissed] [--json]
26
+ sf eval compare <evalRunIdA> <evalRunIdB> [--json]
27
+ sf eval datasets [--json]
28
+ sf eval engine status [--json]
29
+ sf eval engine install
30
+
31
+ Agent evals: --model overrides the agent's model, --user-model the model that plays the user. An eval whose
32
+ conversation.tools is real also needs --allow-real-tools.
33
+
34
+ Every subcommand also accepts --project-root <id-or-path>.`;
35
+ const defaultIo = {
36
+ log: (line) => console.log(line),
37
+ error: (line) => console.error(line),
38
+ };
39
+ const COMMON_FLAGS = {
40
+ "--json": {},
41
+ "--help": {},
42
+ "-h": {},
43
+ "--project-root": { value: true },
44
+ };
45
+ const FLAGS = {
46
+ list: {},
47
+ show: {},
48
+ validate: { "--strict": {} },
49
+ run: {
50
+ "--repeats": { value: true },
51
+ "--judge": { value: true },
52
+ "--model": { value: true },
53
+ "--user-model": { value: true },
54
+ "--allow-real-tools": {},
55
+ "--max-cost": { value: true },
56
+ "--case": { value: true, repeat: true },
57
+ "--tag": { value: true, repeat: true },
58
+ "--label": { value: true },
59
+ "--no-wait": {},
60
+ },
61
+ runs: {},
62
+ sort: { "--classifier": { value: true }, "--limit": { value: true }, "--resort": {} },
63
+ suggestions: { "--status": { value: true } },
64
+ compare: {},
65
+ datasets: {},
66
+ engine: {},
67
+ };
68
+ class UsageError extends Error {
69
+ }
70
+ function parseArgs(command, rest) {
71
+ const specs = { ...COMMON_FLAGS, ...FLAGS[command] };
72
+ const parsed = {
73
+ help: false,
74
+ json: false,
75
+ strict: false,
76
+ noWait: false,
77
+ allowRealTools: false,
78
+ resort: false,
79
+ cases: [],
80
+ tags: [],
81
+ positionals: [],
82
+ };
83
+ const seen = new Set();
84
+ for (let i = 0; i < rest.length; i++) {
85
+ const arg = rest[i];
86
+ if (!arg.startsWith("-")) {
87
+ parsed.positionals.push(arg);
88
+ continue;
89
+ }
90
+ const spec = specs[arg];
91
+ if (spec === undefined)
92
+ throw new UsageError(`Unknown flag: ${arg}`);
93
+ if (seen.has(arg) && spec.repeat !== true)
94
+ throw new UsageError(`Duplicate flag: ${arg}`);
95
+ seen.add(arg);
96
+ let value;
97
+ if (spec.value === true) {
98
+ value = rest[++i];
99
+ if (value === undefined || value.length === 0)
100
+ throw new UsageError(`Missing value for ${arg}`);
101
+ }
102
+ switch (arg) {
103
+ case "--help":
104
+ case "-h":
105
+ parsed.help = true;
106
+ break;
107
+ case "--json":
108
+ parsed.json = true;
109
+ break;
110
+ case "--strict":
111
+ parsed.strict = true;
112
+ break;
113
+ case "--no-wait":
114
+ parsed.noWait = true;
115
+ break;
116
+ case "--allow-real-tools":
117
+ parsed.allowRealTools = true;
118
+ break;
119
+ case "--user-model":
120
+ parsed.userModel = value;
121
+ break;
122
+ case "--project-root":
123
+ parsed.projectRoot = value;
124
+ break;
125
+ case "--judge":
126
+ parsed.judge = value;
127
+ break;
128
+ case "--model":
129
+ parsed.model = value;
130
+ break;
131
+ case "--label":
132
+ parsed.label = value;
133
+ break;
134
+ case "--case":
135
+ parsed.cases.push(value);
136
+ break;
137
+ case "--classifier":
138
+ if (value !== "auto" && value !== "jev" && value !== "judge") {
139
+ throw new UsageError("--classifier must be auto, jev or judge");
140
+ }
141
+ parsed.classifier = value;
142
+ break;
143
+ case "--limit": {
144
+ const n = Number(value);
145
+ if (!Number.isInteger(n) || n < 1)
146
+ throw new UsageError("--limit must be a positive integer");
147
+ parsed.limit = n;
148
+ break;
149
+ }
150
+ case "--resort":
151
+ parsed.resort = true;
152
+ break;
153
+ case "--status":
154
+ if (value !== "pending" && value !== "accepted" && value !== "dismissed") {
155
+ throw new UsageError("--status must be pending, accepted or dismissed");
156
+ }
157
+ parsed.status = value;
158
+ break;
159
+ case "--tag":
160
+ parsed.tags.push(value);
161
+ break;
162
+ case "--repeats": {
163
+ const n = Number(value);
164
+ if (!Number.isInteger(n) || n < 1)
165
+ throw new UsageError("--repeats must be a positive integer");
166
+ parsed.repeats = n;
167
+ break;
168
+ }
169
+ case "--max-cost": {
170
+ const n = Number(value);
171
+ if (!Number.isFinite(n) || n <= 0)
172
+ throw new UsageError("--max-cost must be a positive number");
173
+ parsed.maxCost = n;
174
+ break;
175
+ }
176
+ }
177
+ }
178
+ if (parsed.cases.length > 0 && parsed.tags.length > 0) {
179
+ throw new UsageError("Use either --case or --tag, not both");
180
+ }
181
+ return parsed;
182
+ }
183
+ function looksLikePath(ref) {
184
+ return /[\\/]/.test(ref) || /\.ya?ml$/.test(ref);
185
+ }
186
+ function pct(value) {
187
+ return typeof value === "number" ? `${(value * 100).toFixed(1)}%` : "n/a";
188
+ }
189
+ function usd(value) {
190
+ return typeof value === "number" ? `$${value.toFixed(4)}` : "n/a";
191
+ }
192
+ function suggestionLine(s) {
193
+ return [
194
+ s.case_id,
195
+ `#${s.repeat_index}`,
196
+ s.bucket,
197
+ s.failure_type ?? "-",
198
+ `${Math.round(s.confidence * 100)}%`,
199
+ s.status,
200
+ ].join("\t");
201
+ }
202
+ function agentLabel(target) {
203
+ const id = String(target.agent_id ?? target.id ?? "");
204
+ return typeof target.revision === "number" ? `Agent ${id} r${target.revision}` : `Agent ${id}`;
205
+ }
206
+ function targetLabel(target) {
207
+ if (target?.kind === "agent")
208
+ return agentLabel(target);
209
+ return target?.label ?? "";
210
+ }
211
+ function conversationLine(conversation) {
212
+ if (!conversation)
213
+ return null;
214
+ return `Conversation: ${conversation.mode ?? "script"}, up to ${conversation.max_turns ?? "?"} turns, tools ${conversation.tools ?? "read_only"}, approvals ${conversation.approvals ?? "record_only"}`;
215
+ }
216
+ const REAL_TOOLS_WARNING = "Warning: real tools can change files and call services from this machine.";
217
+ function outcomeOf(detail) {
218
+ if (detail.status === "completed")
219
+ return detail.passed === true ? "passed" : "below_threshold";
220
+ if (detail.status === "cancelled")
221
+ return "cancelled";
222
+ if (detail.status === "budget_stopped")
223
+ return "budget_stopped";
224
+ return "failed";
225
+ }
226
+ const TERMINAL = new Set(["completed", "failed", "cancelled", "budget_stopped"]);
227
+ export async function runEvalCommand(args, options = {}) {
228
+ const out = { ...defaultIo, ...options.io };
229
+ const cwd = options.cwd ?? process.cwd();
230
+ const base = options.hostBaseUrl ?? hostBaseUrl();
231
+ const ensureService = options.ensureService ?? (() => ensureGlobalService());
232
+ const usageError = (message) => {
233
+ if (message !== undefined)
234
+ out.error(message);
235
+ out.error(EVAL_USAGE);
236
+ return 1;
237
+ };
238
+ if (args.length === 0)
239
+ return usageError();
240
+ if (args[0] === "--help" || args[0] === "-h") {
241
+ out.error(EVAL_USAGE);
242
+ return 0;
243
+ }
244
+ const command = args[0];
245
+ if (!(command in FLAGS))
246
+ return usageError(`Unknown eval subcommand: ${command}`);
247
+ let parsed;
248
+ try {
249
+ parsed = parseArgs(command, args.slice(1));
250
+ }
251
+ catch (err) {
252
+ if (err instanceof UsageError)
253
+ return usageError(err.message);
254
+ throw err;
255
+ }
256
+ if (parsed.help) {
257
+ out.error(EVAL_USAGE);
258
+ return 0;
259
+ }
260
+ const positionals = [...parsed.positionals];
261
+ const json = parsed.json;
262
+ const printJson = (payload) => out.log(JSON.stringify(payload, null, 2));
263
+ const fail = (failure) => {
264
+ if (json) {
265
+ printJson({
266
+ error: failure.reason,
267
+ ...(failure.code !== undefined ? { code: failure.code } : {}),
268
+ ...(failure.status !== undefined ? { status: failure.status } : {}),
269
+ });
270
+ }
271
+ else {
272
+ out.error(failure.code !== undefined ? `${failure.code}: ${failure.reason}` : failure.reason);
273
+ }
274
+ return 1;
275
+ };
276
+ let projectRoot = options.projectRoot ?? cwd;
277
+ if (parsed.projectRoot !== undefined) {
278
+ const local = resolveCliProjectRootLocal(parsed.projectRoot);
279
+ if (!local)
280
+ return usageError(`Unknown project_root: ${parsed.projectRoot.trim()}`);
281
+ projectRoot = local.rootPath;
282
+ }
283
+ const expectArgs = (min, max, label) => {
284
+ if (positionals.length < min)
285
+ return `Missing ${label}`;
286
+ if (positionals.length > max)
287
+ return `Unexpected argument: ${positionals[max]}`;
288
+ return null;
289
+ };
290
+ const resolveEvalFile = async (ref, local = false) => {
291
+ if (looksLikePath(ref)) {
292
+ const absPath = path.resolve(cwd, ref);
293
+ try {
294
+ if (!(await stat(absPath)).isFile())
295
+ return `Not a file: ${ref}`;
296
+ }
297
+ catch {
298
+ return `Eval file not found: ${ref}`;
299
+ }
300
+ const outcome = await loadEvalOutcome(absPath, projectRoot);
301
+ return { id: outcome.ok ? outcome.value.id : evalStem(path.basename(absPath)), absPath };
302
+ }
303
+ if (!local)
304
+ return { id: ref };
305
+ const found = await findEvalFile(projectRoot, ref);
306
+ if (found === undefined)
307
+ return `Eval not found: ${ref}`;
308
+ return { id: ref, absPath: found.absPath };
309
+ };
310
+ if (command === "engine") {
311
+ const sub = positionals.shift();
312
+ const home = options.home ?? globalStageflowHome();
313
+ if (sub === "status") {
314
+ const extra = expectArgs(0, 0, "");
315
+ if (extra)
316
+ return usageError(extra);
317
+ const status = engineStatus({ home });
318
+ const wire = {
319
+ installed: status.installed,
320
+ version: status.version,
321
+ pinned: status.pinned,
322
+ node: status.node,
323
+ node_ok: status.nodeOk,
324
+ install_command: status.installCommand,
325
+ };
326
+ if (json)
327
+ printJson(wire);
328
+ else {
329
+ out.log(status.installed
330
+ ? `Eval engine: promptfoo ${status.version} installed (pinned ${status.pinned})`
331
+ : `Eval engine: not installed (pinned ${status.pinned}). Run \`${status.installCommand}\``);
332
+ out.log(`Node ${status.node}${status.nodeOk ? "" : " (too old for evals)"}`);
333
+ }
334
+ return 0;
335
+ }
336
+ if (sub === "install") {
337
+ const extra = expectArgs(0, 0, "");
338
+ if (extra)
339
+ return usageError(extra);
340
+ if (json)
341
+ return usageError("Unknown flag: --json");
342
+ try {
343
+ const result = await installPromptfoo({
344
+ home,
345
+ ...(options.runNpm !== undefined ? { runNpm: options.runNpm } : {}),
346
+ log: (line) => out.error(line),
347
+ });
348
+ out.log(result.status === "installed"
349
+ ? `Installed promptfoo ${result.version}`
350
+ : `promptfoo ${result.version} is already installed`);
351
+ return 0;
352
+ }
353
+ catch (err) {
354
+ if (err instanceof EngineError) {
355
+ out.error(`${err.code}: ${err.message}`);
356
+ return 1;
357
+ }
358
+ out.error(err instanceof Error ? err.message : String(err));
359
+ return 1;
360
+ }
361
+ }
362
+ return usageError(sub === undefined ? "Missing engine subcommand" : `Unknown engine subcommand: ${sub}`);
363
+ }
364
+ if (command === "validate") {
365
+ const extra = expectArgs(0, 1, "");
366
+ if (extra)
367
+ return usageError(extra);
368
+ let findings;
369
+ try {
370
+ const manifest = await manifestForWrite(projectRoot);
371
+ const entries = await loadEvalCatalog(manifest);
372
+ if (positionals.length === 1) {
373
+ const target = await resolveEvalFile(positionals[0], true);
374
+ if (typeof target === "string") {
375
+ out.error(target);
376
+ return 1;
377
+ }
378
+ const entry = entries.find((e) => path.resolve(e.absPath) === path.resolve(target.absPath));
379
+ if (entry === undefined) {
380
+ out.error(`Eval is not in a catalog root: ${positionals[0]}`);
381
+ return 1;
382
+ }
383
+ findings = entry.findings;
384
+ }
385
+ else {
386
+ findings = entries.flatMap((e) => e.findings);
387
+ }
388
+ }
389
+ catch (err) {
390
+ out.error(err instanceof Error ? err.message : String(err));
391
+ return 1;
392
+ }
393
+ const result = buildValidationResult("full", findings, parsed.strict);
394
+ out.log(json ? formatValidationJson(result) : formatValidationHuman(result, { strict: parsed.strict }));
395
+ return exitCodeForValidation(result);
396
+ }
397
+ const argError = command === "list" || command === "datasets"
398
+ ? expectArgs(0, 0, "")
399
+ : command === "show"
400
+ ? expectArgs(1, 1, "<id|path>")
401
+ : command === "run"
402
+ ? expectArgs(1, 1, "<id|path>")
403
+ : command === "compare"
404
+ ? expectArgs(2, 2, "<evalRunIdA> <evalRunIdB>")
405
+ : command === "sort" || command === "suggestions"
406
+ ? expectArgs(1, 1, "<evalRunId>")
407
+ : null;
408
+ if (argError)
409
+ return usageError(argError);
410
+ let runsSub;
411
+ if (command === "runs") {
412
+ if (["show", "cancel", "delete"].includes(positionals[0] ?? "")) {
413
+ runsSub = positionals.shift();
414
+ const err = expectArgs(1, 1, "<evalRunId>");
415
+ if (err)
416
+ return usageError(err);
417
+ }
418
+ else {
419
+ const err = expectArgs(0, 1, "");
420
+ if (err)
421
+ return usageError(err);
422
+ }
423
+ if (json && (runsSub === "cancel" || runsSub === "delete"))
424
+ return usageError("Unknown flag: --json");
425
+ }
426
+ const ensured = await ensureService();
427
+ if (!ensured.ok) {
428
+ if (json)
429
+ printJson({ error: ensured.message, reason: ensured.reason });
430
+ else
431
+ out.error(ensured.message);
432
+ return 1;
433
+ }
434
+ const project = await httpEnsureProject(base, projectRoot);
435
+ if (!project.ok)
436
+ return fail(project);
437
+ const wireRoot = project.project_root;
438
+ const show = (result, human) => {
439
+ if (!result.ok)
440
+ return fail(result);
441
+ if (json)
442
+ printJson(result.body);
443
+ else
444
+ human(result.body);
445
+ return 0;
446
+ };
447
+ switch (command) {
448
+ case "list":
449
+ return show(await httpListEvals(base, wireRoot), (body) => {
450
+ const items = (body.items ?? []);
451
+ for (const item of items) {
452
+ out.log([
453
+ item.id,
454
+ item.status,
455
+ item.valid === false ? "invalid" : "valid",
456
+ item.last_run ? pct(item.last_run.pass_rate) : "-",
457
+ targetLabel(item.target),
458
+ ].join("\t"));
459
+ }
460
+ });
461
+ case "show": {
462
+ const target = await resolveEvalFile(positionals[0]);
463
+ if (typeof target === "string")
464
+ return fail({ reason: target, code: "EVAL_NOT_FOUND" });
465
+ return show(await httpGetEval(base, target.id, wireRoot), (body) => {
466
+ const detail = body;
467
+ if (detail.definition?.target?.kind === "agent") {
468
+ const live = detail.agent;
469
+ out.log(live
470
+ ? `${agentLabel({ id: live.id, revision: live.revision })}${live.model ? ` (${live.model})` : ""}`
471
+ : agentLabel({ id: detail.definition.target.agent }));
472
+ const line = conversationLine({
473
+ mode: detail.definition.conversation?.mode,
474
+ max_turns: detail.definition.conversation?.max_turns,
475
+ tools: detail.definition.conversation?.tools,
476
+ approvals: detail.definition.conversation?.approvals,
477
+ });
478
+ if (line)
479
+ out.log(line);
480
+ out.log("");
481
+ }
482
+ out.log(String(detail.yaml ?? ""));
483
+ });
484
+ }
485
+ case "datasets":
486
+ return show(await httpListDatasets(base, wireRoot), (body) => {
487
+ for (const item of body.items) {
488
+ out.log([item.id, item.format, item.cases, item.path].join("\t"));
489
+ }
490
+ });
491
+ case "compare":
492
+ return show(await httpCompareEvalRuns(base, positionals[0], positionals[1]), (body) => {
493
+ out.log(JSON.stringify(body, null, 2));
494
+ });
495
+ case "runs": {
496
+ if (runsSub === "show") {
497
+ return show(await httpGetEvalRun(base, positionals[0]), (d) => {
498
+ out.log(`${d.eval_id} run #${d.run_number}\t${d.status}\t${d.eval_run_id}`);
499
+ if (d.status_reason)
500
+ out.log(`reason: ${d.status_reason}`);
501
+ out.log(`pass rate ${pct(d.stats?.pass_rate)}\tresults ${d.progress?.results_done}/${d.progress?.results_total}\tcost ${usd(d.stats?.cost_total ?? d.progress?.cost_usd)}`);
502
+ if (d.config?.target?.agent_id !== undefined) {
503
+ out.log(agentLabel(d.config.target));
504
+ const line = conversationLine(d.config.settings?.conversation);
505
+ if (line)
506
+ out.log(line);
507
+ out.log(`mean turns ${d.stats?.mean_turns ?? "n/a"}\tmean tool calls ${d.stats?.mean_tool_calls ?? "n/a"}`);
508
+ }
509
+ out.log(`fingerprint ${d.fingerprint}`);
510
+ });
511
+ }
512
+ if (runsSub === "cancel") {
513
+ const result = await httpCancelEvalRun(base, positionals[0]);
514
+ if (!result.ok)
515
+ return fail(result);
516
+ out.log(`Eval run ${result.body.eval_run_id} ${result.body.status}`);
517
+ return 0;
518
+ }
519
+ if (runsSub === "delete") {
520
+ const result = await httpDeleteEvalRun(base, positionals[0]);
521
+ if (!result.ok)
522
+ return fail(result);
523
+ out.log(`Deleted eval run ${positionals[0]}`);
524
+ return 0;
525
+ }
526
+ return show(await httpListEvalRuns(base, {
527
+ project_root: wireRoot,
528
+ ...(positionals[0] !== undefined ? { eval_id: positionals[0] } : {}),
529
+ }), (body) => {
530
+ for (const item of body.items) {
531
+ out.log([
532
+ item.eval_id,
533
+ `#${item.run_number}`,
534
+ item.status,
535
+ pct(item.pass_rate),
536
+ usd(item.cost_total),
537
+ item.eval_run_id,
538
+ ].join("\t"));
539
+ }
540
+ });
541
+ }
542
+ case "run":
543
+ return runEval();
544
+ case "sort": {
545
+ const result = await httpSortEvalFailures(base, positionals[0], {
546
+ classifier: parsed.classifier ?? "auto",
547
+ ...(parsed.resort ? { only_unsorted: false } : {}),
548
+ ...(parsed.limit !== undefined ? { limit: parsed.limit } : {}),
549
+ });
550
+ return show(result, (b) => {
551
+ out.log(`Sorted ${b.sorted}, skipped ${b.skipped}, failed ${b.failed} with ${b.classifier} (${b.model}); sorting cost ${usd(b.triage_cost_usd)}`);
552
+ for (const s of b.suggestions)
553
+ out.log(suggestionLine(s));
554
+ out.log(`Review and accept in the console, or POST /api/eval-runs/${positionals[0]}/failures/suggestions/accept`);
555
+ });
556
+ }
557
+ case "suggestions":
558
+ return show(await httpListEvalSuggestions(base, positionals[0], parsed.status), (b) => {
559
+ for (const s of b.items)
560
+ out.log(suggestionLine(s));
561
+ const sum = b.summary;
562
+ out.log(`agent_wrong ${sum.by_bucket.agent_wrong}\tgrader_wrong ${sum.by_bucket.grader_wrong}\tcase_wrong ${sum.by_bucket.case_wrong}\tunclear ${sum.by_bucket.unclear}\t` +
563
+ `pending ${sum.pending}\taccepted ${sum.accepted}\tdismissed ${sum.dismissed}\tlow confidence ${sum.low_confidence}`);
564
+ });
565
+ default:
566
+ return usageError(`Unknown eval subcommand: ${command}`);
567
+ }
568
+ async function runEval() {
569
+ const target = await resolveEvalFile(positionals[0]);
570
+ if (typeof target === "string")
571
+ return fail({ reason: target, code: "EVAL_NOT_FOUND" });
572
+ const input = {
573
+ project_root: wireRoot,
574
+ started_via: "cli",
575
+ ...(parsed.repeats !== undefined ? { repeats: parsed.repeats } : {}),
576
+ ...(parsed.judge !== undefined ? { judge: parsed.judge } : {}),
577
+ ...(parsed.model !== undefined ? { model: parsed.model } : {}),
578
+ ...(parsed.userModel !== undefined ? { user_model: parsed.userModel } : {}),
579
+ ...(parsed.allowRealTools ? { allow_real_tools: true } : {}),
580
+ ...(parsed.maxCost !== undefined ? { max_cost_usd: parsed.maxCost } : {}),
581
+ ...(parsed.cases.length > 0 ? { case_ids: parsed.cases } : {}),
582
+ ...(parsed.tags.length > 0 ? { tags: parsed.tags } : {}),
583
+ ...(parsed.label !== undefined ? { label: parsed.label } : {}),
584
+ };
585
+ if (parsed.allowRealTools) {
586
+ const detail = await httpGetEval(base, target.id, wireRoot);
587
+ const definition = detail.ok ? detail.body.definition : null;
588
+ if (definition?.target?.kind === "agent" && definition.conversation?.tools === "real") {
589
+ out.error(REAL_TOOLS_WARNING);
590
+ }
591
+ }
592
+ const started = await httpStartEvalRun(base, target.id, input);
593
+ if (!started.ok) {
594
+ if (started.code === "EVAL_REAL_TOOLS_NOT_CONFIRMED") {
595
+ if (json)
596
+ printJson({ ok: false, code: started.code, message: started.reason });
597
+ else
598
+ out.error(`${started.code}: ${started.reason} Re-run with --allow-real-tools to confirm.`);
599
+ return 1;
600
+ }
601
+ if (started.code === "EVAL_INVALID" && started.findings !== undefined) {
602
+ const result = buildValidationResult("full", started.findings, false);
603
+ out.log(json ? formatValidationJson(result) : formatValidationHuman(result));
604
+ return 1;
605
+ }
606
+ return fail(started);
607
+ }
608
+ const evalRunId = started.body.eval_run_id;
609
+ if (parsed.noWait) {
610
+ if (json)
611
+ printJson({ eval_run_id: evalRunId, status: started.body.status });
612
+ else
613
+ out.log(evalRunId);
614
+ return 0;
615
+ }
616
+ let interrupted = false;
617
+ let wake = () => { };
618
+ const woken = new Promise((resolve) => {
619
+ wake = resolve;
620
+ });
621
+ const subscribe = options.onInterrupt ??
622
+ ((handler) => {
623
+ process.once("SIGINT", handler);
624
+ return () => process.removeListener("SIGINT", handler);
625
+ });
626
+ const unsubscribe = subscribe(() => {
627
+ interrupted = true;
628
+ wake();
629
+ });
630
+ const interval = options.pollIntervalMs ?? 1500;
631
+ const tty = options.stderrIsTTY ?? process.stderr.isTTY === true;
632
+ let lastProgress = "";
633
+ let detail;
634
+ try {
635
+ for (;;) {
636
+ if (interrupted) {
637
+ const cancelled = await httpCancelEvalRun(base, evalRunId);
638
+ if (!cancelled.ok) {
639
+ fail(cancelled);
640
+ return 1;
641
+ }
642
+ detail = cancelled.body;
643
+ break;
644
+ }
645
+ const polled = await httpGetEvalRun(base, evalRunId);
646
+ if (!polled.ok)
647
+ return fail(polled);
648
+ detail = polled.body;
649
+ if (TERMINAL.has(detail.status))
650
+ break;
651
+ if (tty) {
652
+ const line = `${detail.progress?.results_done ?? 0}/${detail.progress?.results_total ?? 0} results, ${usd(detail.progress?.cost_usd ?? 0)}`;
653
+ if (line !== lastProgress) {
654
+ lastProgress = line;
655
+ out.error(line);
656
+ }
657
+ }
658
+ await Promise.race([new Promise((resolve) => setTimeout(resolve, interval)), woken]);
659
+ }
660
+ }
661
+ finally {
662
+ unsubscribe();
663
+ }
664
+ return reportRun(detail);
665
+ }
666
+ function reportRun(detail) {
667
+ const outcome = outcomeOf(detail);
668
+ const stats = detail.stats;
669
+ const threshold = detail.config?.settings?.pass_threshold ?? detail.components?.pass_threshold ?? null;
670
+ const doc = {
671
+ outcome,
672
+ eval_run_id: detail.eval_run_id,
673
+ eval_id: detail.eval_id,
674
+ pass_rate: stats?.pass_rate ?? null,
675
+ pass_rate_ci: stats?.pass_rate_ci ?? null,
676
+ threshold,
677
+ results: {
678
+ executed: stats?.results_executed ?? 0,
679
+ passed: stats?.results_passed ?? 0,
680
+ errored: stats?.errored ?? 0,
681
+ },
682
+ reliability: stats?.reliability ?? null,
683
+ cost_total: stats?.cost_total ?? detail.progress?.cost_usd ?? 0,
684
+ fingerprint: detail.fingerprint,
685
+ url: `${base}/#/evals/${detail.eval_id}/runs/${detail.run_number}`,
686
+ ...(detail.config?.target?.agent_id !== undefined
687
+ ? {
688
+ target_kind: "agent",
689
+ agent_id: detail.config.target.agent_id,
690
+ agent_revision: (detail.config.target.revision ?? null),
691
+ mean_turns: (stats?.mean_turns ?? null),
692
+ mean_tool_calls: (stats?.mean_tool_calls ?? null),
693
+ tools_policy: (detail.config.settings?.conversation?.tools ?? null),
694
+ }
695
+ : {}),
696
+ };
697
+ if (json) {
698
+ printJson(doc);
699
+ }
700
+ else {
701
+ const ci = doc.pass_rate_ci
702
+ ? ` (CI ${pct(doc.pass_rate_ci.low)} to ${pct(doc.pass_rate_ci.high)})`
703
+ : "";
704
+ out.log(`Eval ${doc.eval_id} run #${detail.run_number}: ${outcome.replace("_", " ").toUpperCase()}`);
705
+ if (detail.status_reason)
706
+ out.log(`Reason: ${detail.status_reason}`);
707
+ out.log(`Pass rate: ${pct(doc.pass_rate)}${ci}, threshold ${pct(threshold)}`);
708
+ out.log(`Results: ${doc.results.executed} executed, ${doc.results.passed} passed, ${doc.results.errored} errored`);
709
+ out.log(`Reliability: ${doc.reliability ?? "n/a"}`);
710
+ if (doc.target_kind === "agent") {
711
+ out.log(`${agentLabel({ id: doc.agent_id, revision: doc.agent_revision })}, tools ${doc.tools_policy ?? "n/a"}`);
712
+ out.log(`Mean turns: ${doc.mean_turns ?? "n/a"}, mean tool calls: ${doc.mean_tool_calls ?? "n/a"}`);
713
+ }
714
+ out.log(`Cost: ${usd(doc.cost_total)}`);
715
+ out.log(`Fingerprint: ${doc.fingerprint}`);
716
+ out.log(`URL: ${doc.url}`);
717
+ }
718
+ return outcome === "passed" ? 0 : 1;
719
+ }
720
+ }