agent-inspect 3.3.0 → 3.5.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.
@@ -5,7 +5,7 @@ import { fileURLToPath, pathToFileURL } from 'url';
5
5
  import { Command, Option } from 'commander';
6
6
  import { AsyncLocalStorage } from 'async_hooks';
7
7
  import crypto, { webcrypto, createHash } from 'crypto';
8
- import { unlink, stat, mkdir, writeFile, appendFile, readdir, readFile, access, open, constants } from 'fs/promises';
8
+ import { unlink, stat, mkdir, writeFile, appendFile, rm, readdir, readFile, access, open, constants } from 'fs/promises';
9
9
  import os from 'os';
10
10
  import process3, { stdin, stdout } from 'process';
11
11
  import tty from 'tty';
@@ -14,7 +14,7 @@ import { createServer } from 'http';
14
14
  import { createRequire } from 'module';
15
15
 
16
16
  // package.json
17
- var version = "3.3.0";
17
+ var version = "3.5.0";
18
18
 
19
19
  // packages/core/src/correlation-metadata.ts
20
20
  var TRACE_CORRELATION_KEYS = [
@@ -3060,12 +3060,12 @@ function buildCriticalPath(runs, handoffs) {
3060
3060
  handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
3061
3061
  );
3062
3062
  const ordered = [...runs].sort(compareRuns);
3063
- const path18 = [];
3063
+ const path19 = [];
3064
3064
  const visited = /* @__PURE__ */ new Set();
3065
3065
  const pushRun = (run, confidence, source) => {
3066
3066
  if (visited.has(run.runId)) return;
3067
3067
  visited.add(run.runId);
3068
- path18.push({
3068
+ path19.push({
3069
3069
  runId: run.runId,
3070
3070
  name: run.name,
3071
3071
  startedAt: run.startedAt,
@@ -3090,7 +3090,7 @@ function buildCriticalPath(runs, handoffs) {
3090
3090
  const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
3091
3091
  pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
3092
3092
  }
3093
- return path18;
3093
+ return path19;
3094
3094
  }
3095
3095
  function metaRunIdMatches(run, token, runById) {
3096
3096
  const meta = extractSessionWorkflowMetadata(run.metadata);
@@ -3203,6 +3203,62 @@ async function isAgentInspectTrace(filePath) {
3203
3203
  }
3204
3204
  }
3205
3205
 
3206
+ // packages/cli/src/trace-dir-scale.ts
3207
+ var TRACE_COUNT_WARN = 1e3;
3208
+ var TRACE_COUNT_SEVERE = 1e4;
3209
+ var LARGE_TRACE_FILE_BYTES = 50 * 1024 * 1024;
3210
+ function buildScaleWarnings(traceCount, largeFileCount) {
3211
+ const warnings = [];
3212
+ if (traceCount >= TRACE_COUNT_SEVERE) {
3213
+ warnings.push(
3214
+ `trace directory has ${traceCount} runs (>= ${TRACE_COUNT_SEVERE}); archive or split traces \u2014 see docs/SCALE-LIMITS.md`
3215
+ );
3216
+ } else if (traceCount >= TRACE_COUNT_WARN) {
3217
+ warnings.push(
3218
+ `trace directory has ${traceCount} runs (>= ${TRACE_COUNT_WARN}); list/search/stats may be slow \u2014 consider agent-inspect index build`
3219
+ );
3220
+ }
3221
+ if (largeFileCount > 0) {
3222
+ warnings.push(
3223
+ `${largeFileCount} trace file(s) exceed ${Math.round(LARGE_TRACE_FILE_BYTES / (1024 * 1024))}MB; open/check/report may be slow`
3224
+ );
3225
+ }
3226
+ return warnings;
3227
+ }
3228
+ async function assessTraceDirectoryScale(td, options = {}) {
3229
+ const files = await td.list();
3230
+ const sample = options.sampleLargeFiles ?? 25;
3231
+ let largeFileCount = 0;
3232
+ if (files.length > 0 && files.length <= sample * 4) {
3233
+ for (const file of files) {
3234
+ try {
3235
+ const stats = await td.getFileStats(file);
3236
+ if (stats.size >= LARGE_TRACE_FILE_BYTES) largeFileCount += 1;
3237
+ } catch {
3238
+ }
3239
+ }
3240
+ } else if (files.length > sample) {
3241
+ for (const file of files.slice(0, sample)) {
3242
+ try {
3243
+ const stats = await td.getFileStats(file);
3244
+ if (stats.size >= LARGE_TRACE_FILE_BYTES) largeFileCount += 1;
3245
+ } catch {
3246
+ }
3247
+ }
3248
+ if (largeFileCount > 0) {
3249
+ largeFileCount = Math.max(largeFileCount, 1);
3250
+ }
3251
+ }
3252
+ const warnings = buildScaleWarnings(files.length, largeFileCount);
3253
+ return { traceCount: files.length, largeFileCount, warnings };
3254
+ }
3255
+ function emitScaleWarnings(assessment, options = {}) {
3256
+ if (options.json || assessment.warnings.length === 0) return;
3257
+ for (const warning of assessment.warnings) {
3258
+ console.error(`[AgentInspect] warning: ${warning}`);
3259
+ }
3260
+ }
3261
+
3206
3262
  // packages/cli/src/list.ts
3207
3263
  function parseLimit(raw) {
3208
3264
  const fallback = 20;
@@ -3237,6 +3293,8 @@ async function list(options = {}) {
3237
3293
  parseDuration(options.since.trim());
3238
3294
  }
3239
3295
  const files = await td.list();
3296
+ const scale = await assessTraceDirectoryScale(td);
3297
+ emitScaleWarnings(scale, { json: options.json });
3240
3298
  if (files.length === 0) {
3241
3299
  if (options.json) {
3242
3300
  console.log("[]");
@@ -8333,13 +8391,13 @@ function pairSteps(left, right) {
8333
8391
  return pairs;
8334
8392
  }
8335
8393
  function compareLeafSteps(L, R, segments, opts, out) {
8336
- const path18 = buildPath(segments);
8394
+ const path19 = buildPath(segments);
8337
8395
  if (L.name !== R.name) {
8338
8396
  out.push({
8339
8397
  kind: "structure",
8340
8398
  severity: "warning",
8341
8399
  message: "Step name differs",
8342
- path: path18,
8400
+ path: path19,
8343
8401
  left: L.name,
8344
8402
  right: R.name
8345
8403
  });
@@ -8349,7 +8407,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8349
8407
  kind: "step-type",
8350
8408
  severity: "warning",
8351
8409
  message: "Step type differs",
8352
- path: path18,
8410
+ path: path19,
8353
8411
  left: L.type,
8354
8412
  right: R.type
8355
8413
  });
@@ -8359,7 +8417,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8359
8417
  kind: "step-status",
8360
8418
  severity: "warning",
8361
8419
  message: "Step status differs",
8362
- path: path18,
8420
+ path: path19,
8363
8421
  left: L.status,
8364
8422
  right: R.status
8365
8423
  });
@@ -8371,7 +8429,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8371
8429
  kind: "error",
8372
8430
  severity: "error",
8373
8431
  message: "Step error message differs",
8374
- path: path18,
8432
+ path: path19,
8375
8433
  left: le || void 0,
8376
8434
  right: re || void 0
8377
8435
  });
@@ -8389,20 +8447,20 @@ function compareLeafSteps(L, R, segments, opts, out) {
8389
8447
  kind: "duration",
8390
8448
  severity: "info",
8391
8449
  message: "Step duration differs",
8392
- path: path18,
8450
+ path: path19,
8393
8451
  left: ld,
8394
8452
  right: rd
8395
8453
  });
8396
8454
  }
8397
8455
  }
8398
8456
  const lm = stableJson(L.metadata ?? {});
8399
- const rm = stableJson(R.metadata ?? {});
8400
- if (lm !== rm) {
8457
+ const rm2 = stableJson(R.metadata ?? {});
8458
+ if (lm !== rm2) {
8401
8459
  out.push({
8402
8460
  kind: "metadata",
8403
8461
  severity: "info",
8404
8462
  message: "Step metadata differs",
8405
- path: path18,
8463
+ path: path19,
8406
8464
  left: L.metadata,
8407
8465
  right: R.metadata
8408
8466
  });
@@ -8414,7 +8472,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
8414
8472
  kind: "output",
8415
8473
  severity: "info",
8416
8474
  message: "Output preview differs",
8417
- path: path18,
8475
+ path: path19,
8418
8476
  left: L.outputPreview,
8419
8477
  right: R.outputPreview
8420
8478
  });
@@ -8574,11 +8632,11 @@ function diffRuns(left, right, options) {
8574
8632
  }
8575
8633
 
8576
8634
  // packages/core/src/diff/renderer.ts
8577
- function formatPath(path18) {
8578
- if (path18 === void 0 || path18.path.length === 0) {
8635
+ function formatPath(path19) {
8636
+ if (path19 === void 0 || path19.path.length === 0) {
8579
8637
  return "(run)";
8580
8638
  }
8581
- return path18.path.map((s) => s.name).join(" > ");
8639
+ return path19.path.map((s) => s.name).join(" > ");
8582
8640
  }
8583
8641
  function formatValue(v, verbose) {
8584
8642
  if (v === void 0) return "(undefined)";
@@ -8804,6 +8862,8 @@ async function statsCommand(options = {}) {
8804
8862
  parseDuration(options.since.trim());
8805
8863
  }
8806
8864
  const files = await td.list();
8865
+ const scale = await assessTraceDirectoryScale(td);
8866
+ emitScaleWarnings(scale, { json: options.json });
8807
8867
  if (files.length === 0) {
8808
8868
  if (options.json) {
8809
8869
  console.log(
@@ -8874,6 +8934,8 @@ async function searchCommand(options = {}) {
8874
8934
  parseDurationFilter(options.duration);
8875
8935
  }
8876
8936
  const files = await td.list();
8937
+ const scale = await assessTraceDirectoryScale(td);
8938
+ emitScaleWarnings(scale, { json: options.json });
8877
8939
  let metas = await loadTraceMetadataList(
8878
8940
  traceDir,
8879
8941
  files,
@@ -9456,17 +9518,17 @@ function applyRule(rule, value, replacement) {
9456
9518
  }
9457
9519
  return value;
9458
9520
  }
9459
- function childPath(path18, key) {
9521
+ function childPath(path19, key) {
9460
9522
  if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
9461
- return path18 ? `${path18}.${key}` : key;
9523
+ return path19 ? `${path19}.${key}` : key;
9462
9524
  }
9463
- return `${path18 || "$"}[${JSON.stringify(key)}]`;
9525
+ return `${path19 || "$"}[${JSON.stringify(key)}]`;
9464
9526
  }
9465
- function indexPath(path18, index) {
9466
- return `${path18 || "$"}[${index}]`;
9527
+ function indexPath(path19, index) {
9528
+ return `${path19 || "$"}[${index}]`;
9467
9529
  }
9468
- function makeFinding(path18, detector, action, matchKind, severity = "warning", preview) {
9469
- return preview === void 0 ? { path: path18, detector, action, severity, matchKind } : { path: path18, detector, action, severity, matchKind, preview };
9530
+ function makeFinding(path19, detector, action, matchKind, severity = "warning", preview) {
9531
+ return preview === void 0 ? { path: path19, detector, action, severity, matchKind } : { path: path19, detector, action, severity, matchKind, preview };
9470
9532
  }
9471
9533
  function createRedactionProfile(profile = "local") {
9472
9534
  switch (profile) {
@@ -9535,11 +9597,11 @@ var Redactor2 = class {
9535
9597
  #recordFinding(state, finding) {
9536
9598
  if (this.#collectFindings) state.findings.push(finding);
9537
9599
  }
9538
- #redactValue(value, key, path18, depth, state) {
9600
+ #redactValue(value, key, path19, depth, state) {
9539
9601
  if (depth > this.#maxDepth) {
9540
9602
  this.#recordFinding(
9541
9603
  state,
9542
- makeFinding(path18, "structure.maxDepth", "truncate", "value", "warning")
9604
+ makeFinding(path19, "structure.maxDepth", "truncate", "value", "warning")
9543
9605
  );
9544
9606
  return "[Truncated]";
9545
9607
  }
@@ -9548,19 +9610,19 @@ var Redactor2 = class {
9548
9610
  if (rule) {
9549
9611
  this.#recordFinding(
9550
9612
  state,
9551
- makeFinding(path18, `key.${rule.key}`, actionForRule(rule), "key", "warning")
9613
+ makeFinding(path19, `key.${rule.key}`, actionForRule(rule), "key", "warning")
9552
9614
  );
9553
9615
  return applyRule(rule, value, this.#replacement);
9554
9616
  }
9555
9617
  }
9556
9618
  for (const detector of this.#detectors) {
9557
- const detections = detector.detect({ path: path18, key, value });
9619
+ const detections = detector.detect({ path: path19, key, value });
9558
9620
  for (const detection of detections) {
9559
9621
  const action = detection.action ?? "replace";
9560
9622
  this.#recordFinding(
9561
9623
  state,
9562
9624
  makeFinding(
9563
- path18,
9625
+ path19,
9564
9626
  detector.id,
9565
9627
  action,
9566
9628
  detection.matchKind ?? detector.matchKind ?? "custom",
@@ -9578,7 +9640,7 @@ var Redactor2 = class {
9578
9640
  const out = [];
9579
9641
  state.seen.set(value, out);
9580
9642
  value.forEach((item, index) => {
9581
- out[index] = this.#redactValue(item, void 0, indexPath(path18, index), depth + 1, state);
9643
+ out[index] = this.#redactValue(item, void 0, indexPath(path19, index), depth + 1, state);
9582
9644
  });
9583
9645
  return out;
9584
9646
  }
@@ -9590,7 +9652,7 @@ var Redactor2 = class {
9590
9652
  out[entryKey] = this.#redactValue(
9591
9653
  entryValue,
9592
9654
  entryKey,
9593
- childPath(path18 === "$" ? "" : path18, entryKey),
9655
+ childPath(path19 === "$" ? "" : path19, entryKey),
9594
9656
  depth + 1,
9595
9657
  state
9596
9658
  );
@@ -10462,7 +10524,7 @@ function stripPrefix(name, prefixes) {
10462
10524
  }
10463
10525
  return name;
10464
10526
  }
10465
- function eventEvidence(event, path18) {
10527
+ function eventEvidence(event, path19) {
10466
10528
  return {
10467
10529
  runId: event.runId,
10468
10530
  eventId: event.eventId,
@@ -10472,7 +10534,7 @@ function eventEvidence(event, path18) {
10472
10534
  kind: event.kind,
10473
10535
  name: event.name,
10474
10536
  status: event.status,
10475
- ...path18 ? { path: path18 } : {}
10537
+ ...path19 ? { path: path19 } : {}
10476
10538
  };
10477
10539
  }
10478
10540
  function runEvidence(run) {
@@ -10535,9 +10597,9 @@ function eventEndMs(event) {
10535
10597
  function normalizedKey(value) {
10536
10598
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
10537
10599
  }
10538
- function lastPathSegment(path18) {
10539
- const parts = path18.split(".");
10540
- return parts[parts.length - 1] ?? path18;
10600
+ function lastPathSegment(path19) {
10601
+ const parts = path19.split(".");
10602
+ return parts[parts.length - 1] ?? path19;
10541
10603
  }
10542
10604
  function valueType(value) {
10543
10605
  if (Array.isArray(value)) return "array";
@@ -10551,12 +10613,12 @@ function serializedByteLength(value) {
10551
10613
  return void 0;
10552
10614
  }
10553
10615
  }
10554
- function pushValueEntries(entries, event, value, path18, key, depth = 0) {
10555
- entries.push({ event, path: path18, key, value });
10616
+ function pushValueEntries(entries, event, value, path19, key, depth = 0) {
10617
+ entries.push({ event, path: path19, key, value });
10556
10618
  if (depth >= 8) return;
10557
10619
  if (Array.isArray(value)) {
10558
10620
  for (const [index, item] of value.entries()) {
10559
- pushValueEntries(entries, event, item, `${path18}.${index}`, String(index), depth + 1);
10621
+ pushValueEntries(entries, event, item, `${path19}.${index}`, String(index), depth + 1);
10560
10622
  }
10561
10623
  return;
10562
10624
  }
@@ -10566,7 +10628,7 @@ function pushValueEntries(entries, event, value, path18, key, depth = 0) {
10566
10628
  entries,
10567
10629
  event,
10568
10630
  value[nestedKey],
10569
- `${path18}.${nestedKey}`,
10631
+ `${path19}.${nestedKey}`,
10570
10632
  nestedKey,
10571
10633
  depth + 1
10572
10634
  );
@@ -10647,9 +10709,9 @@ function eventDurationMs(event) {
10647
10709
  }
10648
10710
  function treeShape(nodes) {
10649
10711
  const lines = [];
10650
- const visit = (node, path18) => {
10651
- lines.push(`${path18}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
10652
- node.children.forEach((child, index) => visit(child, `${path18}.${index}`));
10712
+ const visit = (node, path19) => {
10713
+ lines.push(`${path19}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
10714
+ node.children.forEach((child, index) => visit(child, `${path19}.${index}`));
10653
10715
  };
10654
10716
  nodes.forEach((node, index) => visit(node, String(index)));
10655
10717
  return lines;
@@ -10698,9 +10760,9 @@ function retrievalShape(context) {
10698
10760
  function guardrailShape(context) {
10699
10761
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
10700
10762
  }
10701
- function firstEvidenceForKind(context, kind, path18) {
10763
+ function firstEvidenceForKind(context, kind, path19) {
10702
10764
  const event = context.events.find((candidate) => candidate.kind === kind);
10703
- return event ? [eventEvidence(event, path18)] : runEvidence(context.selectedRun);
10765
+ return event ? [eventEvidence(event, path19)] : runEvidence(context.selectedRun);
10704
10766
  }
10705
10767
  function baselineDiffFinding(message, evidence, expected, actual) {
10706
10768
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -10764,6 +10826,108 @@ function createRunDurationRule(options) {
10764
10826
  }
10765
10827
  };
10766
10828
  }
10829
+ function createMaxStepDurationRule(options) {
10830
+ return {
10831
+ id: "run.maxStepDuration",
10832
+ category: "run",
10833
+ defaultSeverity: "error",
10834
+ evaluate(context) {
10835
+ const over = context.events.filter((event) => {
10836
+ const duration = eventDurationMs(event);
10837
+ return duration !== void 0 && duration > options.maxDurationMs;
10838
+ });
10839
+ if (over.length === 0) return [];
10840
+ return [
10841
+ failFinding(
10842
+ "run.maxStepDuration",
10843
+ `${over.length} step(s) exceeded max duration ${options.maxDurationMs}ms.`,
10844
+ over.map((event) => eventEvidence(event, "durationMs")),
10845
+ { maxDurationMs: options.maxDurationMs },
10846
+ over.map((event) => ({
10847
+ eventId: event.eventId,
10848
+ name: event.name,
10849
+ durationMs: eventDurationMs(event)
10850
+ }))
10851
+ )
10852
+ ];
10853
+ }
10854
+ };
10855
+ }
10856
+ function createStallDetectionRule(options = {}) {
10857
+ const requireEndedAt = options.requireEndedAt === true;
10858
+ return {
10859
+ id: "run.stall",
10860
+ category: "run",
10861
+ defaultSeverity: "warning",
10862
+ evaluate(context) {
10863
+ const findings = [];
10864
+ const running = context.events.filter((event) => event.status === "running");
10865
+ if (running.length > 0) {
10866
+ findings.push(
10867
+ failFinding(
10868
+ "run.stall",
10869
+ `Found ${running.length} event(s) still running (possible stall).`,
10870
+ running.map((event) => eventEvidence(event, "status")),
10871
+ "no running events",
10872
+ running.length
10873
+ )
10874
+ );
10875
+ }
10876
+ if (requireEndedAt) {
10877
+ const incomplete = context.events.filter(
10878
+ (event) => event.startedAt !== void 0 && event.endedAt === void 0 && event.status !== "running"
10879
+ );
10880
+ if (incomplete.length > 0) {
10881
+ findings.push(
10882
+ failFinding(
10883
+ "run.stall",
10884
+ `Found ${incomplete.length} started event(s) without endedAt.`,
10885
+ incomplete.map((event) => eventEvidence(event, "endedAt")),
10886
+ "endedAt for started events",
10887
+ incomplete.length
10888
+ )
10889
+ );
10890
+ }
10891
+ }
10892
+ return findings;
10893
+ }
10894
+ };
10895
+ }
10896
+ function createRequireCompletedRule() {
10897
+ return {
10898
+ id: "run.requireCompleted",
10899
+ category: "run",
10900
+ defaultSeverity: "error",
10901
+ evaluate(context) {
10902
+ const findings = [];
10903
+ const runStatus = context.selectedRun?.status;
10904
+ if (runStatus === "running") {
10905
+ findings.push(
10906
+ failFinding(
10907
+ "run.requireCompleted",
10908
+ "Run is still running.",
10909
+ runEvidence(context.selectedRun),
10910
+ "completed run",
10911
+ runStatus
10912
+ )
10913
+ );
10914
+ }
10915
+ const running = context.events.filter((event) => event.status === "running");
10916
+ if (running.length > 0) {
10917
+ findings.push(
10918
+ failFinding(
10919
+ "run.requireCompleted",
10920
+ `Run has ${running.length} incomplete running event(s).`,
10921
+ running.map((event) => eventEvidence(event, "status")),
10922
+ "no running events",
10923
+ running.length
10924
+ )
10925
+ );
10926
+ }
10927
+ return findings;
10928
+ }
10929
+ };
10930
+ }
10767
10931
  function createRunDepthRule(options) {
10768
10932
  return {
10769
10933
  id: "run.depth",
@@ -10948,13 +11112,13 @@ function createStructureCycleRule() {
10948
11112
  const seenCycles = /* @__PURE__ */ new Set();
10949
11113
  const findings = [];
10950
11114
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
10951
- const path18 = [];
11115
+ const path19 = [];
10952
11116
  const seenAt = /* @__PURE__ */ new Map();
10953
11117
  let current = event;
10954
11118
  while (current) {
10955
11119
  const existing = seenAt.get(current.eventId);
10956
11120
  if (existing !== void 0) {
10957
- const cycle = path18.slice(existing);
11121
+ const cycle = path19.slice(existing);
10958
11122
  const key = cycle.map((item) => item.eventId).sort().join("\0");
10959
11123
  if (!seenCycles.has(key)) {
10960
11124
  seenCycles.add(key);
@@ -10970,8 +11134,8 @@ function createStructureCycleRule() {
10970
11134
  }
10971
11135
  break;
10972
11136
  }
10973
- seenAt.set(current.eventId, path18.length);
10974
- path18.push(current);
11137
+ seenAt.set(current.eventId, path19.length);
11138
+ path19.push(current);
10975
11139
  current = current.parentId ? byId.get(current.parentId) : void 0;
10976
11140
  }
10977
11141
  }
@@ -11768,23 +11932,23 @@ function evaluatePromptInjection(text, options = {}) {
11768
11932
  }
11769
11933
  return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
11770
11934
  }
11771
- function validateSchemaField(value, field, path18, evidence) {
11935
+ function validateSchemaField(value, field, path19, evidence) {
11772
11936
  const ruleId = "guardrail.structured-output";
11773
11937
  if (field.type) {
11774
11938
  const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
11775
11939
  if (actual !== field.type) {
11776
- evidence.push({ ruleId, path: path18, preview: `expected ${field.type}, got ${actual}` });
11940
+ evidence.push({ ruleId, path: path19, preview: `expected ${field.type}, got ${actual}` });
11777
11941
  return;
11778
11942
  }
11779
11943
  }
11780
11944
  if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
11781
- evidence.push({ ruleId, path: path18, preview: "value not in enum" });
11945
+ evidence.push({ ruleId, path: path19, preview: "value not in enum" });
11782
11946
  }
11783
11947
  if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
11784
11948
  const record = value;
11785
11949
  for (const key of field.required) {
11786
11950
  if (!(key in record)) {
11787
- evidence.push({ ruleId, path: `${path18}.${key}`, preview: "missing required key" });
11951
+ evidence.push({ ruleId, path: `${path19}.${key}`, preview: "missing required key" });
11788
11952
  }
11789
11953
  }
11790
11954
  }
@@ -12145,6 +12309,17 @@ function buildRules(config, options) {
12145
12309
  const safety = checks2.safety ?? {};
12146
12310
  const maxDurationMs = parseNumber(options.maxDurationMs, "--max-duration-ms") ?? run.maxDurationMs;
12147
12311
  const maxTotalTokens = parseNumber(options.maxTotalTokens, "--max-total-tokens") ?? llm.maxTotalTokens;
12312
+ let maxStepDurationMs;
12313
+ if (options.maxStepDuration !== void 0) {
12314
+ try {
12315
+ maxStepDurationMs = parseDuration(options.maxStepDuration.trim());
12316
+ } catch (error) {
12317
+ const message = error instanceof Error ? error.message : String(error);
12318
+ diagnostics.push(
12319
+ diagnostic2("AI_CHECK_INVALID_ARGUMENTS", `--max-step-duration: ${message}`)
12320
+ );
12321
+ }
12322
+ }
12148
12323
  const rules = [
12149
12324
  createRunStatusRule(run),
12150
12325
  createStructureOrphanRule(),
@@ -12155,6 +12330,15 @@ function buildRules(config, options) {
12155
12330
  if (maxDurationMs !== void 0) {
12156
12331
  rules.push(createRunDurationRule({ maxDurationMs }));
12157
12332
  }
12333
+ if (maxStepDurationMs !== void 0) {
12334
+ rules.push(createMaxStepDurationRule({ maxDurationMs: maxStepDurationMs }));
12335
+ }
12336
+ if (options.requireCompleted) {
12337
+ rules.push(createRequireCompletedRule());
12338
+ }
12339
+ if (options.detectStalls) {
12340
+ rules.push(createStallDetectionRule({ requireEndedAt: true }));
12341
+ }
12158
12342
  if (run.maxDepth !== void 0) {
12159
12343
  rules.push(createRunDepthRule({ maxDepth: run.maxDepth }));
12160
12344
  }
@@ -12249,10 +12433,10 @@ function printHuman(result) {
12249
12433
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
12250
12434
  }
12251
12435
  for (const finding of result.findings) {
12252
- const path18 = finding.evidence[0]?.path;
12436
+ const path19 = finding.evidence[0]?.path;
12253
12437
  const run = finding.evidence[0]?.runId;
12254
12438
  const runPrefix = run ? `[${run}] ` : "";
12255
- console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
12439
+ console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
12256
12440
  }
12257
12441
  }
12258
12442
  function readErrorResult(error) {
@@ -12790,10 +12974,10 @@ async function evalRun(input3, options = {}) {
12790
12974
  diagnostics: []
12791
12975
  };
12792
12976
  }
12793
- function evidenceForRun(run, path18) {
12794
- return [{ runId: run.runId, ...path18 !== void 0 ? { path: path18 } : {} }];
12977
+ function evidenceForRun(run, path19) {
12978
+ return [{ runId: run.runId, ...path19 !== void 0 ? { path: path19 } : {} }];
12795
12979
  }
12796
- function evidenceForEvent(event, path18) {
12980
+ function evidenceForEvent(event, path19) {
12797
12981
  return [
12798
12982
  {
12799
12983
  runId: event.runId,
@@ -12801,7 +12985,7 @@ function evidenceForEvent(event, path18) {
12801
12985
  ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
12802
12986
  kind: event.kind,
12803
12987
  name: event.name,
12804
- ...path18 !== void 0 ? { path: path18 } : {}
12988
+ ...path19 !== void 0 ? { path: path19 } : {}
12805
12989
  }
12806
12990
  ];
12807
12991
  }
@@ -12959,9 +13143,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
12959
13143
  function tokenize(text) {
12960
13144
  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));
12961
13145
  }
12962
- function firstEvidence(fields, run, path18) {
13146
+ function firstEvidence(fields, run, path19) {
12963
13147
  const first = fields[0];
12964
- return first === void 0 ? evidenceForRun(run, path18) : evidenceForEvent(first.node.event, first.path);
13148
+ return first === void 0 ? evidenceForRun(run, path19) : evidenceForEvent(first.node.event, first.path);
12965
13149
  }
12966
13150
  function collectSourceIds(nodes, keys) {
12967
13151
  const wanted = keySet(keys);
@@ -13338,8 +13522,8 @@ function renderEvalMarkdown(result) {
13338
13522
  if (result.findings.length > 0) {
13339
13523
  lines.push("", "## Findings");
13340
13524
  for (const finding of result.findings) {
13341
- const path18 = finding.evidence[0]?.path;
13342
- lines.push(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
13525
+ const path19 = finding.evidence[0]?.path;
13526
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13343
13527
  }
13344
13528
  }
13345
13529
  return `${lines.join("\n")}
@@ -13532,8 +13716,8 @@ function printHuman2(result) {
13532
13716
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
13533
13717
  }
13534
13718
  for (const finding of result.findings) {
13535
- const path18 = finding.evidence[0]?.path;
13536
- console.log(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
13719
+ const path19 = finding.evidence[0]?.path;
13720
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13537
13721
  }
13538
13722
  }
13539
13723
  function readErrorResult2(error) {
@@ -13771,8 +13955,8 @@ function printHuman3(result) {
13771
13955
  console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
13772
13956
  }
13773
13957
  for (const finding of result.findings) {
13774
- const path18 = finding.evidence[0]?.path;
13775
- console.log(`- ${finding.ruleId}: ${finding.message}${path18 ? ` (${path18})` : ""}`);
13958
+ const path19 = finding.evidence[0]?.path;
13959
+ console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
13776
13960
  }
13777
13961
  console.log(`Note: ${result.note}`);
13778
13962
  }
@@ -13891,8 +14075,8 @@ function renderCheckSection(result) {
13891
14075
  `Diagnostics: ${result.diagnostics.length}`
13892
14076
  ];
13893
14077
  for (const finding of result.findings.slice(0, 10)) {
13894
- const path18 = finding.evidence[0]?.path ?? "(run)";
13895
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path18})`);
14078
+ const path19 = finding.evidence[0]?.path ?? "(run)";
14079
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path19})`);
13896
14080
  }
13897
14081
  for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
13898
14082
  lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
@@ -14894,6 +15078,174 @@ Summary: ${failed} failed, ${warned} warnings`);
14894
15078
  if (failed > 0) process3.exitCode = 1;
14895
15079
  }
14896
15080
 
15081
+ // packages/adapter-sdk/src/indexer.ts
15082
+ function defineIndexer(indexer) {
15083
+ if (!indexer.id.trim()) throw new Error("indexer id is required");
15084
+ return indexer;
15085
+ }
15086
+ async function indexIsStale(snapshot, traceDir) {
15087
+ const builtMs = Date.parse(snapshot.builtAt);
15088
+ if (Number.isNaN(builtMs)) return true;
15089
+ const td = new TraceDirectory({ dir: traceDir });
15090
+ const files = await td.list();
15091
+ for (const file of files) {
15092
+ const stats = await td.getFileStats(file);
15093
+ if (stats.mtimeMs > builtMs) return true;
15094
+ }
15095
+ return false;
15096
+ }
15097
+ function createTraceDirectoryIndexer() {
15098
+ return defineIndexer({
15099
+ id: "trace-directory-metadata",
15100
+ async rebuild(traceDir, options = {}) {
15101
+ const warnings = [];
15102
+ const td = new TraceDirectory({ dir: traceDir });
15103
+ const files = await td.list();
15104
+ const maxEntries = options.maxEntries ?? 1e4;
15105
+ if (files.length > maxEntries) {
15106
+ warnings.push(
15107
+ `indexer.truncated: trace directory has ${files.length} files; indexing first ${maxEntries}`
15108
+ );
15109
+ }
15110
+ const slice = files.slice(0, maxEntries);
15111
+ const metas = await loadTraceMetadataList(
15112
+ traceDir,
15113
+ slice,
15114
+ (fileName) => td.getPath(fileName)
15115
+ );
15116
+ const entries = metas.map((meta) => ({
15117
+ runId: meta.runId,
15118
+ path: meta.filePath,
15119
+ name: meta.name,
15120
+ startedAt: meta.startedAt,
15121
+ status: meta.status
15122
+ })).sort((a, b) => a.runId.localeCompare(b.runId));
15123
+ if (entries.length < slice.length) {
15124
+ warnings.push(
15125
+ `indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
15126
+ );
15127
+ }
15128
+ return {
15129
+ traceDir,
15130
+ builtAt: (/* @__PURE__ */ new Date()).toISOString(),
15131
+ entries,
15132
+ warnings
15133
+ };
15134
+ }
15135
+ });
15136
+ }
15137
+
15138
+ // packages/cli/src/index-cmd.ts
15139
+ var INDEX_FILENAME = ".agent-inspect-index.json";
15140
+ function traceIndexPath(traceDir) {
15141
+ return path14.join(traceDir, INDEX_FILENAME);
15142
+ }
15143
+ function parseMaxEntries(raw) {
15144
+ if (raw === void 0 || raw.trim() === "") return void 0;
15145
+ const parsed = Number.parseInt(raw, 10);
15146
+ if (!Number.isFinite(parsed) || parsed <= 0) {
15147
+ throw new Error("--max-entries must be a positive integer.");
15148
+ }
15149
+ return parsed;
15150
+ }
15151
+ async function readSnapshot(indexPath2) {
15152
+ try {
15153
+ const raw = await readFile(indexPath2, "utf8");
15154
+ return JSON.parse(raw);
15155
+ } catch {
15156
+ return void 0;
15157
+ }
15158
+ }
15159
+ async function indexBuildCommand(options = {}) {
15160
+ try {
15161
+ const traceDir = resolveTraceDir({ dir: options.dir });
15162
+ await mkdir(traceDir, { recursive: true });
15163
+ const indexer = createTraceDirectoryIndexer();
15164
+ const snapshot = await indexer.rebuild(traceDir, {
15165
+ maxEntries: parseMaxEntries(options.maxEntries)
15166
+ });
15167
+ const indexPath2 = traceIndexPath(traceDir);
15168
+ await writeFile(indexPath2, `${JSON.stringify(snapshot, null, 2)}
15169
+ `, "utf8");
15170
+ if (options.json) {
15171
+ console.log(JSON.stringify({ ok: true, indexPath: indexPath2, ...snapshot }, null, 2));
15172
+ return;
15173
+ }
15174
+ console.log(`Built trace index: ${indexPath2}`);
15175
+ console.log(`Entries: ${snapshot.entries.length}`);
15176
+ if (snapshot.warnings.length > 0) {
15177
+ for (const warning of snapshot.warnings) {
15178
+ console.log(`warning: ${warning}`);
15179
+ }
15180
+ }
15181
+ } catch (e) {
15182
+ const msg = e instanceof Error ? e.message : String(e);
15183
+ console.error(`[AgentInspect] index build failed: ${msg}`);
15184
+ process.exitCode = 1;
15185
+ }
15186
+ }
15187
+ async function indexStatusCommand(options = {}) {
15188
+ try {
15189
+ const traceDir = resolveTraceDir({ dir: options.dir });
15190
+ const indexPath2 = traceIndexPath(traceDir);
15191
+ const snapshot = await readSnapshot(indexPath2);
15192
+ if (!snapshot) {
15193
+ const payload2 = { ok: true, exists: false, indexPath: indexPath2, traceDir, stale: true };
15194
+ if (options.json) {
15195
+ console.log(JSON.stringify(payload2, null, 2));
15196
+ } else {
15197
+ console.log(`No index at ${indexPath2}`);
15198
+ console.log("Run: agent-inspect index build");
15199
+ }
15200
+ return;
15201
+ }
15202
+ const stale = await indexIsStale(snapshot, traceDir);
15203
+ const payload = {
15204
+ ok: true,
15205
+ exists: true,
15206
+ indexPath: indexPath2,
15207
+ traceDir,
15208
+ stale,
15209
+ builtAt: snapshot.builtAt,
15210
+ entries: snapshot.entries.length,
15211
+ warnings: snapshot.warnings
15212
+ };
15213
+ if (options.json) {
15214
+ console.log(JSON.stringify(payload, null, 2));
15215
+ return;
15216
+ }
15217
+ console.log(`Index: ${indexPath2}`);
15218
+ console.log(`Built: ${snapshot.builtAt}`);
15219
+ console.log(`Entries: ${snapshot.entries.length}`);
15220
+ console.log(`Stale: ${stale ? "yes" : "no"}`);
15221
+ if (snapshot.warnings.length > 0) {
15222
+ for (const warning of snapshot.warnings) {
15223
+ console.log(`warning: ${warning}`);
15224
+ }
15225
+ }
15226
+ } catch (e) {
15227
+ const msg = e instanceof Error ? e.message : String(e);
15228
+ console.error(`[AgentInspect] index status failed: ${msg}`);
15229
+ process.exitCode = 1;
15230
+ }
15231
+ }
15232
+ async function indexCleanCommand(options = {}) {
15233
+ try {
15234
+ const traceDir = resolveTraceDir({ dir: options.dir });
15235
+ const indexPath2 = traceIndexPath(traceDir);
15236
+ await rm(indexPath2, { force: true });
15237
+ if (options.json) {
15238
+ console.log(JSON.stringify({ ok: true, removed: indexPath2 }, null, 2));
15239
+ return;
15240
+ }
15241
+ console.log(`Removed index: ${indexPath2}`);
15242
+ } catch (e) {
15243
+ const msg = e instanceof Error ? e.message : String(e);
15244
+ console.error(`[AgentInspect] index clean failed: ${msg}`);
15245
+ process.exitCode = 1;
15246
+ }
15247
+ }
15248
+
14897
15249
  // packages/cli/src/index.ts
14898
15250
  function runCommand(action) {
14899
15251
  void action().catch((error) => {
@@ -15018,7 +15370,10 @@ function createCliProgram() {
15018
15370
  ]).option("--allowed-model <model>", "allow an LLM model (repeatable)", (value, previous = []) => [
15019
15371
  ...previous,
15020
15372
  value
15021
- ]).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(
15373
+ ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").option(
15374
+ "--max-step-duration <duration>",
15375
+ "add run.maxStepDuration (e.g. 30s, 5m)"
15376
+ ).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(
15022
15377
  "--correlate-group",
15023
15378
  "when using --session, also match synthetic group: session keys"
15024
15379
  ).option(
@@ -15212,6 +15567,16 @@ function createCliProgram() {
15212
15567
  ).action((opts) => {
15213
15568
  runCommand(() => doctorCommand(opts));
15214
15569
  });
15570
+ const indexCmd = program.command("index").description("Optional local trace directory index (rebuildable metadata cache)");
15571
+ 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) => {
15572
+ runCommand(() => indexBuildCommand(opts));
15573
+ });
15574
+ indexCmd.command("status").description("Show index freshness and entry count").option("--dir <path>", "trace directory").option("--json", "print JSON result").action((opts) => {
15575
+ runCommand(() => indexStatusCommand(opts));
15576
+ });
15577
+ indexCmd.command("clean").description("Remove the local index file").option("--dir <path>", "trace directory").option("--json", "print JSON result").action((opts) => {
15578
+ runCommand(() => indexCleanCommand(opts));
15579
+ });
15215
15580
  return program;
15216
15581
  }
15217
15582
  function isPrimaryModule() {