agent-inspect 5.0.0 → 5.2.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';
@@ -439,9 +440,9 @@ async function searchTraces(metas, options) {
439
440
  }
440
441
  const limit = options.limit ?? 50;
441
442
  const sessionId = options.session?.trim();
442
- const observationStatus = parseObservationFilter(options.observation);
443
+ const observationStatus2 = parseObservationFilter(options.observation);
443
444
  const hasContentFilter = Boolean(
444
- options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter || observationStatus
445
+ options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter || observationStatus2
445
446
  );
446
447
  const results = [];
447
448
  const sessionLabel = sessionId && sessionId !== "" ? sessionId : void 0;
@@ -486,9 +487,9 @@ async function searchTraces(metas, options) {
486
487
  statusFilter: options.status
487
488
  });
488
489
  results.push(...stepMatches);
489
- if (observationStatus) {
490
+ if (observationStatus2) {
490
491
  const outcomes = extractOutcomesFromTraceEvents(events);
491
- const matched = outcomes.filter((outcome) => outcome.status === observationStatus);
492
+ const matched = outcomes.filter((outcome) => outcome.status === observationStatus2);
492
493
  for (const outcome of matched) {
493
494
  results.push({
494
495
  runId: m.runId,
@@ -1324,12 +1325,12 @@ function buildCriticalPath(runs, handoffs) {
1324
1325
  handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
1325
1326
  );
1326
1327
  const ordered = [...runs].sort(compareRuns);
1327
- const path6 = [];
1328
+ const path7 = [];
1328
1329
  const visited = /* @__PURE__ */ new Set();
1329
1330
  const pushRun = (run, confidence, source) => {
1330
1331
  if (visited.has(run.runId)) return;
1331
1332
  visited.add(run.runId);
1332
- path6.push({
1333
+ path7.push({
1333
1334
  runId: run.runId,
1334
1335
  name: run.name,
1335
1336
  startedAt: run.startedAt,
@@ -1354,7 +1355,7 @@ function buildCriticalPath(runs, handoffs) {
1354
1355
  const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
1355
1356
  pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
1356
1357
  }
1357
- return path6;
1358
+ return path7;
1358
1359
  }
1359
1360
  function metaRunIdMatches(run, token, runById) {
1360
1361
  const meta = extractSessionWorkflowMetadata(run.metadata);
@@ -2218,6 +2219,918 @@ 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
+ // packages/core/src/gate/parse.ts
2739
+ function parseGateList(value) {
2740
+ if (value === void 0 || value.trim() === "") return [];
2741
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
2742
+ }
2743
+ function parseGateNumber(value, label) {
2744
+ if (value === void 0 || value.trim() === "") return void 0;
2745
+ const parsed = Number(value);
2746
+ if (!Number.isFinite(parsed)) {
2747
+ throw new Error(`Invalid ${label}: ${value}`);
2748
+ }
2749
+ return parsed;
2750
+ }
2751
+
2752
+ // packages/core/src/gate/evaluate.ts
2753
+ function percentile3(values, p) {
2754
+ if (values.length === 0) return void 0;
2755
+ const sorted = [...values].sort((a, b) => a - b);
2756
+ const idx = Math.min(
2757
+ sorted.length - 1,
2758
+ Math.max(0, Math.ceil(p / 100 * sorted.length) - 1)
2759
+ );
2760
+ return sorted[idx];
2761
+ }
2762
+ function hasThresholds(options) {
2763
+ return options.maxErrorRate !== void 0 || options.maxP95DurationMs !== void 0 || (options.forbidTools?.length ?? 0) > 0 || (options.requireObservations?.length ?? 0) > 0;
2764
+ }
2765
+ function gateHasThresholds(options) {
2766
+ return hasThresholds(options);
2767
+ }
2768
+ async function loadRunMetrics(runs) {
2769
+ const metrics = [];
2770
+ for (const run of runs) {
2771
+ if (run.filePath === void 0) continue;
2772
+ metrics.push(
2773
+ await computeCohortRunMetrics({
2774
+ runId: run.runId,
2775
+ filePath: run.filePath,
2776
+ metadata: run.metadata,
2777
+ status: run.status,
2778
+ durationMs: run.durationMs,
2779
+ groupKey: "all"
2780
+ })
2781
+ );
2782
+ }
2783
+ return metrics;
2784
+ }
2785
+ async function observationStatus(filePath, name) {
2786
+ const events = await readTraceEventsFromFile(filePath);
2787
+ const outcomes = extractOutcomesFromTraceEvents(events);
2788
+ const match = outcomes.find((item) => item.name === name);
2789
+ if (!match) return "missing";
2790
+ return match.status === "passed" ? "passed" : "failed";
2791
+ }
2792
+ async function evaluateGateThresholds(runs, options) {
2793
+ const checks = [];
2794
+ const readErrors = [];
2795
+ if (!hasThresholds(options)) {
2796
+ return { checks, readErrors };
2797
+ }
2798
+ if (runs.length === 0) {
2799
+ readErrors.push("No trace runs found in the gate directory.");
2800
+ return { checks, readErrors };
2801
+ }
2802
+ let runMetrics;
2803
+ try {
2804
+ runMetrics = await loadRunMetrics(runs);
2805
+ } catch (error) {
2806
+ const message = error instanceof Error ? error.message : String(error);
2807
+ readErrors.push(message);
2808
+ return { checks, readErrors };
2809
+ }
2810
+ if (options.maxErrorRate !== void 0) {
2811
+ const errors = runMetrics.filter((run) => run.error).length;
2812
+ const actual = runMetrics.length > 0 ? errors / runMetrics.length * 100 : 0;
2813
+ const ok = actual <= options.maxErrorRate;
2814
+ checks.push({
2815
+ id: "maxErrorRate",
2816
+ name: "Max error rate",
2817
+ ok,
2818
+ expected: options.maxErrorRate,
2819
+ actual: Math.round(actual * 10) / 10,
2820
+ message: ok ? `Error rate ${actual.toFixed(1)}% within limit ${options.maxErrorRate}%` : `Error rate ${actual.toFixed(1)}% exceeds limit ${options.maxErrorRate}%`
2821
+ });
2822
+ }
2823
+ if (options.maxP95DurationMs !== void 0) {
2824
+ const durations = runMetrics.map((run) => run.durationMs).filter((value) => typeof value === "number");
2825
+ const actual = percentile3(durations, 95);
2826
+ const ok = actual !== void 0 && actual <= options.maxP95DurationMs;
2827
+ checks.push({
2828
+ id: "maxP95Duration",
2829
+ name: "Max p95 duration (ms)",
2830
+ ok,
2831
+ expected: options.maxP95DurationMs,
2832
+ actual: actual ?? "n/a",
2833
+ message: actual === void 0 ? "No duration samples available for p95 check." : ok ? `P95 duration ${Math.round(actual)} ms within limit ${options.maxP95DurationMs} ms` : `P95 duration ${Math.round(actual)} ms exceeds limit ${options.maxP95DurationMs} ms`
2834
+ });
2835
+ }
2836
+ for (const tool of options.forbidTools ?? []) {
2837
+ let violated = false;
2838
+ for (const run of runMetrics) {
2839
+ const used = run.toolChoices.includes(tool) || run.toolOrdering.includes(tool);
2840
+ if (used) {
2841
+ violated = true;
2842
+ checks.push({
2843
+ id: "forbidTool",
2844
+ name: `Forbid tool: ${tool}`,
2845
+ ok: false,
2846
+ expected: `not used`,
2847
+ actual: "used",
2848
+ runId: run.runId,
2849
+ message: `Forbidden tool "${tool}" used in run ${run.runId}`
2850
+ });
2851
+ }
2852
+ }
2853
+ if (!violated) {
2854
+ checks.push({
2855
+ id: "forbidTool",
2856
+ name: `Forbid tool: ${tool}`,
2857
+ ok: true,
2858
+ message: `Forbidden tool "${tool}" not used`
2859
+ });
2860
+ }
2861
+ }
2862
+ for (const observation of options.requireObservations ?? []) {
2863
+ for (const run of runs) {
2864
+ if (run.filePath === void 0) continue;
2865
+ try {
2866
+ const status = await observationStatus(run.filePath, observation);
2867
+ const ok = status === "passed";
2868
+ checks.push({
2869
+ id: "requireObservation",
2870
+ name: `Require observation: ${observation}`,
2871
+ ok,
2872
+ expected: "passed",
2873
+ actual: status,
2874
+ runId: run.runId,
2875
+ message: ok ? `Observation "${observation}" passed in run ${run.runId}` : status === "missing" ? `Observation "${observation}" missing in run ${run.runId}` : `Observation "${observation}" failed in run ${run.runId}`
2876
+ });
2877
+ } catch (error) {
2878
+ const message = error instanceof Error ? error.message : String(error);
2879
+ readErrors.push(`Run ${run.runId}: ${message}`);
2880
+ }
2881
+ }
2882
+ }
2883
+ return { checks, readErrors };
2884
+ }
2885
+ function checksFromSuiteResult(suiteResult) {
2886
+ const checks = [
2887
+ {
2888
+ id: "suite",
2889
+ name: `Suite: ${suiteResult.suiteName}`,
2890
+ ok: suiteResult.ok,
2891
+ message: suiteResult.ok ? `Suite passed (${suiteResult.summary.passed} cases)` : `Suite failed (${suiteResult.summary.failed} failed, ${suiteResult.summary.errors} errors)`
2892
+ }
2893
+ ];
2894
+ for (const suiteCase of suiteResult.cases) {
2895
+ if (suiteCase.status === "pass") continue;
2896
+ checks.push({
2897
+ id: "suite",
2898
+ name: `Case: ${suiteCase.id}`,
2899
+ ok: false,
2900
+ message: suiteCase.message ?? `Case status: ${suiteCase.status}`
2901
+ });
2902
+ }
2903
+ return checks;
2904
+ }
2905
+ function resolveExitCode(input) {
2906
+ if (input.configError) return 2;
2907
+ if (input.readError) return 3;
2908
+ if (!input.ok) return 1;
2909
+ return 0;
2910
+ }
2911
+ function validateOptions(options) {
2912
+ const errors = [];
2913
+ const hasSuite = options.suitePath !== void 0 && options.suitePath.trim() !== "";
2914
+ const hasThresholds2 = gateHasThresholds(options);
2915
+ if (!hasSuite && !hasThresholds2) {
2916
+ errors.push(
2917
+ "No gate rules specified. Pass --suite or at least one threshold flag."
2918
+ );
2919
+ }
2920
+ if (hasThresholds2 && (options.traceDir === void 0 || options.traceDir.trim() === "")) {
2921
+ if (!hasSuite) {
2922
+ errors.push("Threshold flags require --dir <trace-directory>.");
2923
+ }
2924
+ }
2925
+ if (options.maxErrorRate !== void 0 && options.maxErrorRate < 0) {
2926
+ errors.push("--max-error-rate must be a non-negative percentage.");
2927
+ }
2928
+ if (options.maxP95DurationMs !== void 0 && options.maxP95DurationMs < 0) {
2929
+ errors.push("--max-p95-duration must be a non-negative millisecond value.");
2930
+ }
2931
+ return errors;
2932
+ }
2933
+ function isConfigLoadError(error) {
2934
+ if (!(error instanceof Error)) return false;
2935
+ const ext = path3.extname(error.message);
2936
+ if (error.message.includes("Unsupported suite config extension")) return true;
2937
+ if (error.message.includes("TypeScript suite configs require")) return true;
2938
+ if (error.message.includes("No suite config found")) return true;
2939
+ if (error.message.includes("AI_SUITE_CONFIG")) return true;
2940
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") return true;
2941
+ return "diagnostics" in error;
2942
+ }
2943
+ async function runGate(runs, options) {
2944
+ const diagnostics = [];
2945
+ const checks = [];
2946
+ const validationErrors = validateOptions(options);
2947
+ if (validationErrors.length > 0) {
2948
+ return {
2949
+ ok: false,
2950
+ exitCode: 2,
2951
+ runCount: 0,
2952
+ checks,
2953
+ diagnostics: validationErrors
2954
+ };
2955
+ }
2956
+ let traceDir = options.traceDir?.trim();
2957
+ let suiteResult;
2958
+ if (options.suitePath !== void 0 && options.suitePath.trim() !== "") {
2959
+ try {
2960
+ suiteResult = await runSuite({
2961
+ configPath: options.suitePath,
2962
+ cwd: options.cwd
2963
+ });
2964
+ traceDir = traceDir ?? suiteResult.tracesDir;
2965
+ checks.push(...checksFromSuiteResult(suiteResult));
2966
+ } catch (error) {
2967
+ const message = error instanceof Error ? error.message : String(error);
2968
+ diagnostics.push(message);
2969
+ return {
2970
+ ok: false,
2971
+ exitCode: isConfigLoadError(error) ? 2 : 3,
2972
+ traceDir,
2973
+ suitePath: options.suitePath,
2974
+ runCount: 0,
2975
+ checks,
2976
+ diagnostics
2977
+ };
2978
+ }
2979
+ }
2980
+ if (gateHasThresholds(options)) {
2981
+ const thresholdDir = traceDir;
2982
+ if (thresholdDir === void 0 || thresholdDir.trim() === "") {
2983
+ return {
2984
+ ok: false,
2985
+ exitCode: 2,
2986
+ traceDir,
2987
+ suitePath: options.suitePath,
2988
+ runCount: runs.length,
2989
+ checks,
2990
+ diagnostics: ["Threshold evaluation requires a trace directory."],
2991
+ ...suiteResult !== void 0 ? { suiteResult } : {}
2992
+ };
2993
+ }
2994
+ const thresholdRuns = runs.length > 0 ? runs : [];
2995
+ const { checks: thresholdChecks, readErrors } = await evaluateGateThresholds(
2996
+ thresholdRuns,
2997
+ options
2998
+ );
2999
+ checks.push(...thresholdChecks);
3000
+ diagnostics.push(...readErrors);
3001
+ if (readErrors.length > 0) {
3002
+ const ok2 = checks.length > 0 && checks.every((item) => item.ok);
3003
+ return {
3004
+ ok: ok2,
3005
+ exitCode: resolveExitCode({
3006
+ ok: ok2,
3007
+ configError: false,
3008
+ readError: true
3009
+ }),
3010
+ traceDir: thresholdDir,
3011
+ suitePath: options.suitePath,
3012
+ runCount: thresholdRuns.length,
3013
+ checks,
3014
+ diagnostics,
3015
+ ...suiteResult !== void 0 ? { suiteResult } : {}
3016
+ };
3017
+ }
3018
+ }
3019
+ const ok = checks.length > 0 && checks.every((item) => item.ok);
3020
+ return {
3021
+ ok,
3022
+ exitCode: resolveExitCode({ ok, configError: false, readError: false }),
3023
+ traceDir,
3024
+ suitePath: options.suitePath,
3025
+ runCount: runs.length,
3026
+ checks,
3027
+ diagnostics,
3028
+ ...suiteResult !== void 0 ? { suiteResult } : {}
3029
+ };
3030
+ }
3031
+
3032
+ // packages/core/src/gate/render.ts
3033
+ function escapeXml(value) {
3034
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
3035
+ }
3036
+ function renderGateSummaryMarkdown(result) {
3037
+ const lines = [];
3038
+ lines.push("# AgentInspect gate");
3039
+ lines.push("");
3040
+ lines.push(`Status: **${result.ok ? "PASS" : "FAIL"}** (exit ${result.exitCode})`);
3041
+ if (result.traceDir !== void 0) {
3042
+ lines.push(`Trace directory: \`${result.traceDir}\``);
3043
+ }
3044
+ if (result.suitePath !== void 0) {
3045
+ lines.push(`Suite config: \`${result.suitePath}\``);
3046
+ }
3047
+ lines.push(`Runs evaluated: ${result.runCount}`);
3048
+ lines.push("");
3049
+ if (result.diagnostics.length > 0) {
3050
+ lines.push("## Diagnostics");
3051
+ for (const item of result.diagnostics) lines.push(`- ${item}`);
3052
+ lines.push("");
3053
+ }
3054
+ lines.push("## Checks");
3055
+ for (const check of result.checks) {
3056
+ const flag = check.ok ? "PASS" : "FAIL";
3057
+ lines.push(`- [${flag}] ${check.name}: ${check.message}`);
3058
+ }
3059
+ lines.push("");
3060
+ return lines.join("\n").trimEnd();
3061
+ }
3062
+ function renderGateGithubStepSummary(result) {
3063
+ const lines = [];
3064
+ lines.push(`## AgentInspect gate: ${result.ok ? "PASS" : "FAIL"}`);
3065
+ lines.push("");
3066
+ lines.push("| Check | Status | Details |");
3067
+ lines.push("| --- | --- | --- |");
3068
+ for (const check of result.checks) {
3069
+ lines.push(
3070
+ `| ${check.name} | ${check.ok ? "pass" : "fail"} | ${check.message.replace(/\|/g, "/")} |`
3071
+ );
3072
+ }
3073
+ if (result.diagnostics.length > 0) {
3074
+ lines.push("");
3075
+ lines.push("**Diagnostics**");
3076
+ for (const item of result.diagnostics) lines.push(`- ${item}`);
3077
+ }
3078
+ return lines.join("\n").trimEnd();
3079
+ }
3080
+ function renderGateReportHtml(result) {
3081
+ const rows = result.checks.map(
3082
+ (check) => `<tr><td>${escapeHtml(check.name)}</td><td>${check.ok ? "PASS" : "FAIL"}</td><td>${escapeHtml(check.message)}</td></tr>`
3083
+ ).join("");
3084
+ return `<!DOCTYPE html>
3085
+ <html lang="en">
3086
+ <head>
3087
+ <meta charset="utf-8" />
3088
+ <title>Gate report</title>
3089
+ <style>
3090
+ body { font-family: system-ui, sans-serif; margin: 2rem; }
3091
+ table { border-collapse: collapse; width: 100%; }
3092
+ th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; }
3093
+ th { background: #f6f6f6; }
3094
+ </style>
3095
+ </head>
3096
+ <body>
3097
+ <h1>AgentInspect gate</h1>
3098
+ <p>Status: <strong>${result.ok ? "PASS" : "FAIL"}</strong> (exit ${result.exitCode})</p>
3099
+ <h2>Checks</h2>
3100
+ <table>
3101
+ <thead><tr><th>Check</th><th>Status</th><th>Details</th></tr></thead>
3102
+ <tbody>${rows}</tbody>
3103
+ </table>
3104
+ </body>
3105
+ </html>`;
3106
+ }
3107
+ function renderGateJUnit(result) {
3108
+ const failures = result.checks.filter((check) => !check.ok).length;
3109
+ const tests = result.checks.length;
3110
+ const cases = result.checks.map((check) => {
3111
+ if (check.ok) {
3112
+ return ` <testcase name="${escapeXml(check.name)}" classname="gate" />`;
3113
+ }
3114
+ return ` <testcase name="${escapeXml(check.name)}" classname="gate">
3115
+ <failure message="${escapeXml(check.message)}">${escapeXml(check.message)}</failure>
3116
+ </testcase>`;
3117
+ }).join("\n");
3118
+ return `<?xml version="1.0" encoding="UTF-8"?>
3119
+ <testsuites tests="${tests}" failures="${failures}" errors="0" time="0">
3120
+ <testsuite name="agent-inspect-gate" tests="${tests}" failures="${failures}" errors="0" time="0">
3121
+ ${cases}
3122
+ </testsuite>
3123
+ </testsuites>`;
3124
+ }
3125
+ function renderGateReport(result, options = {}) {
3126
+ const format = options.format ?? "markdown";
3127
+ if (format === "json") return JSON.stringify(result, null, 2);
3128
+ if (format === "html") return renderGateReportHtml(result);
3129
+ if (format === "junit") return renderGateJUnit(result);
3130
+ if (format === "github") return renderGateGithubStepSummary(result);
3131
+ return renderGateSummaryMarkdown(result);
3132
+ }
3133
+
3134
+ 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, gateHasThresholds, groupSessionCohorts, isAgentInspectTrace, loadSessionRunRecords, loadSuiteConfig, loadTraceMetadataList, normalizeBundleOutputPath, normalizeSuiteConfig, parseCohortMetricList, parseDurationFilter, parseGateList, parseGateNumber, parseGroupBySpec, renderActivitySummaryHuman, renderCohortReport, renderCohortSummaryMarkdown, renderGateGithubStepSummary, renderGateJUnit, renderGateReport, renderGateSummaryMarkdown, renderSuiteReport, renderSuiteReportMarkdown, renderTraceStats, resolveBundleRunIds, resolveSuiteCaseTrace, resolveSuiteConfigPath, runGate, runSuite, searchTraces, sessionKeyForRun, toMetadataSafeStatus, traceMetasToSessionRunRecords, validateSuiteConfig };
2222
3135
  //# sourceMappingURL=advanced.mjs.map
2223
3136
  //# sourceMappingURL=advanced.mjs.map