@post-print/agent-test 1.1.1 → 1.1.3

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 +4 -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 +223 -142
  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 +147 -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 +9 -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 +4 -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 +37 -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 +848 -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,
@@ -937,6 +926,7 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultCo
937
926
  outputContract,
938
927
  mcpServers,
939
928
  allowUserSkills,
929
+ networkAccess: scenario.networkAccess === true,
940
930
  authMode: getProcessAuthMode(),
941
931
  timeoutMs: liveTimeoutMs,
942
932
  failOnUserInput,
@@ -1073,33 +1063,19 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultCo
1073
1063
  }
1074
1064
  worktreeHandle = undefined;
1075
1065
  }
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 = {
1066
+ const scenarioResult = finalizeScenarioResult({
1085
1067
  suite: suiteName,
1086
- scenario: scenario.name,
1087
- description: plainDescription(scenario.description),
1088
- prompt: scenario.prompt,
1068
+ scenario,
1089
1069
  contextMode,
1090
1070
  contextFiles,
1091
1071
  hostInput,
1092
- passed,
1093
1072
  failures,
1094
1073
  durationMs,
1095
1074
  judgeVerdicts,
1096
1075
  trace,
1097
- story,
1098
- ...buildScenarioResultUsage({
1099
- agentUsage: session.usage ?? trace?.usage,
1100
- judgeVerdicts,
1101
- }),
1102
- };
1076
+ agentUsage: session.usage ?? trace?.usage,
1077
+ });
1078
+ const { passed, story } = scenarioResult;
1103
1079
  const armMetrics = {
1104
1080
  id: runOptions?.compareArm ?? "cell",
1105
1081
  label: runOptions?.compareArm ?? "cell",
@@ -1157,6 +1133,11 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultCo
1157
1133
  authMode: getProcessAuthMode(),
1158
1134
  });
1159
1135
  scenarioResult.debugBundleDir = debugBundleDir;
1136
+ emitViewerEvent({
1137
+ type: "scenario_result",
1138
+ ...viewerEnvelope,
1139
+ result: scenarioResult,
1140
+ });
1160
1141
  emitScenarioVerdict({
1161
1142
  passed,
1162
1143
  index: scenarioIndex,
@@ -1234,60 +1215,18 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1234
1215
  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
1216
  armRuns.push({ id: entry.id, label, scenario: armScenario, result });
1236
1217
  }
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
1218
  const durationMs = Math.round(performance.now() - started);
1219
+ const scenarioResult = await finalizeCompareArmRuns({
1220
+ cwd,
1221
+ suite: suiteName,
1222
+ scenario,
1223
+ host,
1224
+ armRuns,
1225
+ durationMs,
1226
+ judge: Boolean(judge && !isChildProcess()),
1227
+ evaluateGates: !isChildProcess(),
1228
+ });
1229
+ const { passed, story, failures, trace: firstTrace } = scenarioResult;
1291
1230
  if (isChildProcess() && stagingSessionId) {
1292
1231
  const sidecarArms = {};
1293
1232
  for (const arm of armRuns) {
@@ -1309,32 +1248,6 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1309
1248
  },
1310
1249
  });
