@post-print/agent-test 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +34 -6
  4. package/dist/cli.js.map +1 -1
  5. package/dist/compare-scenario.d.ts +10 -0
  6. package/dist/compare-scenario.d.ts.map +1 -1
  7. package/dist/compare-scenario.js +48 -3
  8. package/dist/compare-scenario.js.map +1 -1
  9. package/dist/expect.d.ts +1 -0
  10. package/dist/expect.d.ts.map +1 -1
  11. package/dist/expect.js +24 -0
  12. package/dist/expect.js.map +1 -1
  13. package/dist/html-report.d.ts +4 -1
  14. package/dist/html-report.d.ts.map +1 -1
  15. package/dist/html-report.js +224 -143
  16. package/dist/html-report.js.map +1 -1
  17. package/dist/index.d.ts +3 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/live-isolation.d.ts +2 -0
  22. package/dist/live-isolation.d.ts.map +1 -1
  23. package/dist/live-isolation.js +8 -2
  24. package/dist/live-isolation.js.map +1 -1
  25. package/dist/report-preview.d.ts +24 -0
  26. package/dist/report-preview.d.ts.map +1 -1
  27. package/dist/report-preview.js +46 -2
  28. package/dist/report-preview.js.map +1 -1
  29. package/dist/run-suite.d.ts +10 -0
  30. package/dist/run-suite.d.ts.map +1 -1
  31. package/dist/run-suite.js +146 -126
  32. package/dist/run-suite.js.map +1 -1
  33. package/dist/scenario-finalizer.d.ts +27 -0
  34. package/dist/scenario-finalizer.d.ts.map +1 -0
  35. package/dist/scenario-finalizer.js +39 -0
  36. package/dist/scenario-finalizer.js.map +1 -0
  37. package/dist/scenario-story.d.ts.map +1 -1
  38. package/dist/scenario-story.js +14 -8
  39. package/dist/scenario-story.js.map +1 -1
  40. package/dist/types.d.ts +7 -0
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/validate-suite.d.ts.map +1 -1
  43. package/dist/validate-suite.js +1 -0
  44. package/dist/validate-suite.js.map +1 -1
  45. package/dist/viewer/catalog.d.ts +14 -3
  46. package/dist/viewer/catalog.d.ts.map +1 -1
  47. package/dist/viewer/catalog.js +33 -6
  48. package/dist/viewer/catalog.js.map +1 -1
  49. package/dist/viewer/compare-metrics.d.ts.map +1 -1
  50. package/dist/viewer/compare-metrics.js +8 -9
  51. package/dist/viewer/compare-metrics.js.map +1 -1
  52. package/dist/viewer/events.d.ts +22 -1
  53. package/dist/viewer/events.d.ts.map +1 -1
  54. package/dist/viewer/events.js +1 -0
  55. package/dist/viewer/events.js.map +1 -1
  56. package/dist/viewer/live-runner.d.ts +7 -0
  57. package/dist/viewer/live-runner.d.ts.map +1 -1
  58. package/dist/viewer/live-runner.js +36 -23
  59. package/dist/viewer/live-runner.js.map +1 -1
  60. package/dist/viewer/page.d.ts +14 -1
  61. package/dist/viewer/page.d.ts.map +1 -1
  62. package/dist/viewer/page.js +758 -141
  63. package/dist/viewer/page.js.map +1 -1
  64. package/dist/viewer/run-controller.d.ts +13 -6
  65. package/dist/viewer/run-controller.d.ts.map +1 -1
  66. package/dist/viewer/run-controller.js +246 -74
  67. package/dist/viewer/run-controller.js.map +1 -1
  68. package/dist/viewer/server.d.ts +5 -0
  69. package/dist/viewer/server.d.ts.map +1 -1
  70. package/dist/viewer/server.js +72 -17
  71. package/dist/viewer/server.js.map +1 -1
  72. package/dist/viewer/session-server.d.ts +2 -0
  73. package/dist/viewer/session-server.d.ts.map +1 -0
  74. package/dist/viewer/session-server.js +71 -0
  75. package/dist/viewer/session-server.js.map +1 -0
  76. package/package.json +2 -2
