stageflow 0.42.0 → 0.46.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 (315) hide show
  1. package/README.md +12 -3
  2. package/dist/agent/activity.d.ts +18 -0
  3. package/dist/agent/claudeAdapter.js +7 -0
  4. package/dist/agent/claudeTools.d.ts +27 -18
  5. package/dist/agent/claudeTools.js +54 -15
  6. package/dist/agent/compactionEvents.d.ts +34 -0
  7. package/dist/agent/compactionEvents.js +37 -0
  8. package/dist/agent/openRouterRouting.d.ts +7 -0
  9. package/dist/agent/openRouterRouting.js +23 -0
  10. package/dist/agent/piAdapter.d.ts +6 -0
  11. package/dist/agent/piAdapter.js +128 -27
  12. package/dist/agent/piSettings.d.ts +7 -0
  13. package/dist/agent/piSettings.js +9 -0
  14. package/dist/agent/sandboxPiTools.d.ts +3 -1
  15. package/dist/agent/sandboxPiTools.js +8 -2
  16. package/dist/agent/stageTurnDriver.d.ts +11 -2
  17. package/dist/agent/stageTurnDriver.js +45 -4
  18. package/dist/agent/toolOutputBudget.d.ts +15 -1
  19. package/dist/agent/toolOutputBudget.js +18 -0
  20. package/dist/agent/toolOutputBudgetExtension.d.ts +2 -0
  21. package/dist/agent/toolOutputBudgetExtension.js +40 -3
  22. package/dist/agents/authorChat.d.ts +1 -0
  23. package/dist/agents/authorChat.js +7 -0
  24. package/dist/agents/authorSessionStore.d.ts +3 -0
  25. package/dist/agents/authorSessionStore.js +17 -2
  26. package/dist/agents/evalGate.js +1 -0
  27. package/dist/agents/hostContext.d.ts +1 -1
  28. package/dist/agents/runtime/piRuntime.d.ts +5 -1
  29. package/dist/agents/runtime/piRuntime.js +46 -12
  30. package/dist/agents/service.js +3 -2
  31. package/dist/agents/sessions.js +3 -1
  32. package/dist/cli/evalCommand.d.ts +1 -1
  33. package/dist/cli/evalCommand.js +39 -2
  34. package/dist/cli/hostClient.d.ts +6 -0
  35. package/dist/cli/hostClient.js +4 -0
  36. package/dist/compaction/directives.d.ts +18 -0
  37. package/dist/compaction/directives.js +119 -0
  38. package/dist/compaction/extension.d.ts +20 -0
  39. package/dist/compaction/extension.js +78 -0
  40. package/dist/compaction/mask.d.ts +18 -0
  41. package/dist/compaction/mask.js +155 -0
  42. package/dist/compaction/pin.d.ts +21 -0
  43. package/dist/compaction/pin.js +270 -0
  44. package/dist/compaction/scrub.d.ts +7 -0
  45. package/dist/compaction/scrub.js +26 -0
  46. package/dist/compaction/sessionWiring.d.ts +22 -0
  47. package/dist/compaction/sessionWiring.js +26 -0
  48. package/dist/compaction/summarize.d.ts +79 -0
  49. package/dist/compaction/summarize.js +171 -0
  50. package/dist/compaction/toolPolicy.d.ts +3 -0
  51. package/dist/compaction/toolPolicy.js +33 -0
  52. package/dist/compaction/types.d.ts +45 -0
  53. package/dist/compaction/types.js +7 -0
  54. package/dist/compaction/workingState.d.ts +56 -0
  55. package/dist/compaction/workingState.js +198 -0
  56. package/dist/config/createPipeline.js +2 -0
  57. package/dist/config/createTrigger.js +7 -1
  58. package/dist/config/ensureCatalogScanRoot.d.ts +2 -0
  59. package/dist/config/ensureCatalogScanRoot.js +6 -0
  60. package/dist/config/evalCatalog.js +30 -2
  61. package/dist/config/githubTriggerAction.d.ts +8 -0
  62. package/dist/config/githubTriggerAction.js +29 -0
  63. package/dist/config/hostConfig.js +2 -0
  64. package/dist/config/loadEval.d.ts +3 -1
  65. package/dist/config/loadEval.js +96 -10
  66. package/dist/config/pipelineNeeds.d.ts +2 -0
  67. package/dist/config/pipelineNeeds.js +16 -3
  68. package/dist/config/pipelineRoute.js +5 -4
  69. package/dist/config/validateCatalog.d.ts +1 -1
  70. package/dist/config/validateCatalog.js +12 -0
  71. package/dist/config/validateEval.js +60 -3
  72. package/dist/config/writeEval.js +2 -2
  73. package/dist/envelope/assertEmittedArtifacts.d.ts +12 -0
  74. package/dist/envelope/assertEmittedArtifacts.js +43 -0
  75. package/dist/envelope/preEmitChecks.d.ts +3 -0
  76. package/dist/evals/calibration.d.ts +21 -0
  77. package/dist/evals/calibration.js +69 -0
  78. package/dist/evals/calibrationSuggest.d.ts +29 -0
  79. package/dist/evals/calibrationSuggest.js +72 -0
  80. package/dist/evals/calibrator.d.ts +46 -0
  81. package/dist/evals/calibrator.js +148 -0
  82. package/dist/evals/compare.d.ts +1 -1
  83. package/dist/evals/compare.js +3 -0
  84. package/dist/evals/constants.d.ts +7 -3
  85. package/dist/evals/constants.js +7 -1
  86. package/dist/evals/engine/FakeEngine.d.ts +2 -1
  87. package/dist/evals/engine/FakeEngine.js +9 -3
  88. package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +1 -1
  89. package/dist/evals/engine/promptfoo/PromptfooEngine.js +4 -2
  90. package/dist/evals/engine/promptfoo/generate.d.ts +3 -1
  91. package/dist/evals/engine/promptfoo/generate.js +43 -23
  92. package/dist/evals/engine/promptfoo/judgeProviders.js +2 -1
  93. package/dist/evals/engine/promptfoo/normalize.d.ts +4 -1
  94. package/dist/evals/engine/promptfoo/normalize.js +16 -3
  95. package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +3 -1
  96. package/dist/evals/engine/promptfoo/rubricPrompt.js +15 -5
  97. package/dist/evals/errors.d.ts +7 -0
  98. package/dist/evals/errors.js +10 -0
  99. package/dist/evals/judgeProviders.d.ts +6 -0
  100. package/dist/evals/judgeProviders.js +14 -0
  101. package/dist/evals/judged.d.ts +7 -0
  102. package/dist/evals/judged.js +7 -0
  103. package/dist/evals/manager.d.ts +27 -8
  104. package/dist/evals/manager.js +73 -12
  105. package/dist/evals/plan.d.ts +10 -1
  106. package/dist/evals/plan.js +33 -13
  107. package/dist/evals/planInternalAgent.d.ts +19 -0
  108. package/dist/evals/planInternalAgent.js +201 -0
  109. package/dist/evals/read/api.d.ts +18 -0
  110. package/dist/evals/read/api.js +10 -1
  111. package/dist/evals/read/labelQueue.d.ts +45 -0
  112. package/dist/evals/read/labelQueue.js +83 -0
  113. package/dist/evals/read/list.js +10 -1
  114. package/dist/evals/read/runs.d.ts +5 -0
  115. package/dist/evals/read/runs.js +7 -1
  116. package/dist/evals/runner/caseRunner.d.ts +2 -1
  117. package/dist/evals/runner/caseRunner.js +16 -5
  118. package/dist/evals/runner/internalAgentCaseRunner.d.ts +6 -0
  119. package/dist/evals/runner/internalAgentCaseRunner.js +250 -0
  120. package/dist/evals/snapshot.d.ts +10 -2
  121. package/dist/evals/snapshot.js +8 -3
  122. package/dist/evals/stats.d.ts +40 -0
  123. package/dist/evals/stats.js +69 -0
  124. package/dist/evals/storeTypes.d.ts +42 -0
  125. package/dist/evals/types.d.ts +25 -2
  126. package/dist/evals/types.js +4 -1
  127. package/dist/evals/votes.d.ts +10 -0
  128. package/dist/evals/votes.js +60 -0
  129. package/dist/guide/cardContract.d.ts +133 -0
  130. package/dist/guide/cardContract.js +156 -0
  131. package/dist/guide/chat.d.ts +45 -0
  132. package/dist/guide/chat.js +378 -0
  133. package/dist/guide/knowledge/index.d.ts +14 -0
  134. package/dist/guide/knowledge/index.js +109 -0
  135. package/dist/guide/knowledge/topics/agents.d.ts +2 -0
  136. package/dist/guide/knowledge/topics/agents.js +58 -0
  137. package/dist/guide/knowledge/topics/bestPractices.d.ts +2 -0
  138. package/dist/guide/knowledge/topics/bestPractices.js +67 -0
  139. package/dist/guide/knowledge/topics/connections.d.ts +2 -0
  140. package/dist/guide/knowledge/topics/connections.js +66 -0
  141. package/dist/guide/knowledge/topics/consoleMap.d.ts +2 -0
  142. package/dist/guide/knowledge/topics/consoleMap.js +55 -0
  143. package/dist/guide/knowledge/topics/evals.d.ts +2 -0
  144. package/dist/guide/knowledge/topics/evals.js +72 -0
  145. package/dist/guide/knowledge/topics/limitations.d.ts +2 -0
  146. package/dist/guide/knowledge/topics/limitations.js +84 -0
  147. package/dist/guide/knowledge/topics/mcpSkillsBrowser.d.ts +2 -0
  148. package/dist/guide/knowledge/topics/mcpSkillsBrowser.js +57 -0
  149. package/dist/guide/knowledge/topics/memories.d.ts +2 -0
  150. package/dist/guide/knowledge/topics/memories.js +63 -0
  151. package/dist/guide/knowledge/topics/modelsAndProviders.d.ts +2 -0
  152. package/dist/guide/knowledge/topics/modelsAndProviders.js +56 -0
  153. package/dist/guide/knowledge/topics/overview.d.ts +2 -0
  154. package/dist/guide/knowledge/topics/overview.js +56 -0
  155. package/dist/guide/knowledge/topics/pipelines.d.ts +2 -0
  156. package/dist/guide/knowledge/topics/pipelines.js +93 -0
  157. package/dist/guide/knowledge/topics/stages.d.ts +2 -0
  158. package/dist/guide/knowledge/topics/stages.js +54 -0
  159. package/dist/guide/knowledge/topics/tasks.d.ts +2 -0
  160. package/dist/guide/knowledge/topics/tasks.js +52 -0
  161. package/dist/guide/knowledge/topics/triggers.d.ts +2 -0
  162. package/dist/guide/knowledge/topics/triggers.js +68 -0
  163. package/dist/guide/knowledge/topics/troubleshooting.d.ts +2 -0
  164. package/dist/guide/knowledge/topics/troubleshooting.js +85 -0
  165. package/dist/guide/knowledgeTools.d.ts +2 -0
  166. package/dist/guide/knowledgeTools.js +56 -0
  167. package/dist/guide/showUi.d.ts +6 -0
  168. package/dist/guide/showUi.js +546 -0
  169. package/dist/guide/threadStore.d.ts +48 -0
  170. package/dist/guide/threadStore.js +189 -0
  171. package/dist/guide/writeRoot.d.ts +11 -0
  172. package/dist/guide/writeRoot.js +70 -0
  173. package/dist/hosted/translate.js +2 -0
  174. package/dist/mcp/agentTools.js +12 -0
  175. package/dist/mcp/catalogTools.js +18 -20
  176. package/dist/mcp/catalogWriteTools.d.ts +8 -0
  177. package/dist/mcp/catalogWriteTools.js +225 -0
  178. package/dist/mcp/connectionTools.d.ts +3 -0
  179. package/dist/mcp/connectionTools.js +85 -0
  180. package/dist/mcp/controlTools.js +34 -1
  181. package/dist/mcp/deps.d.ts +9 -0
  182. package/dist/mcp/evalTools.js +723 -21
  183. package/dist/mcp/memoryHostTools.d.ts +3 -0
  184. package/dist/mcp/memoryHostTools.js +72 -0
  185. package/dist/mcp/pipelineAuthorTools.js +14 -1
  186. package/dist/mcp/server.js +5 -0
  187. package/dist/mcp/tools.js +6 -0
  188. package/dist/mcp/triggerTools.js +77 -31
  189. package/dist/operatorAgent/evalGate.d.ts +22 -0
  190. package/dist/operatorAgent/evalGate.js +77 -0
  191. package/dist/operatorAgent/fakeHost.js +10 -2
  192. package/dist/operatorAgent/host.js +18 -2
  193. package/dist/operatorAgent/index.d.ts +1 -0
  194. package/dist/operatorAgent/index.js +1 -0
  195. package/dist/operatorAgent/internalAgents.d.ts +11 -0
  196. package/dist/operatorAgent/internalAgents.js +13 -0
  197. package/dist/operatorAgent/piModel.d.ts +5 -1
  198. package/dist/operatorAgent/piModel.js +15 -10
  199. package/dist/operatorAgent/profiles/agentAuthor.js +66 -22
  200. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
  201. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +1 -1
  202. package/dist/operatorAgent/profiles/guideConfirm.d.ts +21 -0
  203. package/dist/operatorAgent/profiles/guideConfirm.js +100 -0
  204. package/dist/operatorAgent/profiles/guideTools.d.ts +7 -0
  205. package/dist/operatorAgent/profiles/guideTools.js +123 -0
  206. package/dist/operatorAgent/profiles/stageflowGuide.d.ts +7 -0
  207. package/dist/operatorAgent/profiles/stageflowGuide.js +133 -0
  208. package/dist/operatorAgent/profiles/workshopAuthor.js +23 -0
  209. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
  210. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +9 -1
  211. package/dist/operatorAgent/profiles/workshopAuthorTools.d.ts +2 -0
  212. package/dist/operatorAgent/profiles/workshopAuthorTools.js +197 -9
  213. package/dist/operatorAgent/types.d.ts +23 -0
  214. package/dist/package-meta.d.ts +1 -1
  215. package/dist/package-meta.js +1 -1
  216. package/dist/runstore/port.d.ts +13 -1
  217. package/dist/runstore/retention.d.ts +2 -1
  218. package/dist/runstore/retention.js +3 -7
  219. package/dist/runstore/runStatusClock.d.ts +17 -0
  220. package/dist/runstore/runStatusClock.js +19 -0
  221. package/dist/runstore/sqlite/SqliteRunStore.d.ts +11 -1
  222. package/dist/runstore/sqlite/SqliteRunStore.js +70 -45
  223. package/dist/runstore/sqlite/calibrationStore.d.ts +10 -0
  224. package/dist/runstore/sqlite/calibrationStore.js +106 -0
  225. package/dist/runstore/sqlite/evalStore.d.ts +5 -0
  226. package/dist/runstore/sqlite/evalStore.js +7 -0
  227. package/dist/runstore/sqlite/migrations/018-eval-grader-labels.d.ts +11 -0
  228. package/dist/runstore/sqlite/migrations/018-eval-grader-labels.js +43 -0
  229. package/dist/runstore/sqlite/migrations/index.js +2 -0
  230. package/dist/runstore/workspaceLayout.js +6 -1
  231. package/dist/runtime/artifactDiskInspect.d.ts +14 -0
  232. package/dist/runtime/artifactDiskInspect.js +52 -0
  233. package/dist/runtime/artifactPath.d.ts +24 -0
  234. package/dist/runtime/artifactPath.js +84 -0
  235. package/dist/runtime/completionCheckRunner.js +15 -43
  236. package/dist/runtime/githubPollSource.d.ts +22 -4
  237. package/dist/runtime/githubPollSource.js +93 -8
  238. package/dist/runtime/priorArtifactGrants.d.ts +8 -0
  239. package/dist/runtime/priorArtifactGrants.js +105 -0
  240. package/dist/runtime/runActionEligibility.d.ts +46 -0
  241. package/dist/runtime/runActionEligibility.js +123 -0
  242. package/dist/runtime/runManager.d.ts +4 -1
  243. package/dist/runtime/runManager.js +52 -10
  244. package/dist/runtime/runRetryCoordinator.d.ts +2 -14
  245. package/dist/runtime/runRetryCoordinator.js +3 -67
  246. package/dist/runtime/sandboxContainer.d.ts +7 -0
  247. package/dist/runtime/sandboxContainer.js +48 -6
  248. package/dist/runtime/sandboxDesk.d.ts +2 -1
  249. package/dist/runtime/sandboxDesk.js +11 -12
  250. package/dist/runtime/stageEnvironment.js +1 -0
  251. package/dist/runtime/stageRoots.d.ts +12 -0
  252. package/dist/runtime/stageSandbox.d.ts +2 -1
  253. package/dist/runtime/stageSandbox.js +18 -2
  254. package/dist/runtime/stageWorker.js +20 -0
  255. package/dist/server/agentAuthorRoutes.js +12 -3
  256. package/dist/server/bootstrap.d.ts +5 -0
  257. package/dist/server/bootstrap.js +14 -1
  258. package/dist/server/evalRoutes.js +251 -12
  259. package/dist/server/guideRoutes.d.ts +5 -0
  260. package/dist/server/guideRoutes.js +166 -0
  261. package/dist/server/http.d.ts +10 -0
  262. package/dist/server/http.js +35 -1
  263. package/dist/server/internalAgentRoutes.d.ts +6 -0
  264. package/dist/server/internalAgentRoutes.js +12 -0
  265. package/dist/server/mcpHost.js +2 -0
  266. package/dist/toolkit/meta.d.ts +1 -1
  267. package/dist/toolkit/meta.js +41 -1
  268. package/dist/toolkit/profiles.d.ts +5 -0
  269. package/dist/toolkit/profiles.js +12 -0
  270. package/dist/tools/askOperator.js +2 -1
  271. package/dist/tools/emitStageEnvelope.d.ts +17 -16
  272. package/dist/tools/emitStageEnvelope.js +46 -32
  273. package/dist/tools/stageToolNames.d.ts +4 -0
  274. package/dist/tools/stageToolNames.js +4 -0
  275. package/dist/tools/workingState.d.ts +40 -0
  276. package/dist/tools/workingState.js +108 -0
  277. package/dist/tools/writeStageArtifact.d.ts +0 -9
  278. package/dist/tools/writeStageArtifact.js +2 -1
  279. package/dist/ui/assets/{MarkdownText-BTrOqu5J.js → MarkdownText-BS2BBbCx.js} +2 -2
  280. package/dist/ui/assets/adapters-DPD-cAsy.js +17 -0
  281. package/dist/ui/assets/chat-BD1-VTT6.js +2 -0
  282. package/dist/ui/assets/chat-DXnQv9st.css +2 -0
  283. package/dist/ui/assets/main-AX5XNLtX.js +278 -0
  284. package/dist/ui/assets/{main-CHlmTNJe.css → main-CFX0_n8J.css} +1 -1
  285. package/dist/ui/chat.html +3 -4
  286. package/dist/ui/index.html +3 -4
  287. package/dist/workshop/chatTurn.js +1 -1
  288. package/dist/workshop/workshopPin.d.ts +3 -3
  289. package/dist/workshop/workshopPin.js +16 -15
  290. package/examples/compaction-stress/README.md +30 -0
  291. package/examples/compaction-stress/check-run.mjs +53 -0
  292. package/examples/compaction-stress/collect-evidence.yaml +60 -0
  293. package/examples/compaction-stress/compaction-stress.pipeline.yaml +141 -0
  294. package/examples/compaction-stress/compaction-stress.task.yaml +15 -0
  295. package/examples/compaction-stress/generate-evidence.mjs +110 -0
  296. package/examples/compaction-stress/root-cause.yaml +26 -0
  297. package/examples/compaction-stress/stageflow.yaml +9 -0
  298. package/examples/compaction-stress/write-postmortem.yaml +40 -0
  299. package/examples/evals/README.md +18 -0
  300. package/examples/evals/guide-behavior.dataset.jsonl +15 -0
  301. package/examples/evals/guide-behavior.eval.yaml +35 -0
  302. package/examples/evals/guide-depth.dataset.jsonl +35 -0
  303. package/examples/evals/guide-depth.eval.yaml +169 -0
  304. package/examples/memory/evals/guide-behavior.dataset.jsonl +15 -0
  305. package/examples/memory/evals/guide-behavior.eval.yaml +35 -0
  306. package/examples/memory/evals/guide-depth.dataset.jsonl +35 -0
  307. package/examples/memory/evals/guide-depth.eval.yaml +169 -0
  308. package/package.json +1 -1
  309. package/skills/stageflow/references/control-surface.md +1 -1
  310. package/skills/stageflow/references/evals.md +1 -0
  311. package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +0 -9
  312. package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +0 -6
  313. package/dist/ui/assets/chat-Df_wCpak.js +0 -5
  314. package/dist/ui/assets/chat-P7KQznHm.css +0 -2
  315. package/dist/ui/assets/main-CvVfxmMd.js +0 -271
