@post-print/agent-test 0.3.12 → 1.0.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.
- package/README.md +18 -4
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -3
- package/dist/cli.js.map +1 -1
- package/dist/command-allowlist.d.ts +9 -0
- package/dist/command-allowlist.d.ts.map +1 -0
- package/dist/command-allowlist.js +29 -0
- package/dist/command-allowlist.js.map +1 -0
- package/dist/compare-scenario.d.ts +14 -18
- package/dist/compare-scenario.d.ts.map +1 -1
- package/dist/compare-scenario.js +146 -257
- package/dist/compare-scenario.js.map +1 -1
- package/dist/debug-bundle.d.ts.map +1 -1
- package/dist/debug-bundle.js +2 -0
- package/dist/debug-bundle.js.map +1 -1
- package/dist/expect.d.ts +3 -0
- package/dist/expect.d.ts.map +1 -1
- package/dist/expect.js +36 -0
- package/dist/expect.js.map +1 -1
- package/dist/html-report.d.ts +1 -0
- package/dist/html-report.d.ts.map +1 -1
- package/dist/html-report.js +194 -38
- package/dist/html-report.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/live-isolation.d.ts +8 -1
- package/dist/live-isolation.d.ts.map +1 -1
- package/dist/live-isolation.js +96 -8
- package/dist/live-isolation.js.map +1 -1
- package/dist/record-trace.d.ts +2 -2
- package/dist/record-trace.d.ts.map +1 -1
- package/dist/record-trace.js +6 -4
- package/dist/record-trace.js.map +1 -1
- package/dist/run-suite.d.ts +9 -1
- package/dist/run-suite.d.ts.map +1 -1
- package/dist/run-suite.js +190 -58
- package/dist/run-suite.js.map +1 -1
- package/dist/scenario-story.d.ts +4 -3
- package/dist/scenario-story.d.ts.map +1 -1
- package/dist/scenario-story.js +54 -71
- package/dist/scenario-story.js.map +1 -1
- package/dist/scenario-usage.d.ts.map +1 -1
- package/dist/scenario-usage.js +2 -3
- package/dist/scenario-usage.js.map +1 -1
- package/dist/types.d.ts +38 -22
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-suite.d.ts.map +1 -1
- package/dist/validate-suite.js +65 -38
- package/dist/validate-suite.js.map +1 -1
- package/dist/viewer/catalog.d.ts +53 -0
- package/dist/viewer/catalog.d.ts.map +1 -0
- package/dist/viewer/catalog.js +116 -0
- package/dist/viewer/catalog.js.map +1 -0
- package/dist/viewer/cli.d.ts +17 -0
- package/dist/viewer/cli.d.ts.map +1 -0
- package/dist/viewer/cli.js +38 -0
- package/dist/viewer/cli.js.map +1 -0
- package/dist/viewer/compare-metrics.d.ts +30 -0
- package/dist/viewer/compare-metrics.d.ts.map +1 -0
- package/dist/viewer/compare-metrics.js +102 -0
- package/dist/viewer/compare-metrics.js.map +1 -0
- package/dist/viewer/context-files.d.ts +8 -0
- package/dist/viewer/context-files.d.ts.map +1 -0
- package/dist/viewer/context-files.js +56 -0
- package/dist/viewer/context-files.js.map +1 -0
- package/dist/viewer/emit.d.ts +7 -0
- package/dist/viewer/emit.d.ts.map +1 -0
- package/dist/viewer/emit.js +25 -0
- package/dist/viewer/emit.js.map +1 -0
- package/dist/viewer/events.d.ts +75 -0
- package/dist/viewer/events.d.ts.map +1 -0
- package/dist/viewer/events.js +47 -0
- package/dist/viewer/events.js.map +1 -0
- package/dist/viewer/live-runner.d.ts +20 -0
- package/dist/viewer/live-runner.d.ts.map +1 -0
- package/dist/viewer/live-runner.js +102 -0
- package/dist/viewer/live-runner.js.map +1 -0
- package/dist/viewer/page.d.ts +3 -0
- package/dist/viewer/page.d.ts.map +1 -0
- package/dist/viewer/page.js +1664 -0
- package/dist/viewer/page.js.map +1 -0
- package/dist/viewer/run-controller.d.ts +27 -0
- package/dist/viewer/run-controller.d.ts.map +1 -0
- package/dist/viewer/run-controller.js +154 -0
- package/dist/viewer/run-controller.js.map +1 -0
- package/dist/viewer/server.d.ts +16 -0
- package/dist/viewer/server.d.ts.map +1 -0
- package/dist/viewer/server.js +154 -0
- package/dist/viewer/server.js.map +1 -0
- package/dist/worker-pool.d.ts +8 -0
- package/dist/worker-pool.d.ts.map +1 -0
- package/dist/worker-pool.js +26 -0
- package/dist/worker-pool.js.map +1 -0
- package/package.json +7 -5
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 { cancelActiveClaudeRun, cancelActiveCursorRun, cancelActiveOpenaiRun, captureWorkingTreeStatus, createSealedWorkspace, defaultSealedOverlayPaths, enrichTrace, filterWorkingTreeLeaks, findWorkingTreeLeak, formatWorkingTreeLeak, getProcessAuthMode, 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,
|
|
4
|
+
import { applyCompareArm, applySidecarCompareDurations, assertCompareGates, attachCompareStoryResults, buildCompareResult, compareArmDescription, compareArmLabel, compareArmTokens, compareArmTurns, compareResultArms, compareStoryFields, evaluateCompareGates, 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";
|
|
@@ -20,6 +20,9 @@ import { buildScenarioResultUsage, totalTokensFromScenarioUsage } from "./scenar
|
|
|
20
20
|
import { summarizeReportResults } from "./suite-summary.js";
|
|
21
21
|
import { theme } from "./theme.js";
|
|
22
22
|
import { validateSuiteFile } from "./validate-suite.js";
|
|
23
|
+
import { viewerContextFiles } from "./viewer/context-files.js";
|
|
24
|
+
import { emitViewerEvent } from "./viewer/emit.js";
|
|
25
|
+
import { DEFAULT_CLI_WORKERS, runWorkerPool } from "./worker-pool.js";
|
|
23
26
|
const require = createRequire(import.meta.url);
|
|
24
27
|
const packageVersion = require("../package.json").version;
|
|
25
28
|
let activeWorktreeCleanup;
|
|
@@ -142,7 +145,8 @@ export function scenarioNeedsJudge(judge, scenario) {
|
|
|
142
145
|
return false;
|
|
143
146
|
}
|
|
144
147
|
if (scenario.compare) {
|
|
145
|
-
return collectCompareJudgeCriteria(scenario.rubric).length > 0
|
|
148
|
+
return (collectCompareJudgeCriteria(scenario.rubric).length > 0 ||
|
|
149
|
+
(scenario.compare.judgeMetrics?.length ?? 0) > 0);
|
|
146
150
|
}
|
|
147
151
|
return collectJudgeCriteria(scenario.rubric).length > 0;
|
|
148
152
|
}
|
|
@@ -330,26 +334,26 @@ async function runSuiteBody(options) {
|
|
|
330
334
|
const parentCounters = parentScenarioCounters();
|
|
331
335
|
const displayTotal = parentCounters?.total ?? filteredTotal;
|
|
332
336
|
const isolateLive = liveScenarioIsolationEnabled() && !options.scenarioFilter && filteredTotal > 1;
|
|
333
|
-
|
|
337
|
+
const workers = Math.max(1, options.workers ?? DEFAULT_CLI_WORKERS);
|
|
334
338
|
if (shouldPrintSuiteChrome()) {
|
|
335
339
|
logProgress(`\n${theme.suiteHeader(suite.name, defaultHost, displayTotal)}`);
|
|
336
340
|
if (isolateLive) {
|
|
337
341
|
logProgress(` ${theme.isolationNote()}`);
|
|
338
342
|
}
|
|
339
343
|
}
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
344
|
+
if (isolateLive) {
|
|
345
|
+
const isolateSlots = new Array(scenarios.length);
|
|
346
|
+
await runWorkerPool(scenarios.map((scenario, index) => ({ scenario, index })), workers, async ({ scenario, index }) => {
|
|
347
|
+
if (!scenario) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const scenarioIndex = parentCounters?.index ?? index + 1;
|
|
351
|
+
const scenarioTotal = displayTotal;
|
|
348
352
|
if (scenario.skip) {
|
|
349
353
|
const skipLabel = `[${scenarioIndex}/${scenarioTotal}] ${scenario.name}`;
|
|
350
354
|
logProgress(theme.skipped(skipLabel));
|
|
351
355
|
logScenarioDescription(scenario.description);
|
|
352
|
-
|
|
356
|
+
isolateSlots[index] = {
|
|
353
357
|
suite: suite.name,
|
|
354
358
|
scenario: scenario.name,
|
|
355
359
|
description: plainDescription(scenario.description),
|
|
@@ -364,8 +368,8 @@ async function runSuiteBody(options) {
|
|
|
364
368
|
skipped: true,
|
|
365
369
|
failures: [],
|
|
366
370
|
}),
|
|
367
|
-
}
|
|
368
|
-
|
|
371
|
+
};
|
|
372
|
+
return;
|
|
369
373
|
}
|
|
370
374
|
const started = performance.now();
|
|
371
375
|
const debug = isDebugEnabled(options);
|
|
@@ -375,7 +379,7 @@ async function runSuiteBody(options) {
|
|
|
375
379
|
let attempts = 0;
|
|
376
380
|
let failures = [];
|
|
377
381
|
let scenarioTrace;
|
|
378
|
-
let previousAttemptExitCode
|
|
382
|
+
let previousAttemptExitCode;
|
|
379
383
|
let childSidecar;
|
|
380
384
|
while (true) {
|
|
381
385
|
attempts++;
|
|
@@ -403,7 +407,6 @@ async function runSuiteBody(options) {
|
|
|
403
407
|
authMode: options.authMode ?? getProcessAuthMode(),
|
|
404
408
|
});
|
|
405
409
|
previousAttemptExitCode = spawned.exitCode;
|
|
406
|
-
previousIsolatedExitCode = spawned.exitCode;
|
|
407
410
|
failures = [];
|
|
408
411
|
scenarioTrace = undefined;
|
|
409
412
|
childSidecar =
|
|
@@ -451,7 +454,32 @@ async function runSuiteBody(options) {
|
|
|
451
454
|
compareResult = applySidecarCompareDurations(await loadCompareResultFromStaging(options.stagingSessionId, suite.name, scenario), childSidecar);
|
|
452
455
|
scenarioTrace = compareResultArms(compareResult)[0]?.trace ?? scenarioTrace;
|
|
453
456
|
}
|
|
454
|
-
if (
|
|
457
|
+
if (compareResult && options.judge !== false && scenario.compare?.judgeMetrics?.length) {
|
|
458
|
+
for (const arm of compareResult.arms) {
|
|
459
|
+
if (!arm.trace)
|
|
460
|
+
continue;
|
|
461
|
+
try {
|
|
462
|
+
const judged = await runJudgeRubric(arm.trace, { judge: scenario.compare.judgeMetrics }, options.cwd, judgeHost);
|
|
463
|
+
arm.trace = judged.trace;
|
|
464
|
+
arm.judgeVerdicts = toJudgeVerdictResults(judged.trace, scenario.compare.judgeMetrics, judged.verdicts);
|
|
465
|
+
arm.failures = [...(arm.failures ?? []), ...judged.failures];
|
|
466
|
+
failures.push(...prefixCompareFailures(arm.label, judged.failures.filter((failure) => failure.category !== "rubric_miss")));
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
failures.push(assertionFailure("judge", error instanceof Error ? error.message : "failed to judge compare arm", "judge_infra"));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (compareResult) {
|
|
474
|
+
compareResult.gateResults = evaluateCompareGates(compareResult.gates, compareResult);
|
|
475
|
+
if (failures.every((failure) => failure.category === "rubric_miss")) {
|
|
476
|
+
failures.push(...assertCompareGates(compareResult.gates, compareResult));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if ((options.judge !== false && failures.length === 0) ||
|
|
480
|
+
(options.judge !== false &&
|
|
481
|
+
scenario.compare &&
|
|
482
|
+
failures.every((failure) => failure.category === "rubric_miss"))) {
|
|
455
483
|
const compareTraces = compareResult ? compareResultArms(compareResult) : [];
|
|
456
484
|
if (scenario.compare &&
|
|
457
485
|
compareResult &&
|
|
@@ -492,7 +520,9 @@ async function runSuiteBody(options) {
|
|
|
492
520
|
judgeVerdicts = toJudgeVerdictResults(judged.trace, criteria, judged.verdicts);
|
|
493
521
|
}
|
|
494
522
|
catch (error) {
|
|
495
|
-
failures.push(assertionFailure("judge", error instanceof Error
|
|
523
|
+
failures.push(assertionFailure("judge", error instanceof Error
|
|
524
|
+
? error.message
|
|
525
|
+
: "failed to load staging trace for judge", "judge_infra"));
|
|
496
526
|
}
|
|
497
527
|
}
|
|
498
528
|
}
|
|
@@ -563,31 +593,47 @@ async function runSuiteBody(options) {
|
|
|
563
593
|
debug,
|
|
564
594
|
debugBundleDir,
|
|
565
595
|
});
|
|
566
|
-
|
|
596
|
+
isolateSlots[index] = scenarioResult;
|
|
597
|
+
releaseLiveMemory();
|
|
598
|
+
return;
|
|
599
|
+
});
|
|
600
|
+
for (const result of isolateSlots) {
|
|
601
|
+
if (result) {
|
|
602
|
+
results.push(result);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
for (let index = 0; index < scenarios.length; index++) {
|
|
608
|
+
const scenario = scenarios[index];
|
|
609
|
+
if (!scenario) {
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
const scenarioIndex = parentCounters?.index ?? index + 1;
|
|
613
|
+
const scenarioTotal = displayTotal;
|
|
614
|
+
results.push(await runAgentTest({
|
|
615
|
+
cwd: options.cwd,
|
|
616
|
+
suiteName: suite.name,
|
|
617
|
+
scenario,
|
|
618
|
+
defaults: suite.defaults,
|
|
619
|
+
host: options.host,
|
|
620
|
+
judge: options.judge,
|
|
621
|
+
worktree: options.worktree,
|
|
622
|
+
stagingSessionId: options.stagingSessionId,
|
|
623
|
+
scenarioIndex,
|
|
624
|
+
scenarioTotal,
|
|
625
|
+
timeoutMs: options.timeoutMs,
|
|
626
|
+
allowUserInput: options.allowUserInput,
|
|
627
|
+
debug: options.debug,
|
|
628
|
+
debugDir: options.debugDir,
|
|
629
|
+
suitesDir: options.suitesDir,
|
|
630
|
+
keepRecordings: options.keepRecordings,
|
|
631
|
+
rubricsDir: options.rubricsDir,
|
|
632
|
+
scenarioRetries: isChildProcess() ? 0 : options.scenarioRetries,
|
|
633
|
+
compareArm: options.compareArm,
|
|
634
|
+
}));
|
|
567
635
|
releaseLiveMemory();
|
|
568
|
-
continue;
|
|
569
636
|
}
|
|
570
|
-
results.push(await runAgentTest({
|
|
571
|
-
cwd: options.cwd,
|
|
572
|
-
suiteName: suite.name,
|
|
573
|
-
scenario,
|
|
574
|
-
defaults: suite.defaults,
|
|
575
|
-
host: options.host,
|
|
576
|
-
judge: options.judge,
|
|
577
|
-
worktree: options.worktree,
|
|
578
|
-
stagingSessionId: options.stagingSessionId,
|
|
579
|
-
scenarioIndex,
|
|
580
|
-
scenarioTotal,
|
|
581
|
-
timeoutMs: options.timeoutMs,
|
|
582
|
-
allowUserInput: options.allowUserInput,
|
|
583
|
-
debug: options.debug,
|
|
584
|
-
debugDir: options.debugDir,
|
|
585
|
-
suitesDir: options.suitesDir,
|
|
586
|
-
keepRecordings: options.keepRecordings,
|
|
587
|
-
rubricsDir: options.rubricsDir,
|
|
588
|
-
scenarioRetries: isChildProcess() ? 0 : options.scenarioRetries,
|
|
589
|
-
}));
|
|
590
|
-
releaseLiveMemory();
|
|
591
637
|
}
|
|
592
638
|
return {
|
|
593
639
|
suite: suite.name,
|
|
@@ -690,7 +736,7 @@ async function runAgentTestBody(options) {
|
|
|
690
736
|
let result;
|
|
691
737
|
while (true) {
|
|
692
738
|
attempts++;
|
|
693
|
-
result = await runAgentTestOnce(options.cwd, suiteName, options.scenario, defaultHost, options.defaults?.profile, options.defaults?.skills, options.defaults?.contextSources, options.defaults?.mcpServers, options.defaults?.workspace, options.defaults?.allowUserSkills, options.judge ?? true, options.worktree ?? true, options.stagingSessionId, options.scenarioIndex, options.scenarioTotal, options.timeoutMs, options.allowUserInput, options.debug, options.debugDir, options.suitesDir ?? "agent-suites", options.keepRecordings, options.rubricsDir, { suppressEmit: maxAttempts > 1 });
|
|
739
|
+
result = await runAgentTestOnce(options.cwd, suiteName, options.scenario, defaultHost, options.defaults?.profile, options.defaults?.skills, options.defaults?.contextSources, options.defaults?.mcpServers, options.defaults?.workspace, options.defaults?.allowUserSkills, options.judge ?? true, options.worktree ?? true, options.stagingSessionId, options.scenarioIndex, options.scenarioTotal, options.timeoutMs, options.allowUserInput, options.debug, options.debugDir, options.suitesDir ?? "agent-suites", options.keepRecordings, options.rubricsDir, { suppressEmit: maxAttempts > 1, compareArm: options.compareArm });
|
|
694
740
|
const canRetry = !result.skipped &&
|
|
695
741
|
!result.passed &&
|
|
696
742
|
attempts < maxAttempts &&
|
|
@@ -769,6 +815,9 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultPr
|
|
|
769
815
|
if (scenario.compare && runOptions?.compareArm === undefined) {
|
|
770
816
|
return runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultProfile, defaultSkills, defaultContextSources, defaultMcpServers, defaultWorkspace, defaultAllowUserSkills, judge, worktree, stagingSessionId, scenarioIndex, scenarioTotal, timeoutMs, allowUserInput, debugFlag, debugDir, suitesDir, keepRecordings, rubricsDir, { suppressEmit });
|
|
771
817
|
}
|
|
818
|
+
if (runOptions?.compareArm && scenario.compare) {
|
|
819
|
+
scenario = requireCompareArm(scenario, runOptions.compareArm);
|
|
820
|
+
}
|
|
772
821
|
const host = scenario.host ?? defaultHost;
|
|
773
822
|
const profile = scenario.profile ?? defaultProfile ?? defaultProfileForHost(host);
|
|
774
823
|
const skills = scenario.skills ?? defaultSkills;
|
|
@@ -781,6 +830,12 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultPr
|
|
|
781
830
|
: mergeMcpServers(defaultMcpServers, scenario.mcpServers);
|
|
782
831
|
const liveTimeoutMs = resolveLiveTimeoutMs(timeoutMs);
|
|
783
832
|
const failOnUserInput = !allowUserInput;
|
|
833
|
+
const viewerEnvelope = {
|
|
834
|
+
suite: suiteName,
|
|
835
|
+
scenario: scenario.name,
|
|
836
|
+
host,
|
|
837
|
+
...(runOptions?.compareArm ? { arm: runOptions.compareArm } : {}),
|
|
838
|
+
};
|
|
784
839
|
if (runOptions?.compareArm) {
|
|
785
840
|
logPhase(theme.phase("arm", `${runOptions.compareArm.toUpperCase()} ${host}`));
|
|
786
841
|
}
|
|
@@ -797,6 +852,7 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultPr
|
|
|
797
852
|
let callerHeadBefore;
|
|
798
853
|
const callerTreeBefore = useWorktree ? await captureWorkingTreeStatus(cwd) : undefined;
|
|
799
854
|
if (useWorktree) {
|
|
855
|
+
emitViewerEvent({ type: "status", text: "Creating sealed workspace.", ...viewerEnvelope });
|
|
800
856
|
if (scenario.seedPatch) {
|
|
801
857
|
callerHeadBefore = await captureCallerHead(cwd);
|
|
802
858
|
setCallerHeadRestore(cwd, callerHeadBefore);
|
|
@@ -811,9 +867,15 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultPr
|
|
|
811
867
|
activeWorktreeCleanup = worktreeHandle.cleanup;
|
|
812
868
|
if (scenario.seedPatch) {
|
|
813
869
|
logPhase(theme.phase("seed", theme.basename(scenario.seedPatch)));
|
|
870
|
+
emitViewerEvent({
|
|
871
|
+
type: "status",
|
|
872
|
+
text: `Applying seed ${basename(scenario.seedPatch)}.`,
|
|
873
|
+
...viewerEnvelope,
|
|
874
|
+
});
|
|
814
875
|
await seedScenarioWorktree(cwd, worktreeHandle.path, scenario.seedPatch, {
|
|
815
876
|
stageOnly: scenario.seedStageOnly === true,
|
|
816
877
|
});
|
|
878
|
+
emitViewerEvent({ type: "status", text: "Seed applied.", ...viewerEnvelope });
|
|
817
879
|
}
|
|
818
880
|
}
|
|
819
881
|
else {
|
|
@@ -829,8 +891,20 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultPr
|
|
|
829
891
|
});
|
|
830
892
|
const outputContract = outputContractForRubric(scenario.rubric);
|
|
831
893
|
const agentStartMarkerPath = isChildProcess() && stagingSessionId
|
|
832
|
-
? getStagingAgentStartPath(stagingSessionId, suiteName, scenario.name)
|
|
894
|
+
? getStagingAgentStartPath(stagingSessionId, suiteName, scenario.name, runOptions?.compareArm)
|
|
833
895
|
: undefined;
|
|
896
|
+
emitViewerEvent({ type: "cell_started", ...viewerEnvelope });
|
|
897
|
+
emitViewerEvent({ type: "status", text: "Starting host agent.", ...viewerEnvelope });
|
|
898
|
+
const contextFiles = viewerContextFiles(context.preamble, context.sources, contextSources ?? []);
|
|
899
|
+
if (contextFiles.length > 0) {
|
|
900
|
+
emitViewerEvent({
|
|
901
|
+
type: "status",
|
|
902
|
+
text: contextFiles.map((file) => file.why).join(" "),
|
|
903
|
+
...viewerEnvelope,
|
|
904
|
+
});
|
|
905
|
+
emitViewerEvent({ type: "context", files: contextFiles, ...viewerEnvelope });
|
|
906
|
+
}
|
|
907
|
+
emitViewerEvent({ type: "prompt", text: scenario.prompt, ...viewerEnvelope });
|
|
834
908
|
logPhase(theme.phase("agent", theme.phaseDim("started")));
|
|
835
909
|
const agentStarted = performance.now();
|
|
836
910
|
let livePreview;
|
|
@@ -852,11 +926,18 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultPr
|
|
|
852
926
|
: undefined,
|
|
853
927
|
onAgentEvent: (event) => {
|
|
854
928
|
if (event.type === "tool") {
|
|
929
|
+
emitViewerEvent({
|
|
930
|
+
type: "tool",
|
|
931
|
+
name: event.name,
|
|
932
|
+
args: event.args,
|
|
933
|
+
...viewerEnvelope,
|
|
934
|
+
});
|
|
855
935
|
logLive(theme.liveTool(event.name, pathFromArgs(event.args)));
|
|
856
936
|
livePreview = undefined;
|
|
857
937
|
refreshHeartbeat();
|
|
858
938
|
return;
|
|
859
939
|
}
|
|
940
|
+
emitViewerEvent({ type: "text", text: event.text, ...viewerEnvelope });
|
|
860
941
|
// Clock tick paints the preview. Do not rewrite here — a long
|
|
861
942
|
// line wraps and `\r` cannot clear the leftover row.
|
|
862
943
|
livePreview = quoteExcerpt(event.text);
|
|
@@ -953,7 +1034,7 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultPr
|
|
|
953
1034
|
}
|
|
954
1035
|
const durationMs = Math.round(performance.now() - started);
|
|
955
1036
|
if (isChildProcess() && stagingSessionId && runOptions?.writeSidecar !== false) {
|
|
956
|
-
await writeStagingResult(getStagingResultPath(stagingSessionId, suiteName, scenario.name), {
|
|
1037
|
+
await writeStagingResult(getStagingResultPath(stagingSessionId, suiteName, scenario.name, runOptions?.compareArm), {
|
|
957
1038
|
passed: failures.length === 0,
|
|
958
1039
|
failures,
|
|
959
1040
|
durationMs,
|
|
@@ -993,6 +1074,42 @@ async function runAgentTestOnce(cwd, suiteName, scenario, defaultHost, defaultPr
|
|
|
993
1074
|
judgeVerdicts,
|
|
994
1075
|
}),
|
|
995
1076
|
};
|
|
1077
|
+
const armMetrics = {
|
|
1078
|
+
id: runOptions?.compareArm ?? "cell",
|
|
1079
|
+
label: runOptions?.compareArm ?? "cell",
|
|
1080
|
+
prompt: scenario.prompt,
|
|
1081
|
+
trace,
|
|
1082
|
+
};
|
|
1083
|
+
const turns = compareArmTurns(armMetrics);
|
|
1084
|
+
const tokens = compareArmTokens(armMetrics);
|
|
1085
|
+
const tools = trace.toolCalls.length;
|
|
1086
|
+
emitViewerEvent({
|
|
1087
|
+
type: "cell_finished",
|
|
1088
|
+
...viewerEnvelope,
|
|
1089
|
+
passed,
|
|
1090
|
+
durationMs,
|
|
1091
|
+
failures: failures.map((failure) => ({
|
|
1092
|
+
matcher: failure.matcher,
|
|
1093
|
+
message: failure.message,
|
|
1094
|
+
})),
|
|
1095
|
+
metrics: {
|
|
1096
|
+
...(turns !== undefined ? { turns } : {}),
|
|
1097
|
+
...(tokens !== undefined ? { tokens } : {}),
|
|
1098
|
+
tools,
|
|
1099
|
+
},
|
|
1100
|
+
});
|
|
1101
|
+
if (judgeVerdicts && judgeVerdicts.length > 0) {
|
|
1102
|
+
emitViewerEvent({
|
|
1103
|
+
type: "judge",
|
|
1104
|
+
...viewerEnvelope,
|
|
1105
|
+
verdicts: judgeVerdicts.map((verdict) => ({
|
|
1106
|
+
id: verdict.id,
|
|
1107
|
+
question: verdict.question,
|
|
1108
|
+
pass: verdict.pass,
|
|
1109
|
+
rationale: verdict.rationale,
|
|
1110
|
+
})),
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
996
1113
|
if (!suppressEmit) {
|
|
997
1114
|
const debugBundleDir = await maybeWriteDebugBundle({
|
|
998
1115
|
debug,
|
|
@@ -1090,8 +1207,10 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
|
|
|
1090
1207
|
const result = await runAgentTestOnce(shared.cwd, shared.suiteName, armScenario, shared.defaultHost, 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 });
|
|
1091
1208
|
armRuns.push({ id: entry.id, label, scenario: armScenario, result });
|
|
1092
1209
|
}
|
|
1093
|
-
const
|
|
1094
|
-
const
|
|
1210
|
+
const experimentMode = (scenario.compare?.gates?.length ?? 0) > 0;
|
|
1211
|
+
const failures = armRuns.flatMap((arm) => prefixCompareFailures(arm.label, experimentMode
|
|
1212
|
+
? arm.result.failures.filter((failure) => failure.category !== "rubric_miss")
|
|
1213
|
+
: arm.result.failures));
|
|
1095
1214
|
const compareResult = buildCompareResult(armRuns.map((arm) => ({
|
|
1096
1215
|
id: arm.id,
|
|
1097
1216
|
label: arm.label,
|
|
@@ -1099,12 +1218,25 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
|
|
|
1099
1218
|
prompt: arm.scenario.prompt,
|
|
1100
1219
|
trace: arm.result.trace,
|
|
1101
1220
|
durationMs: arm.result.durationMs,
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1221
|
+
passed: !arm.result.failures.some((failure) => failure.category === "rubric_miss"),
|
|
1222
|
+
failures: arm.result.failures,
|
|
1223
|
+
})), scenario.compare?.gates);
|
|
1224
|
+
if (judge && !isChildProcess() && scenario.compare?.judgeMetrics?.length) {
|
|
1225
|
+
for (const arm of compareResult.arms) {
|
|
1226
|
+
if (!arm.trace)
|
|
1227
|
+
continue;
|
|
1228
|
+
const judged = await runJudgeRubric(arm.trace, { judge: scenario.compare.judgeMetrics }, cwd, host);
|
|
1229
|
+
arm.trace = judged.trace;
|
|
1230
|
+
arm.judgeVerdicts = toJudgeVerdictResults(judged.trace, scenario.compare.judgeMetrics, judged.verdicts);
|
|
1231
|
+
arm.failures = [...(arm.failures ?? []), ...judged.failures];
|
|
1232
|
+
failures.push(...prefixCompareFailures(arm.label, judged.failures.filter((failure) => failure.category !== "rubric_miss")));
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (!isChildProcess()) {
|
|
1236
|
+
compareResult.gateResults = evaluateCompareGates(compareResult.gates, compareResult);
|
|
1237
|
+
if (failures.every((failure) => failure.category === "rubric_miss")) {
|
|
1238
|
+
failures.push(...assertCompareGates(compareResult.gates, compareResult));
|
|
1239
|
+
}
|
|
1108
1240
|
}
|
|
1109
1241
|
const deferJudgeToParent = isChildProcess();
|
|
1110
1242
|
const compareArms = compareResultArms(compareResult);
|
|
@@ -1112,7 +1244,7 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
|
|
|
1112
1244
|
let judgeVerdicts;
|
|
1113
1245
|
if (judge &&
|
|
1114
1246
|
!deferJudgeToParent &&
|
|
1115
|
-
failures.
|
|
1247
|
+
failures.every((failure) => failure.category === "rubric_miss") &&
|
|
1116
1248
|
compareArms.length > 0 &&
|
|
1117
1249
|
compareArms.every((arm) => arm.trace)) {
|
|
1118
1250
|
const criteria = collectCompareJudgeCriteria(scenario.rubric);
|
|
@@ -1207,7 +1339,6 @@ async function runCompareAgentTestOnce(cwd, suiteName, scenario, defaultHost, de
|
|
|
1207
1339
|
}
|
|
1208
1340
|
async function loadCompareResultFromStaging(stagingSessionId, suiteName, scenario) {
|
|
1209
1341
|
const resolved = resolveCompareArms(scenario.compare);
|
|
1210
|
-
const armIds = resolved.map((entry) => entry.id);
|
|
1211
1342
|
const arms = [];
|
|
1212
1343
|
for (const entry of resolved) {
|
|
1213
1344
|
const armScenario = applyCompareArm(scenario, entry.id);
|
|
@@ -1224,12 +1355,11 @@ async function loadCompareResultFromStaging(stagingSessionId, suiteName, scenari
|
|
|
1224
1355
|
description: compareArmDescription(entry.arm),
|
|
1225
1356
|
prompt: armScenario.prompt,
|
|
1226
1357
|
trace,
|
|
1358
|
+
passed: trace ? assertRubric(trace, armScenario.rubric).length === 0 : undefined,
|
|
1359
|
+
failures: trace ? assertRubric(trace, armScenario.rubric) : undefined,
|
|
1227
1360
|
});
|
|
1228
1361
|
}
|
|
1229
|
-
return buildCompareResult(arms,
|
|
1230
|
-
faster: resolveCompareMetricPairs(scenario.compare?.faster, armIds),
|
|
1231
|
-
cheaper: resolveCompareMetricPairs(scenario.compare?.cheaper, armIds),
|
|
1232
|
-
});
|
|
1362
|
+
return buildCompareResult(arms, scenario.compare?.gates);
|
|
1233
1363
|
}
|
|
1234
1364
|
async function runCompareJudgeRubric(compare, rubric, runCwd, host) {
|
|
1235
1365
|
const criteria = collectCompareJudgeCriteria(rubric);
|
|
@@ -1358,6 +1488,8 @@ async function runAllSuitesBody(options) {
|
|
|
1358
1488
|
rubricsDir: options.rubricsDir,
|
|
1359
1489
|
adapterModules: options.adapterModules,
|
|
1360
1490
|
authMode: options.authMode ?? getProcessAuthMode(),
|
|
1491
|
+
compareArm: options.compareArm,
|
|
1492
|
+
workers: options.workers,
|
|
1361
1493
|
}));
|
|
1362
1494
|
}
|
|
1363
1495
|
}
|