package/dist/run-suite.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { buildHostPrompt, cancelActiveClaudeRun, cancelActiveCursorRun, cancelActiveOpenaiRun, captureWorkingTreeStatus, createSealedWorkspace, defaultSealedOverlayPaths, enrichTrace, filterWorkingTreeLeaks, findWorkingTreeLeak, formatWorkingTreeLeak, getProcessAuthMode, isBuiltinAgentHost, judgeCompareTraces, judgeTrace, loadContext, loadUnifiedDiffPaths, mergeMcpServers, parseScenarioWorkspace, partitionSeedCollateralLeaks, porcelainPathsFromLines, resolveAllowUserSkills, resolveHarnessArtifactIgnoreRoots, restoreWorkingTreePaths, runAgent, setProcessAuthMode, skillInvokeJudgeCriteria, skillPathsFromSetting, sumUsageParts, toolPathsOutsideWorkspace, traceEditsOutsideWorktree, traceHasUserInputTool, } from "@post-print/agent-harness";
4
- import { applyCompareArm, applySidecarCompareDurations, assertCompareGates, attachCompareStoryResults, buildCompareResult, compareArmDescription, compareArmLabel, compareArmTokens, compareArmTurns, compareResultArms, compareStoryFields, evaluateCompareGates, plainDescription, prefixCompareFailures, requireCompareArm, resolveCompareArms, } from "./compare-scenario.js";
4
+ import { applyCompareArm, applySidecarCompareDurations, assertCompareGates, attachCompareStoryResults, buildCompareResult, compareArmDescription, compareArmLabel, compareArmTokens, compareArmTurns, compareResultArms, compareStoryFields, evaluateCompareGates, finalizeCompareGates, finalizeCompareOutcome, plainDescription, prefixCompareFailures, requireCompareArm, resolveCompareArms, } from "./compare-scenario.js";
5
5
  import { collectDebugEnvironment, getDebugBundleDir, writeDebugBundle } from "./debug-bundle.js";
6
6
  import { discoverSuites } from "./discover-suites.js";
7
7
  import { assertRubric } from "./expect.js";
@@ -13,10 +13,11 @@ import { loadSuiteFile } from "./load-suite.js";
13
13
  import { bindMcpServersToCaller, mcpStdioScriptPaths } from "./mcp-config.js";
14
14
  import { formatDuration, logLive, logPhase, logProgress, logVerdict, refreshHeartbeat, withHeartbeat, } from "./progress.js";
15
15
  import { cleanupStagingSession, createLiveStagingSessionId, getLiveStagingRootOverride, getLiveStagingSessionRoot, getStagingAgentStartPath, getStagingResultPath, getStagingTracePath, loadStagingResult, loadStagingTrace, recordTrace, resolveRecordingPath, setLiveStagingRootOverride, writeAgentStartMarker, writeStagingResult, } from "./record-trace.js";
16
+ import { finalizeScenarioResult } from "./scenario-finalizer.js";
16
17
  import { resolveScenarioRetryMaxAttempts, shouldRetryAnnounceStopFlake } from "./scenario-retry.js";
17
18
  import { captureCallerHead, restoreCallerHeadIfSeedCommit, seedScenarioWorktree, } from "./scenario-seed.js";
18
19
  import { buildScenarioStory, pathFromArgs, quoteExcerpt } from "./scenario-story.js";
19
- import { buildScenarioResultUsage, totalTokensFromScenarioUsage } from "./scenario-usage.js";
20
+ import { totalTokensFromScenarioUsage } from "./scenario-usage.js";
20
21
  import { summarizeReportResults } from "./suite-summary.js";
21
22
  import { theme } from "./theme.js";
22
23
  import { validateSuiteFile } from "./validate-suite.js";
@@ -190,6 +191,9 @@ function toJudgeVerdictResults(trace, criteria, verdictsFromJudge) {
190
191
  question: questionForCriterion(criteria, extended.id),
191
192
  pass: extended.pass,
192
193
  rationale: extended.rationale,
194
+ evidence: extended.evidence,
195
+ prompt: extended.prompt,
196
+ response: extended.response,
193
197
  infraError: extended.infraError,
194
198
  parseError: extended.parseError,
195
199
  rawSdkStatus: extended.rawSdkStatus,
@@ -529,44 +533,29 @@ async function runSuiteBody(options) {
529
533
  }
530
534
  }