@@ -0,0 +1,250 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createOperatorEvalGate } from "../../operatorAgent/evalGate.js";
3
+ import { buildConversationSubject } from "./conversationSubject.js";
4
+ import { createDriver, DriverScenarioError } from "./userDriver.js";
5
+ const ZERO_USAGE = {
6
+ cost_usd: 0,
7
+ agent_cost_usd: 0,
8
+ simulator_cost_usd: 0,
9
+ tokens_in: 0,
10
+ tokens_out: 0,
11
+ simulator_tokens_in: 0,
12
+ simulator_tokens_out: 0,
13
+ simulator_estimated: false,
14
+ agent_calls: 0,
15
+ simulator_calls: 0,
16
+ };
17
+ function failure(code, message, extra = {}) {
18
+ return { ok: false, code, message, ...extra };
19
+ }
20
+ function parseMocks(value) {
21
+ const out = {};
22
+ if (typeof value !== "object" || value === null || Array.isArray(value))
23
+ return out;
24
+ for (const [tool, mock] of Object.entries(value)) {
25
+ if (typeof mock !== "object" || mock === null)
26
+ continue;
27
+ const { result, error } = mock;
28
+ if (typeof error === "string")
29
+ out[tool] = { error };
30
+ else if ("result" in mock)
31
+ out[tool] = { result };
32
+ }
33
+ return out;
34
+ }
35
+ function detailFrom(sessionId, profileId, messages) {
36
+ const now = new Date().toISOString();
37
+ return {
38
+ session: {
39
+ id: sessionId,
40
+ agentId: profileId,
41
+ revision: 0,
42
+ backend: "pi",
43
+ status: "closed",
44
+ source: "eval",
45
+ sourceRef: null,
46
+ threadKey: null,
47
+ title: null,
48
+ createdAt: now,
49
+ updatedAt: now,
50
+ },
51
+ messages,
52
+ turns: [],
53
+ spans: [],
54
+ approvals: [],
55
+ pendingApproval: null,
56
+ evalScores: [],
57
+ deliveries: [],
58
+ };
59
+ }
60
+ export function createInternalAgentCaseRunner(deps) {
61
+ const clock = deps.now ?? Date.now;
62
+ const states = new Map();
63
+ const stateFor = (id) => {
64
+ let state = states.get(id);
65
+ if (!state) {
66
+ state = { cancelled: false, abort: new AbortController() };
67
+ states.set(id, state);
68
+ }
69
+ return state;
70
+ };
71
+ async function runCase(plan, req) {
72
+ const state = stateFor(req.evalRunId);
73
+ if (state.cancelled)
74
+ return failure("EVAL_CANCELLED", "Eval run was cancelled");
75
+ if (plan.target.kind !== "internal_agent" || !plan.conversation) {
76
+ return failure("EVAL_ENGINE_ERROR", "Not an internal-agent plan");
77
+ }
78
+ if (plan.conversation.tools === "real") {
79
+ return failure("EVAL_ENGINE_ERROR", "Internal agents cannot run an eval with real tools");
80
+ }
81
+ const host = deps.host();
82
+ if (!host)
83
+ return failure("EVAL_ENGINE_ERROR", "Internal agents are unavailable on this host");
84
+ const planCase = plan.cases.find((c) => c.id === req.caseId);
85
+ if (!planCase)
86
+ return failure("EVAL_ENGINE_ERROR", `Case "${req.caseId}" is not in the plan`);
87
+ const scenario = planCase.input;
88
+ const mode = scenario.mode === "script" || scenario.mode === "simulated" ? scenario.mode : plan.conversation.mode;
89
+ let driver;
90
+ try {
91
+ driver = createDriver(mode, scenario, {});
92
+ }
93
+ catch (err) {
94
+ return failure("EVAL_ENGINE_ERROR", err instanceof DriverScenarioError ? err.message : "Scenario could not be prepared");
95
+ }
96
+ if (mode === "simulated") {
97
+ return failure("EVAL_ENGINE_ERROR", "A simulated internal-agent case needs a user model, which this runner does not call");
98
+ }
99
+ const conversation = plan.conversation;
100
+ const sessionId = `op_eval_${randomUUID()}`;
101
+ let turn = 0;
102
+ const gate = createOperatorEvalGate({
103
+ policy: { tools: conversation.tools, mocks: parseMocks(scenario.tool_mocks) },
104
+ turn: () => turn,
105
+ });
106
+ let session;
107
+ try {
108
+ session = host.openEvalSession({
109
+ profileId: plan.target.profileId,
110
+ context: { kind: "stageflow_guide" },
111
+ gate,
112
+ });
113
+ }
114
+ catch (err) {
115
+ return failure("EVAL_ENGINE_ERROR", err instanceof Error ? err.message : "The eval session could not be opened");
116
+ }
117
+ const startedAt = clock();
118
+ const messages = [];
119
+ const transcript = [];
120
+ let end = {
121
+ kind: "done",
122
+ stopReason: "max_turns",
123
+ };
124
+ const caseCtl = new AbortController();
125
+ let wallExpired = false;
126
+ let turnTimedOut = false;
127
+ const stopCase = () => {
128
+ caseCtl.abort();
129
+ void session.abort().catch(() => undefined);
130
+ };
131
+ const onRunAbort = () => stopCase();
132
+ state.abort.signal.addEventListener("abort", onRunAbort, { once: true });
133
+ const wallMs = conversation.maxTurns * conversation.turnTimeoutMs + 60_000;
134
+ const wall = setTimeout(() => {
135
+ wallExpired = true;
136
+ stopCase();
137
+ }, wallMs);
138
+ const stopped = new Promise((resolve) => {
139
+ if (caseCtl.signal.aborted)
140
+ resolve("stopped");
141
+ else
142
+ caseCtl.signal.addEventListener("abort", () => resolve("stopped"), { once: true });
143
+ });
144
+ try {
145
+ for (turn = 1; turn <= conversation.maxTurns; turn += 1) {
146
+ if (state.cancelled) {
147
+ end = { kind: "cancelled" };
148
+ break;
149
+ }
150
+ if (wallExpired || turnTimedOut || caseCtl.signal.aborted) {
151
+ end = {
152
+ kind: "failed",
153
+ code: "EVAL_AGENT_TIMEOUT",
154
+ message: turnTimedOut ? `Turn ${turn} exceeded ${conversation.turnTimeoutMs} ms` : "Conversation exceeded its time limit",
155
+ stopReason: "timeout",
156
+ };
157
+ break;
158
+ }
159
+ const step = await driver.next({ turn, transcript, signal: caseCtl.signal });
160
+ if (step.kind === "end") {
161
+ end = { kind: "done", stopReason: step.reason };
162
+ break;
163
+ }
164
+ const now = new Date().toISOString();
165
+ messages.push({ id: randomUUID(), sessionId, seq: messages.length, turnId: String(turn), role: "user", author: null, text: step.text, createdAt: now });
166
+ const turnTimer = setTimeout(() => {
167
+ turnTimedOut = true;
168
+ stopCase();
169
+ }, conversation.turnTimeoutMs);
170
+ const sent = session.send(step.text, plan.modelOverride ? { modelId: plan.modelOverride } : {}).then((events) => ({ ok: true, events }), (err) => ({ ok: false, err }));
171
+ const outcome = await Promise.race([sent, stopped]);
172
+ clearTimeout(turnTimer);
173
+ if (state.cancelled) {
174
+ end = { kind: "cancelled" };
175
+ break;
176
+ }
177
+ if (outcome === "stopped" || wallExpired || turnTimedOut) {
178
+ end = {
179
+ kind: "failed",
180
+ code: "EVAL_AGENT_TIMEOUT",
181
+ message: turnTimedOut ? `Turn ${turn} exceeded ${conversation.turnTimeoutMs} ms` : "Conversation exceeded its time limit",
182
+ stopReason: "timeout",
183
+ };
184
+ break;
185
+ }
186
+ if (!outcome.ok) {
187
+ end = { kind: "failed", code: "EVAL_ENGINE_ERROR", message: outcome.err instanceof Error ? outcome.err.message : "Conversation failed", stopReason: "error" };
188
+ break;
189
+ }
190
+ const text = outcome.events
191
+ .filter((event) => event.type === "message" && event.role === "assistant")
192
+ .map((event) => (event.type === "message" ? event.text : ""))
193
+ .filter(Boolean)
194
+ .join("\n\n");
195
+ if (!text && gate.records().every((call) => call.turn !== turn)) {
196
+ end = { kind: "failed", code: "EVAL_AGENT_EMPTY_REPLY", message: "The agent returned an empty reply", stopReason: "error" };
197
+ break;
198
+ }
199
+ messages.push({ id: randomUUID(), sessionId, seq: messages.length, turnId: String(turn), role: "assistant", author: null, text, createdAt: now });
200
+ transcript.push({ role: "user", text: step.text }, { role: "assistant", text });
201
+ if (step.endAfter !== undefined) {
202
+ end = { kind: "done", stopReason: step.endAfter };
203
+ break;
204
+ }
205
+ if (turn === conversation.maxTurns)
206
+ end = { kind: "done", stopReason: "max_turns" };
207
+ }
208
+ }
209
+ catch (err) {
210
+ end = state.cancelled
211
+ ? { kind: "cancelled" }
212
+ : wallExpired || turnTimedOut
213
+ ? { kind: "failed", code: "EVAL_AGENT_TIMEOUT", message: turnTimedOut ? `Turn ${turn} exceeded ${conversation.turnTimeoutMs} ms` : "Conversation exceeded its time limit", stopReason: "timeout" }
214
+ : { kind: "failed", code: "EVAL_ENGINE_ERROR", message: err instanceof Error ? err.message : "Conversation failed", stopReason: "error" };
215
+ }
216
+ finally {
217
+ clearTimeout(wall);
218
+ state.abort.signal.removeEventListener("abort", onRunAbort);
219
+ session.close();
220
+ }
221
+ const usage = { ...ZERO_USAGE, agent_calls: messages.filter((m) => m.role === "assistant").length };
222
+ const subject = (stopReason, failed) => buildConversationSubject({
223
+ detail: detailFrom(sessionId, plan.target.kind === "internal_agent" ? plan.target.profileId : "", messages),
224
+ calls: gate.records(),
225
+ stopReason,
226
+ usage,
227
+ turns: messages.filter((m) => m.role === "user").length,
228
+ ...(failed !== undefined ? { failure: failed } : {}),
229
+ });
230
+ const latencyMs = Math.max(0, clock() - startedAt);
231
+ if (end.kind === "done") {
232
+ return { ok: true, subjectRef: sessionId, output: subject(end.stopReason), latencyMs, costUsd: 0, tokensIn: 0, tokensOut: 0 };
233
+ }
234
+ if (end.kind === "cancelled")
235
+ return failure("EVAL_CANCELLED", "Eval run was cancelled", { subjectRef: sessionId, latencyMs, costUsd: 0 });
236
+ return failure(end.code, end.message, { subjectRef: sessionId, output: subject(end.stopReason, end.message), latencyMs, costUsd: 0 });
237
+ }
238
+ return {
239
+ runCase,
240
+ async cancelAll(evalRunId) {
241
+ const state = states.get(evalRunId);
242
+ if (!state)
243
+ return;
244
+ state.cancelled = true;
245
+ state.abort.abort();
246
+ },
247
+ spend: () => 0,
248
+ addSpend: () => undefined,
249
+ };
250
+ }
@@ -74,6 +74,14 @@ export type ConversationSettings = {
74
74
  user_model_from_judge: boolean;
75
75
  simulator_prompt_version: number;
76
76
  };
