agent-inspect 5.0.0 → 5.1.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.
@@ -4,6 +4,7 @@ import { buildRunSummary, extractMetadata } from './chunk-3USJVBLA.mjs';
4
4
  export { buildRunSummary, buildRunTimeline, buildRunWhatSummary, extractMetadata, renderRunWhat, renderTimeline } from './chunk-3USJVBLA.mjs';
5
5
  import { parseObservationFilter, extractOutcomesFromTraceEvents, extractOutcomesFromPersistedEvents } from './chunk-VFO76UH3.mjs';
6
6
  import './chunk-BT7CATSD.mjs';
7
+ import { escapeHtml } from './chunk-CAWPF22J.mjs';
7
8
  import { resolveRedactionProfile, readTraceEventsFromFile } from './chunk-ULCGIRTC.mjs';
8
9
  export { DEFAULT_MAX_EVENT_BYTES, DEFAULT_MAX_METADATA_VALUE_LENGTH, DEFAULT_MAX_PREVIEW_LENGTH, getRunIdFromTraceFileName, initializeTraceFile, listTraceFiles, prepareMetadataForDisk, prepareTraceEventForDisk, readTraceEvents, readTraceFile, resolveRedactionProfile, resolveTraceSafetyOptions, serializeEvent, validateEvent, writeTraceEvent } from './chunk-ULCGIRTC.mjs';
9
10
  import { Redactor } from './chunk-VU6O5QAH.mjs';
@@ -2218,6 +2219,522 @@ function renderSuiteReport(result, options = {}) {
2218
2219
  return renderSuiteReportMarkdown(result);
2219
2220
  }
2220
2221
 