531
535
  const durationMs = Math.round(performance.now() - started);
532
- const passed = failures.length === 0;
533
- const usageFields = buildScenarioResultUsage({
534
- agentUsage: compareResult
535
- ? sumUsageParts(compareResultArms(compareResult).map((arm) => arm.trace?.usage))
536
- : scenarioTrace?.usage,
537
- judgeVerdicts,
538
- });
539
- const story = buildScenarioStory({
540
- rubric: scenario.rubric,
541
- trace: scenarioTrace,
542
- passed,
543
- failures,
544
- judgeVerdicts,
545
- compare: compareResult
546
- ? attachCompareStoryResults(compareStoryFields(scenario), compareResult)
547
- : undefined,
548
- });
549
- const scenarioResult = {
536
+ const scenarioResult = finalizeScenarioResult({
550
537
  suite: suite.name,
551
- scenario: scenario.name,
552
- description: plainDescription(scenario.description),
553
- prompt: scenario.prompt,
538
+ scenario,
554
539
  contextMode: childSidecar?.contextMode ??
555
540
  scenario.contextMode ??
556
541
  suite.defaults?.contextMode ??
557
542
  "harness-preamble",
558
543
  contextFiles: childSidecar?.contextFiles,
559
544
  hostInput: childSidecar?.hostInput,
560
- passed,
561
545
  failures,
562
546
  durationMs,
563
547
  attempts,
564
548
  judgeVerdicts,
565
549
  trace: scenarioTrace,
566
550
  compare: compareResult,
567
- story,
568
- ...usageFields,
569
- };
551
+ storyCompare: compareResult
552
+ ? attachCompareStoryResults(compareStoryFields(scenario), compareResult)
553
+ : undefined,
554
+ agentUsage: compareResult
555
+ ? sumUsageParts(compareResultArms(compareResult).map((arm) => arm.trace?.usage))
556
+ : scenarioTrace?.usage,
557
+ });
558
+ const { passed, story } = scenarioResult;
570
559
  const debugBundleDir = await maybeWriteDebugBundle({
571
560
  debug,
572
561
  cwd: options.cwd,
@@ -1073,33 +1062,19 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultCo
1073
1062
  }
1074
1063
  worktreeHandle = undefined;
1075
1064
  }
1076
- const passed = failures.length === 0;
1077
- const story = buildScenarioStory({
1078
- rubric: scenario.rubric,
1079
- trace,
1080
- passed,
1081
- failures,
1082
- judgeVerdicts,
1083
- });
1084
- const scenarioResult = {
1065
+ const scenarioResult = finalizeScenarioResult({
1085
1066
  suite: suiteName,
1086
- scenario: scenario.name,
1087
- description: plainDescription(scenario.description),
1088
- prompt: scenario.prompt,
1067
+ scenario,
1089
1068
  contextMode,
1090
1069
  contextFiles,
1091
1070
  hostInput,
1092
- passed,
1093
1071
  failures,
1094
1072
  durationMs,
1095
1073
  judgeVerdicts,
1096
1074
  trace,
1097
- story,
1098
- ...buildScenarioResultUsage({
1099
- agentUsage: session.usage ?? trace?.usage,
1100
- judgeVerdicts,
1101
- }),
1102
- };
1075
+ agentUsage: session.usage ?? trace?.usage,
1076
+ });
1077
+ const { passed, story } = scenarioResult;
1103
1078
  const armMetrics = {
1104
1079
  id: runOptions?.compareArm ?? "cell",
1105
1080
  label: runOptions?.compareArm ?? "cell",
@@ -1157,6 +1132,11 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultCo
1157
1132
  authMode: getProcessAuthMode(),
1158
1133
  });
1159
1134
  scenarioResult.debugBundleDir = debugBundleDir;