77
+ export type InternalAgentSnapshotTarget = {
78
+ kind: "internal_agent";
79
+ profile_id: string;
80
+ title: string;
81
+ playbook_hash: string;
82
+ tools: string[];
83
+ model: string;
84
+ };
77
85
  export type ConfigSnapshot = {
78
86
  snapshot_version: number;
79
87
  eval: {
@@ -83,7 +91,7 @@ export type ConfigSnapshot = {
83
91
  definition: Record<string, unknown>;
84
92
  };
85
93
  overrides: SnapshotOverrides;
86
- target: StageSnapshotTarget | AgentSnapshotTarget;
94
+ target: StageSnapshotTarget | AgentSnapshotTarget | InternalAgentSnapshotTarget;
87
95
  dataset: {
88
96
  path: string;
89
97
  hash: string;
@@ -169,7 +177,7 @@ export type SnapshotInput = {
169
177
  evalHash: string;
170
178
  evalDefinition: Record<string, unknown>;
171
179
  overrides: SnapshotOverrides;
172
- target: Omit<StageSnapshotTarget, "kind"> | Omit<AgentSnapshotTarget, "kind">;
180
+ target: Omit<StageSnapshotTarget, "kind"> | Omit<AgentSnapshotTarget, "kind"> | Omit<InternalAgentSnapshotTarget, "kind">;
173
181
  dataset: ConfigSnapshot["dataset"];
174
182
  graders: SnapshotGrader[];
175
183
  settings: ConfigSnapshot["settings"];
@@ -40,7 +40,8 @@ export function computeFingerprint(components) {
40
40
  return sha256Hex(canonicalJson(payload)).slice(0, 8);
41
41
  }
42
42
  export function buildSnapshot(input) {
43
- const agentTarget = "agent_id" in input.target ? input.target : null;
43
+ const internalTarget = "profile_id" in input.target ? input.target : null;
44
+ const agentTarget = !internalTarget && "agent_id" in input.target ? input.target : null;
44
45
  return {
45
46
  snapshot_version: CONFIG_SNAPSHOT_VERSION,
46
47
  eval: {
@@ -50,14 +51,18 @@ export function buildSnapshot(input) {
50
51
  definition: input.evalDefinition,
51
52
  },
52
53
  overrides: input.overrides,
53
- target: agentTarget ? { kind: "agent", ...agentTarget } : { kind: "stage", ...input.target },
54
+ target: internalTarget
55
+ ? { kind: "internal_agent", ...internalTarget }
56
+ : agentTarget
57
+ ? { kind: "agent", ...agentTarget }
58
+ : { kind: "stage", ...input.target },
54
59
  dataset: input.dataset,
55
60
  graders: input.graders,
56
61
  settings: input.settings,
57
62
  engine: {
58
63
  name: input.engine.name,
59
64
  version: input.engine.version,
60
- rubric_prompt_version: agentTarget ? CONVERSATION_RUBRIC_PROMPT_VERSION : RUBRIC_PROMPT_VERSION,
65
+ rubric_prompt_version: agentTarget || internalTarget ? CONVERSATION_RUBRIC_PROMPT_VERSION : RUBRIC_PROMPT_VERSION,
61
66
  node_version: input.engine.nodeVersion,
62
67
  },
63
68
  stageflow_version: input.stageflowVersion,
@@ -93,3 +93,43 @@ export declare function clusteredCi(center: number, perCase: number[], lo: numbe
93
93
  export declare function buildCaseStats(results: ResultRecord[]): CaseStat[];
94
94
  export declare function computeByGrader(graders: GraderRef[], grades: GradeRecord[], previousRun: PreviousRunLite | null | undefined): GraderStat[];
95
95
  export declare function computeStats(input: ComputeStatsInput): Stats;
96
+ export declare const CALIBRATION_MIN_LABELS = 20;
97
+ export declare const CALIBRATION_MINORITY_SHARE = 0.2;
98
+ export interface CalibrationSample {
99
+ caseId: string;
100
+ repeatIndex: number;
101
+ label: boolean;
102
+ judge: boolean;
103
+ }
104
+ export interface CalibrationStats {
105
+ n: number;
106
+ agreement: number | null;
107
+ cohen_kappa: number | null;
108
+ tpr: number | null;
109
+ tnr: number | null;
110
+ confusion: {
111
+ tp: number;
112
+ fp: number;
113
+ tn: number;
114
+ fn: number;
115
+ };
116
+ warnings: Array<"low_n" | "imbalanced" | "no_variance">;
117
+ }
118
+ /** Judge pass is the positive class; the human label is the truth. */
119
+ export declare function computeCalibration(samples: CalibrationSample[]): CalibrationStats;
120
+ /** A strict majority of valid votes passes; a tie fails. */
121
+ export declare function majorityPasses(passes: number, valid: number): boolean;
122
+ export declare function meanOf(values: readonly number[]): number;
123
+ /** Deterministic 70/30 split by a stable hash of the case id, so every repeat of a case lands on the same side. */
124
+ export declare function calibrationSplitOf(caseId: string): "dev" | "held_out";
125
+ export interface StabilityStats {
126
+ /** Judge calls per output. */
127
+ k: number;
128
+ /** Outputs with at least two valid votes. */
129
+ n: number;
130
+ /** Outputs whose valid votes were not all the same verdict. */
131
+ unstable: number;
132
+ flip_rate: number | null;
133
+ }
134
+ /** How often the judge changes its mind on the same output: the share of outputs with a split vote. */
135
+ export declare function computeStability(voteSets: boolean[][], k: number): StabilityStats;
@@ -218,3 +218,72 @@ export function computeStats(input) {
218
218
  ...(hasAgentData ? { mean_turns: mean(turnValues), mean_tool_calls: mean(toolCallValues) } : {}),
219
219
  };
220
220
  }
221
+ export const CALIBRATION_MIN_LABELS = 20;
222
+ export const CALIBRATION_MINORITY_SHARE = 0.2;
223
+ /** Judge pass is the positive class; the human label is the truth. */
224
+ export function computeCalibration(samples) {
225
+ let tp = 0;
226
+ let fp = 0;
227
+ let tn = 0;
228
+ let fn = 0;
229
+ for (const s of samples) {
230
+ if (s.judge && s.label)
231
+ tp++;
232
+ else if (s.judge && !s.label)
233
+ fp++;
234
+ else if (!s.judge && !s.label)
235
+ tn++;
236
+ else
237
+ fn++;
238
+ }
239
+ const n = tp + fp + tn + fn;
240
+ const warnings = [];
241
+ if (n < CALIBRATION_MIN_LABELS)
242
+ warnings.push("low_n");
243
+ const labelPass = tp + fn;
244
+ const labelFail = fp + tn;
245
+ if (n > 0 && Math.min(labelPass, labelFail) / n < CALIBRATION_MINORITY_SHARE)
246
+ warnings.push("imbalanced");
247
+ const agreement = n > 0 ? (tp + tn) / n : null;
248
+ const judgePass = tp + fp;
249
+ const judgeFail = fn + tn;
250
+ const expected = n > 0 ? (judgePass * labelPass + judgeFail * labelFail) / (n * n) : 0;
251
+ let kappa = null;
252
+ if (n > 0 && agreement !== null) {
253
+ if (expected >= 1)
254
+ warnings.push("no_variance");
255
+ else
256
+ kappa = (agreement - expected) / (1 - expected);
257
+ }
258
+ return {
259
+ n,
260
+ agreement,
261
+ cohen_kappa: kappa,
262
+ tpr: labelPass > 0 ? tp / labelPass : null,
263
+ tnr: labelFail > 0 ? tn / labelFail : null,
264
+ confusion: { tp, fp, tn, fn },
265
+ warnings,
266
+ };
267
+ }
268
+ /** A strict majority of valid votes passes; a tie fails. */
269
+ export function majorityPasses(passes, valid) {
270
+ return passes * 2 > valid;
271
+ }
272
+ export function meanOf(values) {
273
+ return values.reduce((total, v) => total + v, 0) / values.length;
274
+ }
275
+ /** Deterministic 70/30 split by a stable hash of the case id, so every repeat of a case lands on the same side. */
276
+ export function calibrationSplitOf(caseId) {
277
+ let h = 2166136261;
278
+ for (let i = 0; i < caseId.length; i++) {
279
+ h ^= caseId.charCodeAt(i);
280
+ h = Math.imul(h, 16777619) >>> 0;
281
+ }
282
+ return h % 10 < 7 ? "dev" : "held_out";
283
+ }
284
+ /** How often the judge changes its mind on the same output: the share of outputs with a split vote. */
285
+ export function computeStability(voteSets, k) {
286
+ const counted = voteSets.filter((votes) => votes.length >= 2);
287
+ const unstable = counted.filter((votes) => votes.some((v) => v !== votes[0])).length;
288
+ return { k, n: counted.length, unstable, flip_rate: counted.length > 0 ? unstable / counted.length : null };
289
+ }
@@ -205,3 +205,45 @@ export type EvalGraderVersionInput = {
205
205
  graderId: string;
206
206
  graderHash: string;
207
207
  };
208
+ export type EvalGraderLabelRow = {
209
+ evalRunId: string;
210
+ caseId: string;
211
+ repeatIndex: number;
212
+ graderId: string;
213
+ label: EvalReviewLabel;
214
+ note: string | null;
215
+ createdAt: string;
216
+ updatedAt: string;
217
+ };
218
+ export type EvalGraderLabelPatch = {
219
+ label: EvalReviewLabel | null;
220
+ note?: string | null;
221
+ };
222
+ export type EvalCalibrationSplit = "all" | "dev" | "held_out";
223
+ export type EvalCalibrationDisagreement = {
224
+ /** The run the labeled result came from; absent on calibrations stored before this field existed. */
225
+ evalRunId?: string;
226
+ caseId: string;
227
+ repeatIndex: number;
228
+ label: EvalReviewLabel;
229
+ judge: EvalReviewLabel;
230
+ reason: string | null;
231
+ };
232
+ export type EvalCalibrationInput = {
233
+ projectRoot: string;
234
+ evalId: string;
235
+ graderId: string;
236
+ graderHash: string;
237
+ judge: string;
238
+ promptVersion: number;
239
+ split: EvalCalibrationSplit;
240
+ candidate: boolean;
241
+ stats: EvalJson;
242
+ disagreements: EvalCalibrationDisagreement[];
243
+ costUsd: number;
244
+ };
245
+ export type EvalCalibrationRow = Omit<EvalCalibrationInput, "stats"> & {
246
+ id: string;
247
+ stats: EvalJson;
248
+ createdAt: string;
249
+ };
@@ -24,7 +24,13 @@ export type AgentEvalTarget = {
24
24
  kind: "agent";
25
25
  agent: string;
26
26
  };
27
- export type EvalTarget = StageEvalTarget | AgentEvalTarget;
27
+ export type InternalAgentEvalTarget = {
28
+ kind: "internal_agent";
29
+ profile: string;
30
+ };
31
+ export type EvalTarget = StageEvalTarget | AgentEvalTarget | InternalAgentEvalTarget;
32
+ /** A conversation eval: a project agent or a built-in operator agent. */
33
+ export declare function isConversationTarget(kind: string): boolean;
28
34
  export type ConversationMode = "script" | "simulated";
29
35
  export type ConversationTools = "real" | "read_only" | "mocked";
30
36
  export type ConversationApprovals = "deny" | "auto_approve_read" | "record_only";
@@ -51,9 +57,18 @@ export type EvalTask = {
51
57
  context?: string;
52
58
  constraints?: string;
53
59
  };
60
+ export type JudgeSettings = {
61
+ /** Sampling temperature for the judge call; unset leaves the provider default. */
62
+ temperature?: number;
63
+ /** The judge writes its reason before the verdict. */
64
+ reasoning?: boolean;
65
+ /** Judge calls per grade (odd, 1 to 5); the majority verdict wins. */
66
+ votes?: number;
67
+ };
54
68
  export type EvalDefaults = {
55
69
  repeats?: number;
56
70
  judge?: string;
71
+ judge_settings?: JudgeSettings;
57
72
  pass_threshold?: number;
58
73
  max_cost_usd?: number;
59
74
  concurrency?: number;
@@ -97,6 +112,7 @@ export type GraderConfig = (GraderCommon & {
97
112
  scale?: "pass_fail" | "1_to_5";
98
113
  pass_at?: number;
99
114
  judge?: string;
115
+ judge_settings?: JudgeSettings;
100
116
  examples?: RubricExample[];
101
117
  }) | (GraderCommon & {
102
118
  type: "code";
@@ -116,6 +132,7 @@ export type GraderConfig = (GraderCommon & {
116
132
  }) | (GraderCommon & {
117
133
  type: "goal_reached";
118
134
  judge?: string;
135
+ judge_settings?: JudgeSettings;
119
136
  guidance?: string;
120
137
  });
121
138
  export type EvalFile = {
@@ -143,7 +160,11 @@ export type AgentTargetRef = {
143
160
  agentId: string;
144
161
  revision?: number;
145
162
  };
146
- export type TargetRef = StageTargetRef | AgentTargetRef;
163
+ export type InternalAgentTargetRef = {
164
+ kind: "internal_agent";
165
+ profileId: string;
166
+ };
167
+ export type TargetRef = StageTargetRef | AgentTargetRef | InternalAgentTargetRef;
147
168
  type ResolvedGraderCommon = {
148
169
  id: string;
149
170
  weight: number;
@@ -168,6 +189,7 @@ export type ResolvedGrader = (ResolvedGraderCommon & {
168
189
  scale: "pass_fail" | "1_to_5";
169
190
  passAt: number;
170
191
  judge: string;
192
+ judgeSettings?: JudgeSettings;
171
193
  examples: RubricExample[];
172
194
  }) | (ResolvedGraderCommon & {
173
195
  type: "code";
@@ -187,6 +209,7 @@ export type ResolvedGrader = (ResolvedGraderCommon & {
187
209
  }) | (ResolvedGraderCommon & {
188
210
  type: "goal_reached";
189
211
  judge: string;
212
+ judgeSettings?: JudgeSettings;
190
213
  guidance?: string;
191
214
  });
192
215
  export type PlanCase = {
@@ -1 +1,4 @@
1
- export {};
1
+ /** A conversation eval: a project agent or a built-in operator agent. */
2
+ export function isConversationTarget(kind) {
3
+ return kind === "agent" || kind === "internal_agent";
4
+ }
@@ -0,0 +1,10 @@
1
+ import type { NormalizedGrade } from "./types.js";
2
+ export declare const JUDGE_ERROR_CODE = "EVAL_JUDGE_ERROR";
3
+ /**
4
+ * One grade from several votes: the majority verdict for pass/fail (a tie fails), the mean raw score for 1 to 5.
5
+ * Errored votes are left out; if every vote errored the grade is an error. Tokens and cost add up over all votes.
6
+ */
7
+ export declare function combineVotes(grader: {
8
+ scale: "pass_fail" | "1_to_5";
9
+ passAt: number;
10
+ }, votes: readonly NormalizedGrade[]): NormalizedGrade;
@@ -0,0 +1,60 @@
1
+ import { normalizeOneToFive } from "./scoring.js";
2
+ import { majorityPasses, meanOf } from "./stats.js";
3
+ export const JUDGE_ERROR_CODE = "EVAL_JUDGE_ERROR";
4
+ /**
5
+ * One grade from several votes: the majority verdict for pass/fail (a tie fails), the mean raw score for 1 to 5.
6
+ * Errored votes are left out; if every vote errored the grade is an error. Tokens and cost add up over all votes.
7
+ */
8
+ export function combineVotes(grader, votes) {
9
+ const first = votes[0];
10
+ if (votes.length === 1)
11
+ return first;
12
+ const valid = votes.filter((g) => g.status === "graded" && g.passed !== undefined);
13
+ const sum = (pick) => votes.reduce((total, g) => total + (pick(g) ?? 0), 0);
14
+ const totals = {
15
+ judgeTokensIn: sum((g) => g.judgeTokensIn),
16
+ judgeTokensOut: sum((g) => g.judgeTokensOut),
17
+ judgeCostUsd: sum((g) => g.judgeCostUsd),
18
+ };
19
+ const priceUnknown = votes.some((g) => g.details?.judgeCostUnknown);
20
+ const record = votes.map((g) => ({
21
+ status: g.status,
22
+ ...(g.passed !== undefined ? { passed: g.passed } : {}),
23
+ ...(g.rawValue !== undefined ? { raw_value: g.rawValue } : {}),
24
+ ...(g.reason !== undefined ? { reason: g.reason } : {}),
25
+ }));
26
+ const baseDetails = {
27
+ ...(first.details ?? {}),
28
+ ...(priceUnknown ? { judgeCostUnknown: true } : {}),
29
+ votes: record,
30
+ };
31
+ if (valid.length === 0) {
32
+ return { ...first, ...totals, status: "error", errorCode: first.errorCode ?? JUDGE_ERROR_CODE, details: baseDetails };
33
+ }
34
+ let passed;
35
+ let rawValue;
36
+ let score;
37
+ if (grader.scale === "1_to_5") {
38
+ rawValue = meanOf(valid.map((g) => g.rawValue ?? 0));
39
+ score = normalizeOneToFive(rawValue);
40
+ passed = rawValue >= grader.passAt;
41
+ }
42
+ else {
43
+ passed = majorityPasses(valid.filter((g) => g.passed === true).length, valid.length);
44
+ rawValue = passed ? 1 : 0;
45
+ score = rawValue;
46
+ }
47
+ const lead = valid.find((g) => g.passed === passed) ?? valid[0];
48
+ return {
49
+ graderId: first.graderId,
50
+ graderType: first.graderType,
51
+ status: "graded",
52
+ passed,
53
+ score,
54
+ rawValue,
55
+ ...(lead.reason !== undefined ? { reason: lead.reason } : {}),
56
+ ...(first.judgeModel !== undefined ? { judgeModel: first.judgeModel } : {}),
57
+ ...totals,
58
+ details: baseDetails,
59
+ };
60
+ }