2221
- export { DEFAULT_SUITE_ARTIFACTS_DIR, DEFAULT_SUITE_CONFIG_NAMES, SESSION_WORKFLOW_KEYS, aggregateBundleSafeStatus, aggregateSessionCheckResults, buildActivitySummary, buildBundleMetadata, buildBundleSummaryMarkdown, buildLocalExplanation, buildPlaceholderArtifact, buildSessionIndex, buildTraceStats, bundleFailsOnSafety, defaultBundleOutputPath, defaultSuiteConfigTemplate, deriveSessionStatus, enrichSessionRunRecord, enrichSessionSummary, extractSessionWorkflowMetadata, filterMetasBySessionScope, filterTraces, groupSessionCohorts, isAgentInspectTrace, loadSessionRunRecords, loadSuiteConfig, loadTraceMetadataList, normalizeBundleOutputPath, normalizeSuiteConfig, parseDurationFilter, renderActivitySummaryHuman, renderSuiteReport, renderSuiteReportMarkdown, renderTraceStats, resolveBundleRunIds, resolveSuiteCaseTrace, resolveSuiteConfigPath, runSuite, searchTraces, sessionKeyForRun, toMetadataSafeStatus, traceMetasToSessionRunRecords, validateSuiteConfig };
2222
+ // packages/core/src/cohort/types.ts
2223
+ var COHORT_METRIC_IDS = [
2224
+ "errorRate",
2225
+ "duration",
2226
+ "toolChoice",
2227
+ "toolOrdering",
2228
+ "llmCallCount",
2229
+ "tokenUsage",
2230
+ "retryCount",
2231
+ "observationFailure",
2232
+ "guardrailFailure",
2233
+ "circuitViolation",
2234
+ "redactionWarning"
2235
+ ];
2236
+
2237
+ // packages/core/src/cohort/compare.ts
2238
+ function compareNumber(metric, label, baseline, candidate, higherIsWorse = true) {
2239
+ if (baseline === void 0 && candidate === void 0) return void 0;
2240
+ const delta = baseline !== void 0 && candidate !== void 0 ? candidate - baseline : void 0;
2241
+ const regression = delta !== void 0 && (higherIsWorse && delta > 0 || !higherIsWorse && delta < 0);
2242
+ return {
2243
+ metric,
2244
+ baseline,
2245
+ candidate,
2246
+ delta,
2247
+ regression,
2248
+ message: `${label}: baseline=${baseline ?? "n/a"} candidate=${candidate ?? "n/a"}${delta !== void 0 ? ` (delta ${delta})` : ""}`
2249
+ };
2250
+ }
2251
+ function pickAggregate(groups, cohortLabel, groupKey) {
2252
+ return groups.find(
2253
+ (group) => group.cohortLabel === cohortLabel && (groupKey === void 0 || group.groupKey === groupKey)
2254
+ );
2255
+ }
2256
+ function compareCohortAggregates(groups, options) {
2257
+ const baselineAgg = pickAggregate(groups, options.baseline, options.groupKey);
2258
+ const candidateAgg = pickAggregate(groups, options.candidate, options.groupKey);
2259
+ const comparisons = [];
2260
+ for (const metric of options.metrics) {
2261
+ switch (metric) {
2262
+ case "errorRate": {
2263
+ const item = compareNumber(
2264
+ metric,
2265
+ "Error rate",
2266
+ baselineAgg?.errorRate,
2267
+ candidateAgg?.errorRate
2268
+ );
2269
+ if (item) comparisons.push(item);
2270
+ break;
2271
+ }
2272
+ case "duration": {
2273
+ const item = compareNumber(
2274
+ metric,
2275
+ "Average duration (ms)",
2276
+ baselineAgg?.avgDurationMs,
2277
+ candidateAgg?.avgDurationMs
2278
+ );
2279
+ if (item) comparisons.push(item);
2280
+ break;
2281
+ }
2282
+ case "llmCallCount": {
2283
+ const item = compareNumber(
2284
+ metric,
2285
+ "Average LLM calls",
2286
+ baselineAgg?.avgLlmCallCount,
2287
+ candidateAgg?.avgLlmCallCount
2288
+ );
2289
+ if (item) comparisons.push(item);
2290
+ break;
2291
+ }
2292
+ case "tokenUsage": {
2293
+ const item = compareNumber(
2294
+ metric,
2295
+ "Average token usage",
2296
+ baselineAgg?.avgTokenUsage,
2297
+ candidateAgg?.avgTokenUsage
2298
+ );
2299
+ if (item) comparisons.push(item);
2300
+ break;
2301
+ }
2302
+ case "retryCount": {
2303
+ const item = compareNumber(
2304
+ metric,
2305
+ "Average retries",
2306
+ baselineAgg?.avgRetryCount,
2307
+ candidateAgg?.avgRetryCount
2308
+ );
2309
+ if (item) comparisons.push(item);
2310
+ break;
2311
+ }
2312
+ case "observationFailure": {
2313
+ const item = compareNumber(
2314
+ metric,
2315
+ "Observation failure rate",
2316
+ baselineAgg?.observationFailureRate,
2317
+ candidateAgg?.observationFailureRate
2318
+ );
2319
+ if (item) comparisons.push(item);
2320
+ break;
2321
+ }
2322
+ case "toolChoice": {
2323
+ const baselineValue = baselineAgg?.dominantToolChoice;
2324
+ const candidateValue = candidateAgg?.dominantToolChoice;
2325
+ comparisons.push({
2326
+ metric,
2327
+ baseline: baselineValue,
2328
+ candidate: candidateValue,
2329
+ delta: baselineValue === candidateValue ? "same" : "changed",
2330
+ regression: baselineValue !== candidateValue,
2331
+ message: `Tool choice: baseline=${baselineValue ?? "n/a"} candidate=${candidateValue ?? "n/a"}`
2332
+ });
2333
+ break;
2334
+ }
2335
+ case "toolOrdering": {
2336
+ const baselineValue = baselineAgg?.toolOrderingSignature;
2337
+ const candidateValue = candidateAgg?.toolOrderingSignature;
2338
+ comparisons.push({
2339
+ metric,
2340
+ baseline: baselineValue,
2341
+ candidate: candidateValue,
2342
+ delta: baselineValue === candidateValue ? "same" : "changed",
2343
+ regression: baselineValue !== candidateValue,
2344
+ message: `Tool ordering: baseline=${baselineValue ?? "n/a"} candidate=${candidateValue ?? "n/a"}`
2345
+ });
2346
+ break;
2347
+ }
2348
+ case "guardrailFailure": {
2349
+ const item = compareNumber(
2350
+ metric,
2351
+ "Guardrail failures",
2352
+ baselineAgg?.avgGuardrailFailures,
2353
+ candidateAgg?.avgGuardrailFailures
2354
+ );
2355
+ if (item) comparisons.push(item);
2356
+ break;
2357
+ }
2358
+ case "circuitViolation": {
2359
+ const item = compareNumber(
2360
+ metric,
2361
+ "Circuit violations",
2362
+ baselineAgg?.avgCircuitViolations,
2363
+ candidateAgg?.avgCircuitViolations
2364
+ );
2365
+ if (item) comparisons.push(item);
2366
+ break;
2367
+ }
2368
+ case "redactionWarning": {
2369
+ const item = compareNumber(
2370
+ metric,
2371
+ "Redaction warnings",
2372
+ baselineAgg?.avgRedactionWarnings,
2373
+ candidateAgg?.avgRedactionWarnings
2374
+ );
2375
+ if (item) comparisons.push(item);
2376
+ break;
2377
+ }
2378
+ }
2379
+ }
2380
+ return comparisons;
2381
+ }
2382
+
2383
+ // packages/core/src/cohort/grouping.ts
2384
+ function parseCohortMetricList(value) {
2385
+ if (value === void 0 || value.trim() === "") return [];
2386
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
2387
+ }
2388
+ function parseGroupBySpec(groupBy) {
2389
+ const raw = (groupBy ?? "model").trim();
2390
+ if (raw === "model") return { kind: "model" };
2391
+ if (raw === "session") return { kind: "session" };
2392
+ if (raw === "group") return { kind: "group" };
2393
+ if (raw.startsWith("metadata.")) {
2394
+ const metadataKey = raw.slice("metadata.".length).trim();
2395
+ if (metadataKey === "") throw new Error("metadata group-by requires a key.");
2396
+ return { kind: "metadata", metadataKey };
2397
+ }
2398
+ throw new Error(`Unsupported --group-by value: ${raw}`);
2399
+ }
2400
+ function metadataString(metadata, key) {
2401
+ const value = metadata?.[key];
2402
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
2403
+ }
2404
+ function resolveRunGroupKey(run, groupBy) {
2405
+ const metadata = run.metadata ?? {};
2406
+ switch (groupBy.kind) {
2407
+ case "model":
2408
+ return metadataString(metadata, "model") ?? "unknown";
2409
+ case "session":
2410
+ return extractSessionWorkflowMetadata(metadata)?.sessionId ?? metadataString(metadata, "sessionId") ?? "__unscoped__";
2411
+ case "group":
2412
+ return extractSessionWorkflowMetadata(metadata)?.groupId ?? metadataString(metadata, "groupId") ?? "__unscoped__";
2413
+ case "metadata":
2414
+ return metadataString(metadata, groupBy.metadataKey) ?? "__missing__";
2415
+ default:
2416
+ return "unknown";
2417
+ }
2418
+ }
2419
+ function resolveCohortLabel(run, cohortKey, baseline, candidate) {
2420
+ const label = metadataString(run.metadata, cohortKey);
2421
+ if (label === void 0) return void 0;
2422
+ if (baseline !== void 0 && label === baseline) return baseline;
2423
+ if (candidate !== void 0 && label === candidate) return candidate;
2424
+ if (baseline === void 0 && candidate === void 0) return label;
2425
+ return void 0;
2426
+ }
2427
+ function filterRunsForCohort(runs, options) {
2428
+ const warnings = [];
2429
+ if (options.baseline === void 0 && options.candidate === void 0) {
2430
+ return { runs: [...runs], warnings };
2431
+ }
2432
+ const selected = [];
2433
+ for (const run of runs) {
2434
+ const label = resolveCohortLabel(
2435
+ run,
2436
+ options.cohortKey,
2437
+ options.baseline,
2438
+ options.candidate
2439
+ );
2440
+ if (label !== void 0) selected.push(run);
2441
+ }
2442
+ if (selected.length === 0) {
2443
+ warnings.push(
2444
+ `No runs matched baseline/candidate labels on metadata.${options.cohortKey}.`
2445
+ );
2446
+ }
2447
+ return { runs: selected, warnings };
2448
+ }
2449
+
2450
+ // packages/core/src/cohort/metrics.ts
2451
+ function asNumber(value) {
2452
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2453
+ }
2454
+ function toolSteps(events) {
2455
+ const ordering = [];
2456
+ const choices = /* @__PURE__ */ new Set();
2457
+ const sorted = [...events].filter((event) => event.event === "step_started").sort((a, b) => a.timestamp - b.timestamp);
2458
+ for (const event of sorted) {
2459
+ const step = event;
2460
+ if (step.type !== "tool") continue;
2461
+ const name = typeof step.metadata?.toolName === "string" ? step.metadata.toolName : step.name;
2462
+ ordering.push(name);
2463
+ choices.add(name);
2464
+ }
2465
+ return { choices: [...choices].sort(), ordering };
2466
+ }
2467
+ async function computeCohortRunMetrics(input) {
2468
+ const events = await readTraceEventsFromFile(input.filePath);
2469
+ const summary = buildRunSummary(events);
2470
+ const tools = toolSteps(events);
2471
+ const outcomes = extractOutcomesFromTraceEvents(events);
2472
+ const observationFailures = outcomes.filter((item) => item.status === "failed").length;
2473
+ const metadata = input.metadata ?? {};
2474
+ const retryCount = asNumber(metadata.attempt) !== void 0 && asNumber(metadata.attempt) > 1 ? asNumber(metadata.attempt) - 1 : typeof metadata.retryOf === "string" ? 1 : 0;
2475
+ return {
2476
+ runId: input.runId,
2477
+ ...input.cohortLabel !== void 0 ? { cohortLabel: input.cohortLabel } : {},
2478
+ groupKey: input.groupKey,
2479
+ status: summary.status,
2480
+ error: summary.status === "error",
2481
+ durationMs: summary.durationMs ?? input.durationMs,
2482
+ llmCallCount: summary.llmSteps,
2483
+ tokenUsageTotal: summary.totalTokens?.total,
2484
+ retryCount,
2485
+ observationFailures,
2486
+ guardrailFailures: asNumber(metadata.guardrailFailures) ?? 0,
2487
+ circuitViolations: asNumber(metadata.circuitViolations) ?? 0,
2488
+ redactionWarnings: asNumber(metadata.redactionWarnings) ?? 0,
2489
+ toolChoices: tools.choices,
2490
+ toolOrdering: tools.ordering
2491
+ };
2492
+ }
2493
+ function percentile2(values, p) {
2494
+ if (values.length === 0) return void 0;
2495
+ const sorted = [...values].sort((a, b) => a - b);
2496
+ const idx = Math.min(
2497
+ sorted.length - 1,
2498
+ Math.max(0, Math.ceil(p / 100 * sorted.length) - 1)
2499
+ );
2500
+ return sorted[idx];
2501
+ }
2502
+ function dominantToolChoice(runs) {
2503
+ const counts = /* @__PURE__ */ new Map();
2504
+ for (const run of runs) {
2505
+ const signature = run.toolChoices.join(",");
2506
+ if (signature === "") continue;
2507
+ counts.set(signature, (counts.get(signature) ?? 0) + 1);
2508
+ }
2509
+ let best;
2510
+ let bestCount = 0;
2511
+ for (const [key, count] of counts) {
2512
+ if (count > bestCount) {
2513
+ best = key;
2514
+ bestCount = count;
2515
+ }
2516
+ }
2517
+ return best;
2518
+ }
2519
+ function orderingSignature(runs) {
2520
+ const counts = /* @__PURE__ */ new Map();
2521
+ for (const run of runs) {
2522
+ const signature = run.toolOrdering.join(">");
2523
+ if (signature === "") continue;
2524
+ counts.set(signature, (counts.get(signature) ?? 0) + 1);
2525
+ }
2526
+ let best;
2527
+ let bestCount = 0;
2528
+ for (const [key, count] of counts) {
2529
+ if (count > bestCount) {
2530
+ best = key;
2531
+ bestCount = count;
2532
+ }
2533
+ }
2534
+ return best;
2535
+ }
2536
+ function aggregateCohortMetrics(runs, groupKey, cohortLabel) {
2537
+ const durations = runs.map((run) => run.durationMs).filter((value) => typeof value === "number");
2538
+ const tokenValues = runs.map((run) => run.tokenUsageTotal).filter((value) => typeof value === "number");
2539
+ const errors = runs.filter((run) => run.error).length;
2540
+ const observationFailures = runs.reduce((sum, run) => sum + run.observationFailures, 0);
2541
+ return {
2542
+ groupKey,
2543
+ ...cohortLabel !== void 0 ? { cohortLabel } : {},
2544
+ runCount: runs.length,
2545
+ errorRate: runs.length > 0 ? errors / runs.length : 0,
2546
+ avgDurationMs: durations.length > 0 ? durations.reduce((sum, value) => sum + value, 0) / durations.length : void 0,
2547
+ p95DurationMs: percentile2(durations, 95),
2548
+ avgLlmCallCount: runs.length > 0 ? runs.reduce((sum, run) => sum + run.llmCallCount, 0) / runs.length : 0,
2549
+ avgTokenUsage: tokenValues.length > 0 ? tokenValues.reduce((sum, value) => sum + value, 0) / tokenValues.length : void 0,
2550
+ avgRetryCount: runs.length > 0 ? runs.reduce((sum, run) => sum + run.retryCount, 0) / runs.length : 0,
2551
+ observationFailureRate: runs.length > 0 ? observationFailures / runs.length : 0,
2552
+ avgGuardrailFailures: runs.length > 0 ? runs.reduce((sum, run) => sum + run.guardrailFailures, 0) / runs.length : 0,
2553
+ avgCircuitViolations: runs.length > 0 ? runs.reduce((sum, run) => sum + run.circuitViolations, 0) / runs.length : 0,
2554
+ avgRedactionWarnings: runs.length > 0 ? runs.reduce((sum, run) => sum + run.redactionWarnings, 0) / runs.length : 0,
2555
+ dominantToolChoice: dominantToolChoice(runs),
2556
+ toolOrderingSignature: orderingSignature(runs)
2557
+ };
2558
+ }
2559
+
2560
+ // packages/core/src/cohort/analyze.ts
2561
+ var DEFAULT_METRICS = [
2562
+ "errorRate",
2563
+ "duration",
2564
+ "toolChoice",
2565
+ "observationFailure"
2566
+ ];
2567
+ function normalizeMetrics(metrics) {
2568
+ if (metrics === void 0 || metrics.length === 0) return [...DEFAULT_METRICS];
2569
+ const allowed = new Set(COHORT_METRIC_IDS);
2570
+ return metrics.filter((metric) => allowed.has(metric));
2571
+ }
2572
+ async function analyzeCohort(runsInput, options) {
2573
+ const cohortKey = options.cohortKey ?? "cohort";
2574
+ const groupBySpec = parseGroupBySpec(options.groupBy);
2575
+ const metrics = normalizeMetrics(options.metrics);
2576
+ const { runs: filteredRuns, warnings } = filterRunsForCohort(runsInput, {
2577
+ cohortKey,
2578
+ baseline: options.baseline,
2579
+ candidate: options.candidate
2580
+ });
2581
+ const runMetrics = [];
2582
+ for (const run of filteredRuns) {
2583
+ if (run.filePath === void 0) continue;
2584
+ const cohortLabel = resolveCohortLabel(
2585
+ run,
2586
+ cohortKey,
2587
+ options.baseline,
2588
+ options.candidate
2589
+ );
2590
+ runMetrics.push(
2591
+ await computeCohortRunMetrics({
2592
+ runId: run.runId,
2593
+ filePath: run.filePath,
2594
+ metadata: run.metadata,
2595
+ status: run.status,
2596
+ durationMs: run.durationMs,
2597
+ groupKey: resolveRunGroupKey(run, groupBySpec),
2598
+ cohortLabel
2599
+ })
2600
+ );
2601
+ }
2602
+ const groupMap = /* @__PURE__ */ new Map();
2603
+ for (const run of runMetrics) {
2604
+ const key = `${run.cohortLabel ?? "*"}::${run.groupKey}`;
2605
+ const bucket = groupMap.get(key) ?? [];
2606
+ bucket.push(run);
2607
+ groupMap.set(key, bucket);
2608
+ }
2609
+ const groups = [...groupMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(
2610
+ ([, bucket]) => aggregateCohortMetrics(
2611
+ bucket,
2612
+ bucket[0].groupKey,
2613
+ bucket[0]?.cohortLabel
2614
+ )
2615
+ );
2616
+ const comparisons = options.baseline !== void 0 && options.candidate !== void 0 ? (() => {
2617
+ const groupKeys = [
2618
+ ...new Set(groups.map((group) => group.groupKey))
2619
+ ].sort((a, b) => a.localeCompare(b));
2620
+ const items = [];
2621
+ for (const groupKey of groupKeys) {
2622
+ items.push(
2623
+ ...compareCohortAggregates(groups, {
2624
+ baseline: options.baseline,
2625
+ candidate: options.candidate,
2626
+ metrics,
2627
+ groupKey
2628
+ })
2629
+ );
2630
+ }
2631
+ return items;
2632
+ })() : [];
2633
+ const regression = comparisons.some((item) => item.regression);
2634
+ return {
2635
+ ok: !regression,
2636
+ traceDir: options.traceDir,
2637
+ ...options.baseline !== void 0 ? { baseline: options.baseline } : {},
2638
+ ...options.candidate !== void 0 ? { candidate: options.candidate } : {},
2639
+ cohortKey,
2640
+ groupBy: options.groupBy ?? "model",
2641
+ metrics,
2642
+ groups,
2643
+ comparisons,
2644
+ runs: runMetrics,
2645
+ warnings
2646
+ };
2647
+ }
2648
+
2649
+ // packages/core/src/cohort/render.ts
2650
+ function formatRate(value) {
2651
+ if (value === void 0) return "n/a";
2652
+ return `${(value * 100).toFixed(1)}%`;
2653
+ }
2654
+ function renderCohortSummaryMarkdown(result) {
2655
+ const lines = [];
2656
+ lines.push("# Cohort analysis");
2657
+ lines.push("");
2658
+ lines.push(`Trace directory: \`${result.traceDir}\``);
2659
+ lines.push(`Group by: \`${result.groupBy}\``);
2660
+ if (result.baseline !== void 0 && result.candidate !== void 0) {
2661
+ lines.push(
2662
+ `Baseline/Candidate key: \`${result.cohortKey}\` (${result.baseline} vs ${result.candidate})`
2663
+ );
2664
+ }
2665
+ lines.push(`Status: **${result.ok ? "PASS" : "REGRESSION"}**`);
2666
+ lines.push("");
2667
+ if (result.warnings.length > 0) {
2668
+ lines.push("## Warnings");
2669
+ for (const warning of result.warnings) lines.push(`- ${warning}`);
2670
+ lines.push("");
2671
+ }
2672
+ lines.push("## Groups");
2673
+ for (const group of result.groups) {
2674
+ lines.push(
2675
+ `### ${group.cohortLabel ?? "all"} / ${group.groupKey} (${group.runCount} runs)`
2676
+ );
2677
+ lines.push(`- Error rate: ${formatRate(group.errorRate)}`);
2678
+ if (group.avgDurationMs !== void 0) {
2679
+ lines.push(`- Avg duration: ${Math.round(group.avgDurationMs)} ms`);
2680
+ }
2681
+ if (group.dominantToolChoice !== void 0) {
2682
+ lines.push(`- Dominant tools: ${group.dominantToolChoice}`);
2683
+ }
2684
+ lines.push(
2685
+ `- Observation failure rate: ${formatRate(group.observationFailureRate)}`
2686
+ );
2687
+ lines.push("");
2688
+ }
2689
+ if (result.comparisons.length > 0) {
2690
+ lines.push("## Comparisons");
2691
+ for (const comparison of result.comparisons) {
2692
+ const flag = comparison.regression ? " **REGRESSION**" : "";
2693
+ lines.push(`- ${comparison.message}${flag}`);
2694
+ }
2695
+ lines.push("");
2696
+ }
2697
+ return lines.join("\n").trimEnd();
2698
+ }
2699
+ function renderCohortReportHtml(result) {
2700
+ const rows = result.groups.map(
2701
+ (group) => `<tr><td>${escapeHtml(group.cohortLabel ?? "all")}</td><td>${escapeHtml(group.groupKey)}</td><td>${group.runCount}</td><td>${escapeHtml(formatRate(group.errorRate))}</td><td>${group.avgDurationMs !== void 0 ? Math.round(group.avgDurationMs) : "n/a"}</td></tr>`
2702
+ ).join("");
2703
+ const comparisons = result.comparisons.map(
2704
+ (item) => `<li>${escapeHtml(item.message)}${item.regression ? " <strong>REGRESSION</strong>" : ""}</li>`
2705
+ ).join("");
2706
+ return `<!DOCTYPE html>
2707
+ <html lang="en">
2708
+ <head>
2709
+ <meta charset="utf-8" />
2710
+ <title>Cohort report</title>
2711
+ <style>
2712
+ body { font-family: system-ui, sans-serif; margin: 2rem; }
2713
+ table { border-collapse: collapse; width: 100%; }
2714
+ th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; }
2715
+ th { background: #f6f6f6; }
2716
+ </style>
2717
+ </head>
2718
+ <body>
2719
+ <h1>Cohort analysis</h1>
2720
+ <p>Status: <strong>${result.ok ? "PASS" : "REGRESSION"}</strong></p>
2721
+ <p>Trace directory: <code>${escapeHtml(result.traceDir)}</code></p>
2722
+ <h2>Groups</h2>
2723
+ <table>
2724
+ <thead><tr><th>Cohort</th><th>Group</th><th>Runs</th><th>Error rate</th><th>Avg duration (ms)</th></tr></thead>
2725
+ <tbody>${rows}</tbody>
2726
+ </table>
2727
+ ${result.comparisons.length > 0 ? `<h2>Comparisons</h2><ul>${comparisons}</ul>` : ""}
2728
+ </body>
2729
+ </html>`;
2730
+ }
2731
+ function renderCohortReport(result, options = {}) {
2732
+ const format = options.format ?? "markdown";
2733
+ if (format === "json") return JSON.stringify(result, null, 2);
2734
+ if (format === "html") return renderCohortReportHtml(result);
2735
+ return renderCohortSummaryMarkdown(result);
2736
+ }
2737
+
2738
+ export { COHORT_METRIC_IDS, DEFAULT_SUITE_ARTIFACTS_DIR, DEFAULT_SUITE_CONFIG_NAMES, SESSION_WORKFLOW_KEYS, aggregateBundleSafeStatus, aggregateSessionCheckResults, analyzeCohort, buildActivitySummary, buildBundleMetadata, buildBundleSummaryMarkdown, buildLocalExplanation, buildPlaceholderArtifact, buildSessionIndex, buildTraceStats, bundleFailsOnSafety, compareCohortAggregates, defaultBundleOutputPath, defaultSuiteConfigTemplate, deriveSessionStatus, enrichSessionRunRecord, enrichSessionSummary, extractSessionWorkflowMetadata, filterMetasBySessionScope, filterTraces, groupSessionCohorts, isAgentInspectTrace, loadSessionRunRecords, loadSuiteConfig, loadTraceMetadataList, normalizeBundleOutputPath, normalizeSuiteConfig, parseCohortMetricList, parseDurationFilter, parseGroupBySpec, renderActivitySummaryHuman, renderCohortReport, renderCohortSummaryMarkdown, renderSuiteReport, renderSuiteReportMarkdown, renderTraceStats, resolveBundleRunIds, resolveSuiteCaseTrace, resolveSuiteConfigPath, runSuite, searchTraces, sessionKeyForRun, toMetadataSafeStatus, traceMetasToSessionRunRecords, validateSuiteConfig };
2222
2739
  //# sourceMappingURL=advanced.mjs.map
2223
2740
  //# sourceMappingURL=advanced.mjs.map