1135
+ emitViewerEvent({
1136
+ type: "scenario_result",
1137
+ ...viewerEnvelope,
1138
+ result: scenarioResult,
1139
+ });
1160
1140
  emitScenarioVerdict({
1161
1141
  passed,
1162
1142
  index: scenarioIndex,
@@ -1234,60 +1214,18 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1234
1214
  const result = await runAgentTestOnce(shared.cwd, shared.suiteName, armScenario, shared.defaultHost, shared.defaultContextMode, shared.defaultProfile, shared.defaultSkills, shared.defaultContextSources, shared.defaultMcpServers, shared.defaultWorkspace, shared.defaultAllowUserSkills, false, shared.worktree, shared.stagingSessionId, undefined, undefined, shared.timeoutMs, shared.allowUserInput, shared.debugFlag, shared.debugDir, shared.suitesDir, shared.keepRecordings, shared.rubricsDir, { suppressEmit: true, compareArm: entry.id, writeSidecar: false });
1235
1215
  armRuns.push({ id: entry.id, label, scenario: armScenario, result });
1236
1216
  }
1237
- const experimentMode = (scenario.compare?.gates?.length ?? 0) > 0;
1238
- const failures = armRuns.flatMap((arm) => prefixCompareFailures(arm.label, experimentMode
1239
- ? arm.result.failures.filter((failure) => failure.category !== "rubric_miss")
1240
- : arm.result.failures));
1241
- const compareResult = buildCompareResult(armRuns.map((arm) => ({
1242
- id: arm.id,
1243
- label: arm.label,
1244
- description: compareArmDescription(resolvedArms.find((entry) => entry.id === arm.id)?.arm),
1245
- prompt: arm.scenario.prompt,
1246
- trace: arm.result.trace,
1247
- contextMode: arm.result.contextMode,
1248
- contextFiles: arm.result.contextFiles,
1249
- hostInput: arm.result.hostInput,
1250
- durationMs: arm.result.durationMs,
1251
- passed: !arm.result.failures.some((failure) => failure.category === "rubric_miss"),
1252
- failures: arm.result.failures,
1253
- })), scenario.compare?.gates);
1254
- if (judge && !isChildProcess() && scenario.compare?.judgeMetrics?.length) {
1255
- for (const arm of compareResult.arms) {
1256
- if (!arm.trace)
1257
- continue;
1258
- const judged = await runJudgeRubric(arm.trace, { judge: scenario.compare.judgeMetrics }, cwd, host);
1259
- arm.trace = judged.trace;
1260
- arm.judgeVerdicts = toJudgeVerdictResults(judged.trace, scenario.compare.judgeMetrics, judged.verdicts);
1261
- arm.failures = [...(arm.failures ?? []), ...judged.failures];
1262
- failures.push(...prefixCompareFailures(arm.label, judged.failures.filter((failure) => failure.category !== "rubric_miss")));
1263
- }
1264
- }
1265
- if (!isChildProcess()) {
1266
- compareResult.gateResults = evaluateCompareGates(compareResult.gates, compareResult);
1267
- if (failures.every((failure) => failure.category === "rubric_miss")) {
1268
- failures.push(...assertCompareGates(compareResult.gates, compareResult));
1269
- }
1270
- }
1271
- const deferJudgeToParent = isChildProcess();
1272
- const compareArms = compareResultArms(compareResult);
1273
- const firstTrace = compareArms[0]?.trace;
1274
- let judgeVerdicts;
1275
- if (judge &&
1276
- !deferJudgeToParent &&
1277
- failures.every((failure) => failure.category === "rubric_miss") &&
1278
- compareArms.length > 0 &&
1279
- compareArms.every((arm) => arm.trace)) {
1280
- const criteria = collectCompareJudgeCriteria(scenario.rubric);
1281
- if (criteria.length > 0) {
1282
- logPhase(theme.judgePhase(criteria.length), { last: true });
1283
- }
1284
- const judged = await runCompareJudgeRubric(compareResult, scenario.rubric, cwd, host);
1285
- failures.push(...judged.failures);
1286
- if (firstTrace) {
1287
- judgeVerdicts = toJudgeVerdictResults({ ...firstTrace, judgeVerdicts: judged.verdicts }, criteria, judged.verdicts);
1288
- }
1289
- }
1290
1217
  const durationMs = Math.round(performance.now() - started);
1218
+ const scenarioResult = await finalizeCompareArmRuns({
1219
+ cwd,
1220
+ suite: suiteName,
1221
+ scenario,
1222
+ host,
1223
+ armRuns,
1224
+ durationMs,
1225
+ judge: Boolean(judge && !isChildProcess()),
1226
+ evaluateGates: !isChildProcess(),
1227
+ });
1228
+ const { passed, story, failures, trace: firstTrace } = scenarioResult;
1291
1229
  if (isChildProcess() && stagingSessionId) {
1292
1230
  const sidecarArms = {};
1293
1231
  for (const arm of armRuns) {
@@ -1309,32 +1247,6 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1309
1247
  },
1310
1248
  });
