agent-inspect 3.2.0 → 3.4.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.
@@ -25,7 +25,7 @@ var process3__default = /*#__PURE__*/_interopDefault(process3);
25
25
  var tty__default = /*#__PURE__*/_interopDefault(tty);
26
26
 
27
27
  // package.json
28
- var version = "3.2.0";
28
+ var version = "3.4.0";
29
29
 
30
30
  // packages/core/src/correlation-metadata.ts
31
31
  var TRACE_CORRELATION_KEYS = [
@@ -3071,12 +3071,12 @@ function buildCriticalPath(runs, handoffs) {
3071
3071
  handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
3072
3072
  );
3073
3073
  const ordered = [...runs].sort(compareRuns);
3074
- const path18 = [];
3074
+ const path19 = [];
3075
3075
  const visited = /* @__PURE__ */ new Set();
3076
3076
  const pushRun = (run, confidence, source) => {
3077
3077
  if (visited.has(run.runId)) return;
3078
3078
  visited.add(run.runId);
3079
- path18.push({
3079
+ path19.push({
3080
3080
  runId: run.runId,
3081
3081
  name: run.name,
3082
3082
  startedAt: run.startedAt,
@@ -3101,7 +3101,7 @@ function buildCriticalPath(runs, handoffs) {
3101
3101
  const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
3102
3102
  pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
3103
3103
  }
3104
- return path18;
3104
+ return path19;
3105
3105
  }
3106
3106
  function metaRunIdMatches(run, token, runById) {
3107
3107
  const meta = extractSessionWorkflowMetadata(run.metadata);
@@ -3214,6 +3214,62 @@ async function isAgentInspectTrace(filePath) {
3214
3214
  }
3215
3215
  }
3216
3216
 
3217
+ // packages/cli/src/trace-dir-scale.ts
3218
+ var TRACE_COUNT_WARN = 1e3;
3219
+ var TRACE_COUNT_SEVERE = 1e4;
3220
+ var LARGE_TRACE_FILE_BYTES = 50 * 1024 * 1024;
3221
+ function buildScaleWarnings(traceCount, largeFileCount) {
3222
+ const warnings = [];
3223
+ if (traceCount >= TRACE_COUNT_SEVERE) {
3224
+ warnings.push(
3225
+ `trace directory has ${traceCount} runs (>= ${TRACE_COUNT_SEVERE}); archive or split traces \u2014 see docs/SCALE-LIMITS.md`
3226
+ );
3227
+ } else if (traceCount >= TRACE_COUNT_WARN) {
3228
+ warnings.push(
3229
+ `trace directory has ${traceCount} runs (>= ${TRACE_COUNT_WARN}); list/search/stats may be slow \u2014 consider agent-inspect index build`
3230
+ );
3231
+ }
3232
+ if (largeFileCount > 0) {
3233
+ warnings.push(
3234
+ `${largeFileCount} trace file(s) exceed ${Math.round(LARGE_TRACE_FILE_BYTES / (1024 * 1024))}MB; open/check/report may be slow`
3235
+ );
3236
+ }
3237
+ return warnings;
3238
+ }
3239
+ async function assessTraceDirectoryScale(td, options = {}) {
3240
+ const files = await td.list();
3241
+ const sample = options.sampleLargeFiles ?? 25;
3242
+ let largeFileCount = 0;
3243
+ if (files.length > 0 && files.length <= sample * 4) {
3244
+ for (const file of files) {
3245
+ try {
3246
+ const stats = await td.getFileStats(file);
3247
+ if (stats.size >= LARGE_TRACE_FILE_BYTES) largeFileCount += 1;
3248
+ } catch {
3249
+ }
3250
+ }
3251
+ } else if (files.length > sample) {
3252
+ for (const file of files.slice(0, sample)) {
3253
+ try {
3254
+ const stats = await td.getFileStats(file);
3255
+ if (stats.size >= LARGE_TRACE_FILE_BYTES) largeFileCount += 1;
3256
+ } catch {
3257
+ }
3258
+ }
3259
+ if (largeFileCount > 0) {
3260
+ largeFileCount = Math.max(largeFileCount, 1);
3261
+ }
3262
+ }
3263
+ const warnings = buildScaleWarnings(files.length, largeFileCount);
3264
+ return { traceCount: files.length, largeFileCount, warnings };
3265
+ }
3266
+ function emitScaleWarnings(assessment, options = {}) {
3267
+ if (options.json || assessment.warnings.length === 0) return;
3268
+ for (const warning of assessment.warnings) {
3269
+ console.error(`[AgentInspect] warning: ${warning}`);
3270
+ }
3271
+ }
3272
+
3217
3273
  // packages/cli/src/list.ts
3218
3274
  function parseLimit(raw) {
3219
3275
  const fallback = 20;
@@ -3248,6 +3304,8 @@ async function list(options = {}) {
3248
3304
  parseDuration(options.since.trim());
3249
3305
  }
3250
3306
  const files = await td.list();
3307
+ const scale = await assessTraceDirectoryScale(td);
3308
+ emitScaleWarnings(scale, { json: options.json });
3251
3309
  if (files.length === 0) {
3252
3310
  if (options.json) {
3253
3311
  console.log("[]");
@@ -8344,13 +8402,13 @@ function pairSteps(left, right) {
8344
8402
  return pairs;
8345
8403
  }
8346
8404
  function compareLeafSteps(L, R, segments, opts, out) {
8347
- const path18 = buildPath(segments);
8405
+ const path19 = buildPath(segments);
8348
8406
  if (L.name !== R.name) {
8349
8407
  out.push({
8350
8408
  kind: "structure",
8351
8409
  severity: "warning",
8352
8410
  message: "Step name differs",
8353
- path: path18,
8411
+ path: path19,
8354
8412
  left: L.name,
8355
8413
  right: R.name
8356
8414
  });
@@ -8360,7 +8418,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8360
8418
  kind: "step-type",
8361
8419
  severity: "warning",
8362
8420
  message: "Step type differs",
8363
- path: path18,
8421
+ path: path19,
8364
8422
  left: L.type,
8365
8423
  right: R.type
8366
8424
  });
@@ -8370,7 +8428,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8370
8428
  kind: "step-status",
8371
8429
  severity: "warning",
8372
8430
  message: "Step status differs",
8373
- path: path18,
8431
+ path: path19,
8374
8432
  left: L.status,
8375
8433
  right: R.status
8376
8434
  });
@@ -8382,7 +8440,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8382
8440
  kind: "error",
8383
8441
  severity: "error",
8384
8442
  message: "Step error message differs",
8385
- path: path18,
8443
+ path: path19,
8386
8444
  left: le || void 0,
8387
8445
  right: re || void 0
8388
8446
  });
@@ -8400,20 +8458,20 @@ function compareLeafSteps(L, R, segments, opts, out) {
8400
8458
  kind: "duration",
8401
8459
  severity: "info",
8402
8460
  message: "Step duration differs",
8403
- path: path18,
8461
+ path: path19,
8404
8462
  left: ld,
8405
8463
  right: rd
8406
8464
  });
8407
8465
  }
8408
8466
  }
8409
8467
  const lm = stableJson(L.metadata ?? {});
8410
- const rm = stableJson(R.metadata ?? {});
8411
- if (lm !== rm) {
8468
+ const rm2 = stableJson(R.metadata ?? {});
8469
+ if (lm !== rm2) {
8412
8470
  out.push({
8413
8471
  kind: "metadata",
8414
8472
  severity: "info",
8415
8473
  message: "Step metadata differs",
8416
- path: path18,
8474
+ path: path19,
8417
8475
  left: L.metadata,
8418
8476
  right: R.metadata
8419
8477
  });
@@ -8425,7 +8483,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8425
8483
  kind: "output",
8426
8484
  severity: "info",
8427
8485
  message: "Output preview differs",
8428
- path: path18,
8486
+ path: path19,
8429
8487
  left: L.outputPreview,
8430
8488
  right: R.outputPreview
8431
8489
  });
@@ -8585,11 +8643,11 @@ function diffRuns(left, right, options) {
8585
8643
  }
8586
8644
 
8587
8645
  // packages/core/src/diff/renderer.ts
8588
- function formatPath(path18) {
8589
- if (path18 === void 0 || path18.path.length === 0) {
8646
+ function formatPath(path19) {
8647
+ if (path19 === void 0 || path19.path.length === 0) {
8590
8648
  return "(run)";
8591
8649
  }
8592
- return path18.path.map((s) => s.name).join(" > ");
8650
+ return path19.path.map((s) => s.name).join(" > ");
8593
8651
  }
8594
8652
  function formatValue(v, verbose) {
8595
8653
  if (v === void 0) return "(undefined)";
@@ -8815,6 +8873,8 @@ async function statsCommand(options = {}) {
8815
8873
  parseDuration(options.since.trim());
8816
8874
  }
8817
8875
  const files = await td.list();
8876
+ const scale = await assessTraceDirectoryScale(td);
8877
+ emitScaleWarnings(scale, { json: options.json });
8818
8878
  if (files.length === 0) {
8819
8879
  if (options.json) {
8820
8880
  console.log(
@@ -8885,6 +8945,8 @@ async function searchCommand(options = {}) {
8885
8945
  parseDurationFilter(options.duration);
8886
8946
  }
8887
8947
  const files = await td.list();
8948
+ const scale = await assessTraceDirectoryScale(td);
8949
+ emitScaleWarnings(scale, { json: options.json });
8888
8950
  let metas = await loadTraceMetadataList(
8889
8951
  traceDir,
8890
8952
  files,
@@ -9467,17 +9529,17 @@ function applyRule(rule, value, replacement) {
9467
9529
  }
9468
9530
  return value;
9469
9531
  }
9470
- function childPath(path18, key) {
9532
+ function childPath(path19, key) {
9471
9533
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
9472
- return path18 ? `${path18}.${key}` : key;
9534
+ return path19 ? `${path19}.${key}` : key;
9473
9535
  }
9474
- return `${path18 || "$"}[${JSON.stringify(key)}]`;
9536
+ return `${path19 || "$"}[${JSON.stringify(key)}]`;
9475
9537
  }
9476
- function indexPath(path18, index) {
9477
- return `${path18 || "$"}[${index}]`;
9538
+ function indexPath(path19, index) {
9539
+ return `${path19 || "$"}[${index}]`;
9478
9540
  }
9479
- function makeFinding(path18, detector, action, matchKind, severity = "warning", preview) {
9480
- return preview === void 0 ? { path: path18, detector, action, severity, matchKind } : { path: path18, detector, action, severity, matchKind, preview };
9541
+ function makeFinding(path19, detector, action, matchKind, severity = "warning", preview) {
9542
+ return preview === void 0 ? { path: path19, detector, action, severity, matchKind } : { path: path19, detector, action, severity, matchKind, preview };
9481
9543
  }
9482
9544
  function createRedactionProfile(profile = "local") {
9483
9545
  switch (profile) {
@@ -9546,11 +9608,11 @@ var Redactor2 = class {
9546
9608
  #recordFinding(state, finding) {
9547
9609
  if (this.#collectFindings) state.findings.push(finding);
9548
9610
  }
9549
- #redactValue(value, key, path18, depth, state) {
9611
+ #redactValue(value, key, path19, depth, state) {
9550
9612
  if (depth > this.#maxDepth) {
9551
9613
  this.#recordFinding(
9552
9614
  state,
9553
- makeFinding(path18, "structure.maxDepth", "truncate", "value", "warning")
9615
+ makeFinding(path19, "structure.maxDepth", "truncate", "value", "warning")
9554
9616
  );
9555
9617
  return "[Truncated]";
9556
9618
  }
@@ -9559,19 +9621,19 @@ var Redactor2 = class {
9559
9621
  if (rule) {
9560
9622
  this.#recordFinding(
9561
9623
  state,
9562
- makeFinding(path18, `key.${rule.key}`, actionForRule(rule), "key", "warning")
9624
+ makeFinding(path19, `key.${rule.key}`, actionForRule(rule), "key", "warning")
9563
9625
  );
9564
9626
  return applyRule(rule, value, this.#replacement);
9565
9627
  }
9566
9628
  }
9567
9629
  for (const detector of this.#detectors) {
9568
- const detections = detector.detect({ path: path18, key, value });
9630
+ const detections = detector.detect({ path: path19, key, value });
9569
9631
  for (const detection of detections) {
9570
9632
  const action = detection.action ?? "replace";
9571
9633
  this.#recordFinding(
9572
9634
  state,
9573
9635
  makeFinding(
9574
- path18,
9636
+ path19,
9575
9637
  detector.id,
9576
9638
  action,
9577
9639
  detection.matchKind ?? detector.matchKind ?? "custom",
@@ -9589,7 +9651,7 @@ var Redactor2 = class {
9589
9651
  const out = [];
9590
9652
  state.seen.set(value, out);
9591
9653
  value.forEach((item, index) => {
9592
- out[index] = this.#redactValue(item, void 0, indexPath(path18, index), depth + 1, state);
9654
+ out[index] = this.#redactValue(item, void 0, indexPath(path19, index), depth + 1, state);
9593
9655
  });
9594
9656
  return out;
9595
9657
  }
@@ -9601,7 +9663,7 @@ var Redactor2 = class {
9601
9663
  out[entryKey] = this.#redactValue(
9602
9664
  entryValue,
9603
9665
  entryKey,
9604
- childPath(path18 === "$" ? "" : path18, entryKey),
9666
+ childPath(path19 === "$" ? "" : path19, entryKey),
9605
9667
  depth + 1,
9606
9668
  state
9607
9669
  );
@@ -10473,7 +10535,7 @@ function stripPrefix(name, prefixes) {
10473
10535
  }
10474
10536
  return name;
10475
10537
  }
10476
- function eventEvidence(event, path18) {
10538
+ function eventEvidence(event, path19) {
10477
10539
  return {
10478
10540
  runId: event.runId,
10479
10541
  eventId: event.eventId,
@@ -10483,7 +10545,7 @@ function eventEvidence(event, path18) {
10483
10545
  kind: event.kind,
10484
10546
  name: event.name,
10485
10547
  status: event.status,
10486
- ...path18 ? { path: path18 } : {}
10548
+ ...path19 ? { path: path19 } : {}
10487
10549
  };
10488
10550
  }
10489
10551
  function runEvidence(run) {
@@ -10546,9 +10608,9 @@ function eventEndMs(event) {
10546
10608
  function normalizedKey(value) {
10547
10609
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
10548
10610
  }
10549
- function lastPathSegment(path18) {
10550
- const parts = path18.split(".");
10551
- return parts[parts.length - 1] ?? path18;
10611
+ function lastPathSegment(path19) {
10612
+ const parts = path19.split(".");
10613
+ return parts[parts.length - 1] ?? path19;
10552
10614
  }
10553
10615
  function valueType(value) {
10554
10616
  if (Array.isArray(value)) return "array";
@@ -10562,12 +10624,12 @@ function serializedByteLength(value) {
10562
10624
  return void 0;
10563
10625
  }
10564
10626
  }
10565
- function pushValueEntries(entries, event, value, path18, key, depth = 0) {
10566
- entries.push({ event, path: path18, key, value });
10627
+ function pushValueEntries(entries, event, value, path19, key, depth = 0) {
10628
+ entries.push({ event, path: path19, key, value });
10567
10629
  if (depth >= 8) return;
10568
10630
  if (Array.isArray(value)) {
10569
10631
  for (const [index, item] of value.entries()) {
10570
- pushValueEntries(entries, event, item, `${path18}.${index}`, String(index), depth + 1);
10632
+ pushValueEntries(entries, event, item, `${path19}.${index}`, String(index), depth + 1);
10571
10633
  }
10572
10634
  return;
10573
10635
  }
@@ -10577,7 +10639,7 @@ function pushValueEntries(entries, event, value, path18, key, depth = 0) {
10577
10639
  entries,
10578
10640
  event,
10579
10641
  value[nestedKey],
10580
- `${path18}.${nestedKey}`,
10642
+ `${path19}.${nestedKey}`,
10581
10643
  nestedKey,
10582
10644
  depth + 1
10583
10645
  );
@@ -10658,9 +10720,9 @@ function eventDurationMs(event) {
10658
10720
  }
10659
10721
  function treeShape(nodes) {
10660
10722
  const lines = [];
10661
- const visit = (node, path18) => {
10662
- lines.push(`${path18}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
10663
- node.children.forEach((child, index) => visit(child, `${path18}.${index}`));
10723
+ const visit = (node, path19) => {
10724
+ lines.push(`${path19}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
10725
+ node.children.forEach((child, index) => visit(child, `${path19}.${index}`));
10664
10726
  };
10665
10727
  nodes.forEach((node, index) => visit(node, String(index)));
10666
10728
  return lines;
@@ -10709,9 +10771,9 @@ function retrievalShape(context) {
10709
10771
  function guardrailShape(context) {
10710
10772
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
10711
10773
  }
10712
- function firstEvidenceForKind(context, kind, path18) {
10774
+ function firstEvidenceForKind(context, kind, path19) {
10713
10775
  const event = context.events.find((candidate) => candidate.kind === kind);
10714
- return event ? [eventEvidence(event, path18)] : runEvidence(context.selectedRun);
10776
+ return event ? [eventEvidence(event, path19)] : runEvidence(context.selectedRun);
10715
10777
  }
10716
10778
  function baselineDiffFinding(message, evidence, expected, actual) {
10717
10779
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -10775,6 +10837,108 @@ function createRunDurationRule(options) {
10775
10837
  }
10776
10838
  };
10777
10839
  }
10840
+ function createMaxStepDurationRule(options) {
10841
+ return {
10842
+ id: "run.maxStepDuration",
10843
+ category: "run",
10844
+ defaultSeverity: "error",
10845
+ evaluate(context) {
10846
+ const over = context.events.filter((event) => {
10847
+ const duration = eventDurationMs(event);
10848
+ return duration !== void 0 && duration > options.maxDurationMs;
10849
+ });
10850
+ if (over.length === 0) return [];
10851
+ return [
10852
+ failFinding(
10853
+ "run.maxStepDuration",
10854
+ `${over.length} step(s) exceeded max duration ${options.maxDurationMs}ms.`,
10855
+ over.map((event) => eventEvidence(event, "durationMs")),
10856
+ { maxDurationMs: options.maxDurationMs },
10857
+ over.map((event) => ({
10858
+ eventId: event.eventId,
10859
+ name: event.name,
10860
+ durationMs: eventDurationMs(event)
10861
+ }))
10862
+ )
10863
+ ];
10864
+ }
10865
+ };
10866
+ }
10867
+ function createStallDetectionRule(options = {}) {
10868
+ const requireEndedAt = options.requireEndedAt === true;
10869
+ return {
10870
+ id: "run.stall",
10871
+ category: "run",
10872
+ defaultSeverity: "warning",
10873
+ evaluate(context) {
10874
+ const findings = [];
10875
+ const running = context.events.filter((event) => event.status === "running");
10876
+ if (running.length > 0) {
10877
+ findings.push(
10878
+ failFinding(
10879
+ "run.stall",
10880
+ `Found ${running.length} event(s) still running (possible stall).`,
10881
+ running.map((event) => eventEvidence(event, "status")),
10882
+ "no running events",
10883
+ running.length
10884
+ )
10885
+ );
10886
+ }
10887
+ if (requireEndedAt) {
10888
+ const incomplete = context.events.filter(
10889
+ (event) => event.startedAt !== void 0 && event.endedAt === void 0 && event.status !== "running"
10890
+ );
10891
+ if (incomplete.length > 0) {
10892
+ findings.push(
10893
+ failFinding(
10894
+ "run.stall",
10895
+ `Found ${incomplete.length} started event(s) without endedAt.`,
10896
+ incomplete.map((event) => eventEvidence(event, "endedAt")),
10897
+ "endedAt for started events",
10898
+ incomplete.length
10899
+ )
10900
+ );
10901
+ }
10902
+ }
10903
+ return findings;
10904
+ }
10905
+ };
10906
+ }
10907
+ function createRequireCompletedRule() {
10908
+ return {
10909
+ id: "run.requireCompleted",
10910
+ category: "run",
10911
+ defaultSeverity: "error",
10912
+ evaluate(context) {
10913
+ const findings = [];
10914
+ const runStatus = context.selectedRun?.status;
10915
+ if (runStatus === "running") {
10916
+ findings.push(
10917
+ failFinding(
10918
+ "run.requireCompleted",
10919
+ "Run is still running.",
10920
+ runEvidence(context.selectedRun),
10921
+ "completed run",
10922
+ runStatus
10923
+ )
10924
+ );
10925
+ }
10926
+ const running = context.events.filter((event) => event.status === "running");
10927
+ if (running.length > 0) {
10928
+ findings.push(
10929
+ failFinding(
10930
+ "run.requireCompleted",
10931
+ `Run has ${running.length} incomplete running event(s).`,
10932
+ running.map((event) => eventEvidence(event, "status")),
10933
+ "no running events",
10934
+ running.length
10935
+ )
10936
+ );
10937
+ }
10938
+ return findings;
10939
+ }
10940
+ };
10941
+ }
10778
10942
  function createRunDepthRule(options) {
10779
10943
  return {
10780
10944
  id: "run.depth",
@@ -10959,13 +11123,13 @@ function createStructureCycleRule() {
10959
11123
  const seenCycles = /* @__PURE__ */ new Set();
10960
11124
  const findings = [];
10961
11125
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
10962
- const path18 = [];
11126
+ const path19 = [];
10963
11127
  const seenAt = /* @__PURE__ */ new Map();
10964
11128
  let current = event;
10965
11129
  while (current) {
10966
11130
  const existing = seenAt.get(current.eventId);
10967
11131
  if (existing !== void 0) {
10968
- const cycle = path18.slice(existing);
11132
+ const cycle = path19.slice(existing);
10969
11133
  const key = cycle.map((item) => item.eventId).sort().join("\0");
10970
11134
  if (!seenCycles.has(key)) {
10971
11135
  seenCycles.add(key);
@@ -10981,8 +11145,8 @@ function createStructureCycleRule() {
10981
11145
  }
10982
11146
  break;
10983
11147
  }
10984
- seenAt.set(current.eventId, path18.length);
10985
- path18.push(current);
11148
+ seenAt.set(current.eventId, path19.length);
11149
+ path19.push(current);
10986
11150
  current = current.parentId ? byId.get(current.parentId) : void 0;
10987
11151
  }
10988
11152
  }
@@ -11779,23 +11943,23 @@ function evaluatePromptInjection(text, options = {}) {
11779
11943
  }
11780
11944
  return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
11781
11945
  }
11782
- function validateSchemaField(value, field, path18, evidence) {
11946
+ function validateSchemaField(value, field, path19, evidence) {
11783
11947
  const ruleId = "guardrail.structured-output";
11784
11948
  if (field.type) {
11785
11949
  const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
11786
11950
  if (actual !== field.type) {
11787
- evidence.push({ ruleId, path: path18, preview: `expected ${field.type}, got ${actual}` });
11951
+ evidence.push({ ruleId, path: path19, preview: `expected ${field.type}, got ${actual}` });
11788
11952
  return;
11789
11953
  }
11790
11954
  }
11791
11955
  if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
11792
- evidence.push({ ruleId, path: path18, preview: "value not in enum" });
11956
+ evidence.push({ ruleId, path: path19, preview: "value not in enum" });
11793
11957
  }
11794
11958
  if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
11795
11959
  const record = value;
11796
11960
  for (const key of field.required) {
11797
11961
  if (!(key in record)) {
11798
- evidence.push({ ruleId, path: `${path18}.${key}`, preview: "missing required key" });
11962
+ evidence.push({ ruleId, path: `${path19}.${key}`, preview: "missing required key" });
11799
11963
  }
11800
11964
  }
11801
11965
  }
@@ -12156,6 +12320,17 @@ function buildRules(config, options) {
12156
12320
  const safety = checks2.safety ?? {};
12157
12321
  const maxDurationMs = parseNumber(options.maxDurationMs, "--max-duration-ms") ?? run.maxDurationMs;
12158
12322
  const maxTotalTokens = parseNumber(options.maxTotalTokens, "--max-total-tokens") ?? llm.maxTotalTokens;
12323
+ let maxStepDurationMs;
12324
+ if (options.maxStepDuration !== void 0) {
12325
+ try {
12326
+ maxStepDurationMs = parseDuration(options.maxStepDuration.trim());
12327
+ } catch (error) {
12328
+ const message = error instanceof Error ? error.message : String(error);
12329
+ diagnostics.push(
12330
+ diagnostic2("AI_CHECK_INVALID_ARGUMENTS", `--max-step-duration: ${message}`)
12331
+ );
12332
+ }
12333
+ }
12159
12334
  const rules = [
12160
12335
  createRunStatusRule(run),
12161
12336
  createStructureOrphanRule(),
@@ -12166,6 +12341,15 @@ function buildRules(config, options) {
12166
12341
  if (maxDurationMs !== void 0) {
12167
12342
  rules.push(createRunDurationRule({ maxDurationMs }));
12168
12343
  }
12344
+ if (maxStepDurationMs !== void 0) {
12345
+ rules.push(createMaxStepDurationRule({ maxDurationMs: maxStepDurationMs }));
12346
+ }
12347
+ if (options.requireCompleted) {
12348
+ rules.push(createRequireCompletedRule());
12349
+ }
12350
+ if (options.detectStalls) {
12351
+ rules.push(createStallDetectionRule({ requireEndedAt: true }));
12352
+ }
12169
12353
  if (run.maxDepth !== void 0) {
12170
12354
  rules.push(createRunDepthRule({ maxDepth: run.maxDepth }));
12171
12355
  }
@@ -12260,10 +12444,10 @@ function printHuman(result) {
12260
12444
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
12261
12445
  }
12262
12446
  for (const finding of result.findings) {
12263
- const path18 = finding.evidence[0]?.path;
12447
+ const path19 = finding.evidence[0]?.path;
12264
12448
  const run = finding.evidence[0]?.runId;
12265
12449
  const runPrefix = run ? `[${run}] ` : "";
12266
- console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
12450
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
12267
12451
  }
12268
12452
  }
12269
12453
  function readErrorResult(error) {
@@ -12801,10 +12985,10 @@ async function evalRun(input3, options = {}) {
12801
12985
  diagnostics: []
12802
12986
  };
12803
12987
  }
12804
- function evidenceForRun(run, path18) {
12805
- return [{ runId: run.runId, ...path18 !== void 0 ? { path: path18 } : {} }];
12988
+ function evidenceForRun(run, path19) {
12989
+ return [{ runId: run.runId, ...path19 !== void 0 ? { path: path19 } : {} }];
12806
12990
  }
12807
- function evidenceForEvent(event, path18) {
12991
+ function evidenceForEvent(event, path19) {
12808
12992
  return [
12809
12993
  {
12810
12994
  runId: event.runId,
@@ -12812,7 +12996,7 @@ function evidenceForEvent(event, path18) {
12812
12996
  ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
12813
12997
  kind: event.kind,
12814
12998
  name: event.name,
12815
- ...path18 !== void 0 ? { path: path18 } : {}
12999
+ ...path19 !== void 0 ? { path: path19 } : {}
12816
13000
  }
12817
13001
  ];
12818
13002
  }
@@ -12970,9 +13154,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
12970
13154
  function tokenize(text) {
12971
13155
  return [...text.toLowerCase().matchAll(/[a-z0-9][a-z0-9'-]{2,}/g)].map((match) => match[0].replace(/^['-]+|['-]+$/g, "")).filter((token) => token.length > 2 && !STOP_WORDS.has(token));
12972
13156
  }
12973
- function firstEvidence(fields, run, path18) {
13157
+ function firstEvidence(fields, run, path19) {
12974
13158
  const first = fields[0];
12975
- return first === void 0 ? evidenceForRun(run, path18) : evidenceForEvent(first.node.event, first.path);
13159
+ return first === void 0 ? evidenceForRun(run, path19) : evidenceForEvent(first.node.event, first.path);
12976
13160
  }
12977
13161
  function collectSourceIds(nodes, keys) {
12978
13162
  const wanted = keySet(keys);
@@ -13349,8 +13533,8 @@ function renderEvalMarkdown(result) {
13349
13533
  if (result.findings.length > 0) {
13350
13534
  lines.push("", "## Findings");
13351
13535
  for (const finding of result.findings) {
13352
- const path18 = finding.evidence[0]?.path;
13353
- lines.push(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
13536
+ const path19 = finding.evidence[0]?.path;
13537
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13354
13538
  }
13355
13539
  }
13356
13540
  return `${lines.join("\n")}
@@ -13543,8 +13727,8 @@ function printHuman2(result) {
13543
13727
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
13544
13728
  }
13545
13729
  for (const finding of result.findings) {
13546
- const path18 = finding.evidence[0]?.path;
13547
- console.log(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
13730
+ const path19 = finding.evidence[0]?.path;
13731
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13548
13732
  }
13549
13733
  }
13550
13734
  function readErrorResult2(error) {
@@ -13782,8 +13966,8 @@ function printHuman3(result) {
13782
13966
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
13783
13967
  }
13784
13968
  for (const finding of result.findings) {
13785
- const path18 = finding.evidence[0]?.path;
13786
- console.log(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
13969
+ const path19 = finding.evidence[0]?.path;
13970
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13787
13971
  }
13788
13972
  console.log(`Note: ${result.note}`);
13789
13973
  }
@@ -13902,8 +14086,8 @@ function renderCheckSection(result) {
13902
14086
  `Diagnostics: ${result.diagnostics.length}`
13903
14087
  ];
13904
14088
  for (const finding of result.findings.slice(0, 10)) {
13905
- const path18 = finding.evidence[0]?.path ?? "(run)";
13906
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path18})`);
14089
+ const path19 = finding.evidence[0]?.path ?? "(run)";
14090
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path19})`);
13907
14091
  }
13908
14092
  for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
13909
14093
  lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
@@ -14905,6 +15089,174 @@ Summary: ${failed} failed, ${warned} warnings`);
14905
15089
  if (failed > 0) process3__default.default.exitCode = 1;
14906
15090
  }
14907
15091
 
15092
+ // packages/adapter-sdk/src/indexer.ts
15093
+ function defineIndexer(indexer) {
15094
+ if (!indexer.id.trim()) throw new Error("indexer id is required");
15095
+ return indexer;
15096
+ }
15097
+ async function indexIsStale(snapshot, traceDir) {
15098
+ const builtMs = Date.parse(snapshot.builtAt);
15099
+ if (Number.isNaN(builtMs)) return true;
15100
+ const td = new TraceDirectory({ dir: traceDir });
15101
+ const files = await td.list();
15102
+ for (const file of files) {
15103
+ const stats = await td.getFileStats(file);
15104
+ if (stats.mtimeMs > builtMs) return true;
15105
+ }
15106
+ return false;
15107
+ }
15108
+ function createTraceDirectoryIndexer() {
15109
+ return defineIndexer({
15110
+ id: "trace-directory-metadata",
15111
+ async rebuild(traceDir, options = {}) {
15112
+ const warnings = [];
15113
+ const td = new TraceDirectory({ dir: traceDir });
15114
+ const files = await td.list();
15115
+ const maxEntries = options.maxEntries ?? 1e4;
15116
+ if (files.length > maxEntries) {
15117
+ warnings.push(
15118
+ `indexer.truncated: trace directory has ${files.length} files; indexing first ${maxEntries}`
15119
+ );
15120
+ }
15121
+ const slice = files.slice(0, maxEntries);
15122
+ const metas = await loadTraceMetadataList(
15123
+ traceDir,
15124
+ slice,
15125
+ (fileName) => td.getPath(fileName)
15126
+ );
15127
+ const entries = metas.map((meta) => ({
15128
+ runId: meta.runId,
15129
+ path: meta.filePath,
15130
+ name: meta.name,
15131
+ startedAt: meta.startedAt,
15132
+ status: meta.status
15133
+ })).sort((a, b) => a.runId.localeCompare(b.runId));
15134
+ if (entries.length < slice.length) {
15135
+ warnings.push(
15136
+ `indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
15137
+ );
15138
+ }
15139
+ return {
15140
+ traceDir,
15141
+ builtAt: (/* @__PURE__ */ new Date()).toISOString(),
15142
+ entries,
15143
+ warnings
15144
+ };
15145
+ }
15146
+ });
15147
+ }
15148
+
15149
+ // packages/cli/src/index-cmd.ts
15150
+ var INDEX_FILENAME = ".agent-inspect-index.json";
15151
+ function traceIndexPath(traceDir) {
15152
+ return path14__default.default.join(traceDir, INDEX_FILENAME);
15153
+ }
15154
+ function parseMaxEntries(raw) {
15155
+ if (raw === void 0 || raw.trim() === "") return void 0;
15156
+ const parsed = Number.parseInt(raw, 10);
15157
+ if (!Number.isFinite(parsed) || parsed <= 0) {
15158
+ throw new Error("--max-entries must be a positive integer.");
15159
+ }
15160
+ return parsed;
15161
+ }
15162
+ async function readSnapshot(indexPath2) {
15163
+ try {
15164
+ const raw = await promises.readFile(indexPath2, "utf8");
15165
+ return JSON.parse(raw);
15166
+ } catch {
15167
+ return void 0;
15168
+ }
15169
+ }
15170
+ async function indexBuildCommand(options = {}) {
15171
+ try {
15172
+ const traceDir = resolveTraceDir({ dir: options.dir });
15173
+ await promises.mkdir(traceDir, { recursive: true });
15174
+ const indexer = createTraceDirectoryIndexer();
15175
+ const snapshot = await indexer.rebuild(traceDir, {
15176
+ maxEntries: parseMaxEntries(options.maxEntries)
15177
+ });
15178
+ const indexPath2 = traceIndexPath(traceDir);
15179
+ await promises.writeFile(indexPath2, `${JSON.stringify(snapshot, null, 2)}
15180
+ `, "utf8");
15181
+ if (options.json) {
15182
+ console.log(JSON.stringify({ ok: true, indexPath: indexPath2, ...snapshot }, null, 2));
15183
+ return;
15184
+ }
15185
+ console.log(`Built trace index: ${indexPath2}`);
15186
+ console.log(`Entries: ${snapshot.entries.length}`);
15187
+ if (snapshot.warnings.length > 0) {
15188
+ for (const warning of snapshot.warnings) {
15189
+ console.log(`warning: ${warning}`);
15190
+ }
15191
+ }
15192
+ } catch (e) {
15193
+ const msg = e instanceof Error ? e.message : String(e);
15194
+ console.error(`[AgentInspect] index build failed: ${msg}`);
15195
+ process.exitCode = 1;
15196
+ }
15197
+ }
15198
+ async function indexStatusCommand(options = {}) {
15199
+ try {
15200
+ const traceDir = resolveTraceDir({ dir: options.dir });
15201
+ const indexPath2 = traceIndexPath(traceDir);
15202
+ const snapshot = await readSnapshot(indexPath2);
15203
+ if (!snapshot) {
15204
+ const payload2 = { ok: true, exists: false, indexPath: indexPath2, traceDir, stale: true };
15205
+ if (options.json) {
15206
+ console.log(JSON.stringify(payload2, null, 2));
15207
+ } else {
15208
+ console.log(`No index at ${indexPath2}`);
15209
+ console.log("Run: agent-inspect index build");
15210
+ }
15211
+ return;
15212
+ }
15213
+ const stale = await indexIsStale(snapshot, traceDir);
15214
+ const payload = {
15215
+ ok: true,
15216
+ exists: true,
15217
+ indexPath: indexPath2,
15218
+ traceDir,
15219
+ stale,
15220
+ builtAt: snapshot.builtAt,
15221
+ entries: snapshot.entries.length,
15222
+ warnings: snapshot.warnings
15223
+ };
15224
+ if (options.json) {
15225
+ console.log(JSON.stringify(payload, null, 2));
15226
+ return;
15227
+ }
15228
+ console.log(`Index: ${indexPath2}`);
15229
+ console.log(`Built: ${snapshot.builtAt}`);
15230
+ console.log(`Entries: ${snapshot.entries.length}`);
15231
+ console.log(`Stale: ${stale ? "yes" : "no"}`);
15232
+ if (snapshot.warnings.length > 0) {
15233
+ for (const warning of snapshot.warnings) {
15234
+ console.log(`warning: ${warning}`);
15235
+ }
15236
+ }
15237
+ } catch (e) {
15238
+ const msg = e instanceof Error ? e.message : String(e);
15239
+ console.error(`[AgentInspect] index status failed: ${msg}`);
15240
+ process.exitCode = 1;
15241
+ }
15242
+ }
15243
+ async function indexCleanCommand(options = {}) {
15244
+ try {
15245
+ const traceDir = resolveTraceDir({ dir: options.dir });
15246
+ const indexPath2 = traceIndexPath(traceDir);
15247
+ await promises.rm(indexPath2, { force: true });
15248
+ if (options.json) {
15249
+ console.log(JSON.stringify({ ok: true, removed: indexPath2 }, null, 2));
15250
+ return;
15251
+ }
15252
+ console.log(`Removed index: ${indexPath2}`);
15253
+ } catch (e) {
15254
+ const msg = e instanceof Error ? e.message : String(e);
15255
+ console.error(`[AgentInspect] index clean failed: ${msg}`);
15256
+ process.exitCode = 1;
15257
+ }
15258
+ }
15259
+
14908
15260
  // packages/cli/src/index.ts
14909
15261
  function runCommand(action) {
14910
15262
  void action().catch((error) => {
@@ -15029,7 +15381,10 @@ function createCliProgram() {
15029
15381
  ]).option("--allowed-model <model>", "allow an LLM model (repeatable)", (value, previous = []) => [
15030
15382
  ...previous,
15031
15383
  value
15032
- ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").option("--session <id>", "check all runs in a workflow session (requires --dir)").option("--group <id>", "check all runs sharing a groupId (requires --dir)").option(
15384
+ ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").option(
15385
+ "--max-step-duration <duration>",
15386
+ "add run.maxStepDuration (e.g. 30s, 5m)"
15387
+ ).option("--require-completed", "add run.requireCompleted").option("--detect-stalls", "add run.stall for running or incomplete events").option("--session <id>", "check all runs in a workflow session (requires --dir)").option("--group <id>", "check all runs sharing a groupId (requires --dir)").option(
15033
15388
  "--correlate-group",
15034
15389
  "when using --session, also match synthetic group: session keys"
15035
15390
  ).option(
@@ -15223,6 +15578,16 @@ function createCliProgram() {
15223
15578
  ).action((opts) => {
15224
15579
  runCommand(() => doctorCommand(opts));
15225
15580
  });
15581
+ const indexCmd = program.command("index").description("Optional local trace directory index (rebuildable metadata cache)");
15582
+ indexCmd.command("build").description("Build or refresh .agent-inspect-index.json").option("--dir <path>", "trace directory").option("--json", "print JSON result").option("--max-entries <n>", "cap indexed entries (default 10000)").action((opts) => {
15583
+ runCommand(() => indexBuildCommand(opts));
15584
+ });
15585
+ indexCmd.command("status").description("Show index freshness and entry count").option("--dir <path>", "trace directory").option("--json", "print JSON result").action((opts) => {
15586
+ runCommand(() => indexStatusCommand(opts));
15587
+ });
15588
+ indexCmd.command("clean").description("Remove the local index file").option("--dir <path>", "trace directory").option("--json", "print JSON result").action((opts) => {
15589
+ runCommand(() => indexCleanCommand(opts));
15590
+ });
15226
15591
  return program;
15227
15592
  }
15228
15593
  function isPrimaryModule() {