1311
1250
  }
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
1251
  if (!suppressEmit) {
1339
1252
  const debugBundleDir = await maybeWriteDebugBundle({
1340
1253
  debug,
@@ -1356,6 +1269,13 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1356
1269
  authMode: getProcessAuthMode(),
1357
1270
  });
1358
1271
  scenarioResult.debugBundleDir = debugBundleDir;
1272
+ emitViewerEvent({
1273
+ type: "scenario_result",
1274
+ suite: suiteName,
1275
+ scenario: scenario.name,
1276
+ host,
1277
+ result: scenarioResult,
1278
+ });
1359
1279
  emitScenarioVerdict({
1360
1280
  passed,
1361
1281
  index: scenarioIndex,
@@ -1363,7 +1283,7 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1363
1283
  name: scenario.name,
1364
1284
  durationMs,
1365
1285
  totalTokens: totalTokensFromScenarioUsage(scenarioResult.usage, firstTrace?.usage),
1366
- judgeVerdicts,
1286
+ judgeVerdicts: scenarioResult.judgeVerdicts,
1367
1287
  failures,
1368
1288
  story,
1369
1289
  debug,
@@ -1372,6 +1292,107 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
1372
1292
  }
1373
1293
  return scenarioResult;
1374
1294
  }
1295
+ async function finalizeCompareArmRuns(options) {
1296
+ const resolvedArms = resolveCompareArms(options.scenario.compare);
1297
+ const finalized = finalizeCompareOutcome(options.armRuns.map((arm) => ({
1298
+ id: arm.id,
1299
+ label: arm.label,
1300
+ description: compareArmDescription(resolvedArms.find((entry) => entry.id === arm.id)?.arm),
1301
+ prompt: arm.scenario.prompt,
1302
+ trace: arm.result.trace,
1303
+ contextMode: arm.result.contextMode,
1304
+ contextFiles: arm.result.contextFiles,
1305
+ hostInput: arm.result.hostInput,
1306
+ durationMs: arm.result.durationMs,
1307
+ passed: !arm.result.failures.some((failure) => failure.category === "rubric_miss"),
1308
+ failures: arm.result.failures,
1309
+ })), options.scenario.compare?.gates, { evaluateGates: false });
1310
+ const compareResult = finalized.compare;
1311
+ const failures = finalized.failures;
1312
+ if (options.judge && options.scenario.compare?.judgeMetrics?.length) {
1313
+ for (const arm of compareResult.arms) {
1314
+ if (!arm.trace)
1315
+ continue;
1316
+ const judged = await runJudgeRubric(arm.trace, { judge: options.scenario.compare.judgeMetrics }, options.cwd, options.host);
1317
+ arm.trace = judged.trace;
1318
+ arm.judgeVerdicts = toJudgeVerdictResults(judged.trace, options.scenario.compare.judgeMetrics, judged.verdicts);
1319
+ arm.failures = [...(arm.failures ?? []), ...judged.failures];
1320
+ failures.push(...prefixCompareFailures(arm.label, judged.failures.filter((failure) => failure.category !== "rubric_miss")));
1321
+ }
1322
+ }
1323
+ if (options.evaluateGates)
1324
+ finalizeCompareGates(compareResult, failures);
1325
+ const compareArms = compareResultArms(compareResult);
1326
+ const firstTrace = compareArms[0]?.trace;
1327
+ let judgeVerdicts;
1328
+ if (options.judge &&
1329
+ failures.every((failure) => failure.category === "rubric_miss") &&
1330
+ compareArms.length > 0 &&
1331
+ compareArms.every((arm) => arm.trace)) {
1332
+ const criteria = collectCompareJudgeCriteria(options.scenario.rubric);
1333
+ if (criteria.length > 0)
1334
+ logPhase(theme.judgePhase(criteria.length), { last: true });
1335
+ const judged = await runCompareJudgeRubric(compareResult, options.scenario.rubric, options.cwd, options.host);
1336
+ failures.push(...judged.failures);
1337
+ if (firstTrace) {
1338
+ judgeVerdicts = toJudgeVerdictResults({ ...firstTrace, judgeVerdicts: judged.verdicts }, criteria, judged.verdicts);
1339
+ }
1340
+ }
1341
+ return finalizeScenarioResult({
1342
+ suite: options.suite,
1343
+ scenario: options.scenario,
1344
+ failures,
1345
+ durationMs: options.durationMs,
1346
+ judgeVerdicts,
1347
+ trace: firstTrace,
1348
+ compare: compareResult,
1349
+ storyCompare: attachCompareStoryResults(compareStoryFields(options.scenario), compareResult),
1350
+ agentUsage: sumUsageParts(options.armRuns.map((arm) => arm.result.agentUsage)),
1351
+ });
1352
+ }
1353
+ /** Finalize viewer-scheduled compare arms through the same domain and judge path as the CLI. */
1354
+ export async function finalizeScheduledCompareScenario(options) {
1355
+ const compareArms = options.scenario.compare ?? [];
1356
+ const scenario = {
1357
+ name: options.scenario.name,
1358
+ description: options.scenario.description,
1359
+ prompt: options.scenario.prompt,
1360
+ rubric: options.scenario.rubric,
1361
+ compare: {
1362
+ arms: compareArms.map((arm) => ({
1363
+ id: arm.id,
1364
+ label: arm.label,
1365
+ description: arm.description,
1366
+ prompt: arm.prompt,
1367
+ })),
1368
+ gates: options.scenario.gates,
1369
+ judgeMetrics: options.scenario.judgeMetrics,
1370
+ },
1371
+ };
1372
+ const armRuns = compareArms.flatMap((arm) => {
1373
+ const result = options.armResults.get(arm.id);
1374
+ return result
1375
+ ? [
1376
+ {
1377
+ id: arm.id,
1378
+ label: arm.label,
1379
+ scenario: applyCompareArm(scenario, arm.id),
1380
+ result,
1381
+ },
1382
+ ]
1383
+ : [];
1384
+ });
1385
+ return finalizeCompareArmRuns({
1386
+ cwd: options.cwd,
1387
+ suite: options.suite,
1388
+ scenario,
1389
+ host: options.host,
1390
+ armRuns,
1391
+ durationMs: Math.max(0, ...armRuns.map((arm) => arm.result.durationMs)),
1392
+ judge: options.judge !== false,
1393
+ evaluateGates: true,
1394
+ });
1395
+ }
1375
1396
  async function loadCompareResultFromStaging(stagingSessionId, suiteName, scenario) {
1376
1397
  const resolved = resolveCompareArms(scenario.compare);
1377
1398
  const arms = [];