1311
1249
  }
1312
- const passed = failures.length === 0;
1313
- const story = buildScenarioStory({
1314
- rubric: scenario.rubric,
1315
- trace: firstTrace,
1316
- passed,
1317
- failures,
1318
- judgeVerdicts,
1319
- compare: attachCompareStoryResults(compareStoryFields(scenario), compareResult),
1320
- });
1321
- const scenarioResult = {
1322
- suite: suiteName,
1323
- scenario: scenario.name,
1324
- description: plainDescription(scenario.description),
1325
- prompt: scenario.prompt,
1326
- passed,
1327
- failures,
1328
- durationMs,
1329
- judgeVerdicts,
1330
- trace: firstTrace,
1331
- compare: compareResult,
1332
- story,
1333
- ...buildScenarioResultUsage({
1334
- agentUsage: sumUsageParts(armRuns.map((arm) => arm.result.agentUsage)),
1335
- judgeVerdicts,
1336
- }),
1337
- };
1338
1250
  if (!suppressEmit) {
1339
1251
  const debugBundleDir = await maybeWriteDebugBundle({
1340
1252
  debug,
@@ -1356,6 +1268,13 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1356
1268
  authMode: getProcessAuthMode(),
1357
1269
  });
1358
1270
  scenarioResult.debugBundleDir = debugBundleDir;
1271
+ emitViewerEvent({
1272
+ type: "scenario_result",
1273
+ suite: suiteName,
1274
+ scenario: scenario.name,
1275
+ host,
1276
+ result: scenarioResult,
1277
+ });
1359
1278
  emitScenarioVerdict({
1360
1279
  passed,
1361
1280
  index: scenarioIndex,
@@ -1363,7 +1282,7 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1363
1282
  name: scenario.name,
1364
1283
  durationMs,
1365
1284
  totalTokens: totalTokensFromScenarioUsage(scenarioResult.usage, firstTrace?.usage),
1366
- judgeVerdicts,
1285
+ judgeVerdicts: scenarioResult.judgeVerdicts,
1367
1286
  failures,
1368
1287
  story,
1369
1288
  debug,
@@ -1372,6 +1291,107 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1372
1291
  }
1373
1292
  return scenarioResult;
1374
1293
  }
1294
+ async function finalizeCompareArmRuns(options) {
1295
+ const resolvedArms = resolveCompareArms(options.scenario.compare);
1296
+ const finalized = finalizeCompareOutcome(options.armRuns.map((arm) => ({
1297
+ id: arm.id,
1298
+ label: arm.label,
1299
+ description: compareArmDescription(resolvedArms.find((entry) => entry.id === arm.id)?.arm),
1300
+ prompt: arm.scenario.prompt,
1301
+ trace: arm.result.trace,
1302
+ contextMode: arm.result.contextMode,
1303
+ contextFiles: arm.result.contextFiles,
1304
+ hostInput: arm.result.hostInput,
1305
+ durationMs: arm.result.durationMs,
1306
+ passed: !arm.result.failures.some((failure) => failure.category === "rubric_miss"),
1307
+ failures: arm.result.failures,
1308
+ })), options.scenario.compare?.gates, { evaluateGates: false });
1309
+ const compareResult = finalized.compare;
1310
+ const failures = finalized.failures;
1311
+ if (options.judge && options.scenario.compare?.judgeMetrics?.length) {
1312
+ for (const arm of compareResult.arms) {
1313
+ if (!arm.trace)
1314
+ continue;
1315
+ const judged = await runJudgeRubric(arm.trace, { judge: options.scenario.compare.judgeMetrics }, options.cwd, options.host);
1316
+ arm.trace = judged.trace;
1317
+ arm.judgeVerdicts = toJudgeVerdictResults(judged.trace, options.scenario.compare.judgeMetrics, judged.verdicts);
1318
+ arm.failures = [...(arm.failures ?? []), ...judged.failures];
1319
+ failures.push(...prefixCompareFailures(arm.label, judged.failures.filter((failure) => failure.category !== "rubric_miss")));
1320
+ }
1321
+ }
1322
+ if (options.evaluateGates)
1323
+ finalizeCompareGates(compareResult, failures);
1324
+ const compareArms = compareResultArms(compareResult);
1325
+ const firstTrace = compareArms[0]?.trace;
1326
+ let judgeVerdicts;
1327
+ if (options.judge &&
1328
+ failures.every((failure) => failure.category === "rubric_miss") &&
1329
+ compareArms.length > 0 &&
1330
+ compareArms.every((arm) => arm.trace)) {
1331
+ const criteria = collectCompareJudgeCriteria(options.scenario.rubric);
1332
+ if (criteria.length > 0)
1333
+ logPhase(theme.judgePhase(criteria.length), { last: true });
1334
+ const judged = await runCompareJudgeRubric(compareResult, options.scenario.rubric, options.cwd, options.host);
1335
+ failures.push(...judged.failures);
1336
+ if (firstTrace) {
1337
+ judgeVerdicts = toJudgeVerdictResults({ ...firstTrace, judgeVerdicts: judged.verdicts }, criteria, judged.verdicts);
1338
+ }
1339
+ }
1340
+ return finalizeScenarioResult({
1341
+ suite: options.suite,
1342
+ scenario: options.scenario,
1343
+ failures,
1344
+ durationMs: options.durationMs,
1345
+ judgeVerdicts,
1346
+ trace: firstTrace,
1347
+ compare: compareResult,
1348
+ storyCompare: attachCompareStoryResults(compareStoryFields(options.scenario), compareResult),
1349
+ agentUsage: sumUsageParts(options.armRuns.map((arm) => arm.result.agentUsage)),
1350
+ });
1351
+ }
1352
+ /** Finalize viewer-scheduled compare arms through the same domain and judge path as the CLI. */
1353
+ export async function finalizeScheduledCompareScenario(options) {
1354
+ const compareArms = options.scenario.compare ?? [];
1355
+ const scenario = {
1356
+ name: options.scenario.name,
1357
+ description: options.scenario.description,
1358
+ prompt: options.scenario.prompt,
1359
+ rubric: options.scenario.rubric,
1360
+ compare: {
1361
+ arms: compareArms.map((arm) => ({
1362
+ id: arm.id,
1363
+ label: arm.label,
1364
+ description: arm.description,
1365
+ prompt: arm.prompt,
1366
+ })),
1367
+ gates: options.scenario.gates,
1368
+ judgeMetrics: options.scenario.judgeMetrics,
1369
+ },
1370
+ };
1371
+ const armRuns = compareArms.flatMap((arm) => {
1372
+ const result = options.armResults.get(arm.id);
1373
+ return result
1374
+ ? [
1375
+ {
1376
+ id: arm.id,
1377
+ label: arm.label,
1378
+ scenario: applyCompareArm(scenario, arm.id),
1379
+ result,
1380
+ },
1381
+ ]
1382
+ : [];
1383
+ });
1384
+ return finalizeCompareArmRuns({
1385
+ cwd: options.cwd,
1386
+ suite: options.suite,
1387
+ scenario,
1388
+ host: options.host,
1389
+ armRuns,
1390
+ durationMs: Math.max(0, ...armRuns.map((arm) => arm.result.durationMs)),
1391
+ judge: options.judge !== false,
1392
+ evaluateGates: true,
1393
+ });
1394
+ }
1375
1395
  async function loadCompareResultFromStaging(stagingSessionId, suiteName, scenario) {
1376
1396
  const resolved = resolveCompareArms(scenario.compare);
1377
1397
  const arms = [];