agent-inspect 6.17.4 → 6.17.5

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/docs/ADAPTERS.md +3 -3
  4. package/docs/API.md +13 -7
  5. package/docs/CLI.md +2 -0
  6. package/docs/LIMITATIONS.md +1 -0
  7. package/docs/TRACE-CONTRACTS.md +90 -0
  8. package/package.json +5 -2
  9. package/packages/cli/dist/{chunk-YX6RFLV5.mjs → chunk-GOHYSE7W.mjs} +86 -17
  10. package/packages/cli/dist/chunk-GOHYSE7W.mjs.map +1 -0
  11. package/packages/cli/dist/index.cjs +480 -80
  12. package/packages/cli/dist/index.cjs.map +1 -1
  13. package/packages/cli/dist/index.mjs +399 -68
  14. package/packages/cli/dist/index.mjs.map +1 -1
  15. package/packages/cli/dist/{src-X6SLMBNH.mjs → src-OYGG7QAZ.mjs} +3 -3
  16. package/packages/cli/dist/{src-X6SLMBNH.mjs.map → src-OYGG7QAZ.mjs.map} +1 -1
  17. package/packages/core/dist/advanced.cjs +85 -16
  18. package/packages/core/dist/advanced.cjs.map +1 -1
  19. package/packages/core/dist/advanced.d.cts +1 -1
  20. package/packages/core/dist/advanced.d.ts +1 -1
  21. package/packages/core/dist/advanced.mjs +15 -5
  22. package/packages/core/dist/advanced.mjs.map +1 -1
  23. package/packages/core/dist/checks.cjs +119 -26
  24. package/packages/core/dist/checks.cjs.map +1 -1
  25. package/packages/core/dist/checks.d.cts +28 -2
  26. package/packages/core/dist/checks.d.ts +28 -2
  27. package/packages/core/dist/checks.mjs +1 -1
  28. package/packages/core/dist/{chunk-HN377P23.mjs → chunk-DIZPIPY2.mjs} +121 -28
  29. package/packages/core/dist/chunk-DIZPIPY2.mjs.map +1 -0
  30. package/packages/core/dist/{index-Xk9X-yjY.d.cts → index-DWu54Y28.d.cts} +107 -122
  31. package/packages/core/dist/{index-BsCcOKxy.d.ts → index-DlwbVqEs.d.ts} +107 -122
  32. package/packages/cli/dist/chunk-YX6RFLV5.mjs.map +0 -1
  33. package/packages/core/dist/chunk-HN377P23.mjs.map +0 -1
@@ -3565,14 +3565,15 @@ var init_cohort = __esm({
3565
3565
 
3566
3566
  // packages/core/src/sessions/checks.ts
3567
3567
  function emptySummary() {
3568
- return { passed: 0, failed: 0, warnings: 0, errors: 0 };
3568
+ return { passed: 0, failed: 0, warnings: 0, errors: 0, rulesEvaluated: 0 };
3569
3569
  }
3570
3570
  function mergeSummary(target, source) {
3571
3571
  return {
3572
3572
  passed: target.passed + source.passed,
3573
3573
  failed: target.failed + source.failed,
3574
3574
  warnings: target.warnings + source.warnings,
3575
- errors: target.errors + source.errors
3575
+ errors: target.errors + source.errors,
3576
+ rulesEvaluated: target.rulesEvaluated + source.rulesEvaluated
3576
3577
  };
3577
3578
  }
3578
3579
  function sessionDiagnostic(code, message) {
@@ -3596,6 +3597,7 @@ function aggregateSessionCheckResults(perRun, scope) {
3596
3597
  `${scope.scopeKind} not found: ${scope.scopeLabel}`
3597
3598
  )
3598
3599
  ],
3600
+ ruleExecutions: [],
3599
3601
  ...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
3600
3602
  };
3601
3603
  }
@@ -3616,17 +3618,20 @@ function aggregateSessionCheckResults(perRun, scope) {
3616
3618
  `No readable traces in ${scope.scopeKind}: ${scope.scopeLabel}`
3617
3619
  )
3618
3620
  ],
3621
+ ruleExecutions: [],
3619
3622
  ...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
3620
3623
  };
3621
3624
  }
3622
3625
  let summary = emptySummary();
3623
3626
  const findings = [];
3624
3627
  const diagnostics = [];
3628
+ const ruleExecutions = [];
3625
3629
  const runResults = [];
3626
3630
  for (const result of perRun) {
3627
3631
  summary = mergeSummary(summary, result.summary);
3628
3632
  findings.push(...result.findings);
3629
3633
  diagnostics.push(...result.diagnostics);
3634
+ ruleExecutions.push(...result.ruleExecutions ?? []);
3630
3635
  if (result.runId) {
3631
3636
  runResults.push({ runId: result.runId, status: result.status });
3632
3637
  }
@@ -3639,6 +3644,11 @@ function aggregateSessionCheckResults(perRun, scope) {
3639
3644
  if (runCmp !== 0) return runCmp;
3640
3645
  return a.ruleId.localeCompare(b.ruleId);
3641
3646
  });
3647
+ ruleExecutions.sort((a, b) => {
3648
+ const runCmp = (a.runId ?? "").localeCompare(b.runId ?? "");
3649
+ if (runCmp !== 0) return runCmp;
3650
+ return a.ruleId.localeCompare(b.ruleId);
3651
+ });
3642
3652
  const hasErrors = diagnostics.some((item) => item.severity === "error");
3643
3653
  const status = hasErrors ? "error" : summary.failed > 0 ? "fail" : "pass";
3644
3654
  return {
@@ -3652,6 +3662,7 @@ function aggregateSessionCheckResults(perRun, scope) {
3652
3662
  summary,
3653
3663
  findings,
3654
3664
  diagnostics,
3665
+ ruleExecutions,
3655
3666
  ...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
3656
3667
  };
3657
3668
  }
@@ -6857,10 +6868,11 @@ function emptySummary2() {
6857
6868
  passed: 0,
6858
6869
  failed: 0,
6859
6870
  warnings: 0,
6860
- errors: 0
6871
+ errors: 0,
6872
+ rulesEvaluated: 0
6861
6873
  };
6862
6874
  }
6863
- function errorResult(input3, diagnostics, selectedRun) {
6875
+ function errorResult(input3, diagnostics, selectedRun, ruleExecutions = []) {
6864
6876
  return {
6865
6877
  ok: false,
6866
6878
  status: "error",
@@ -6868,10 +6880,12 @@ function errorResult(input3, diagnostics, selectedRun) {
6868
6880
  ...selectedRun ? { runId: selectedRun.runId } : {},
6869
6881
  summary: {
6870
6882
  ...emptySummary2(),
6871
- errors: diagnostics.filter((item) => item.severity === "error").length
6883
+ errors: diagnostics.filter((item) => item.severity === "error").length,
6884
+ rulesEvaluated: ruleExecutions.length
6872
6885
  },
6873
6886
  findings: [],
6874
- diagnostics: [...diagnostics]
6887
+ diagnostics: [...diagnostics],
6888
+ ruleExecutions: [...ruleExecutions]
6875
6889
  };
6876
6890
  }
6877
6891
  function flattenNodes(nodes) {
@@ -7027,7 +7041,7 @@ function normalizeFinding(rule, finding) {
7027
7041
  ...finding.action !== void 0 ? { action: finding.action } : {}
7028
7042
  };
7029
7043
  }
7030
- function summarize(findings, diagnostics) {
7044
+ function summarize(findings, diagnostics, rulesEvaluated) {
7031
7045
  return {
7032
7046
  passed: findings.filter((finding) => finding.status === "pass").length,
7033
7047
  failed: findings.filter(
@@ -7036,9 +7050,21 @@ function summarize(findings, diagnostics) {
7036
7050
  warnings: findings.filter(
7037
7051
  (finding) => finding.status === "warning" || finding.severity === "warning"
7038
7052
  ).length,
7039
- errors: diagnostics.filter((item) => item.severity === "error").length
7053
+ errors: diagnostics.filter((item) => item.severity === "error").length,
7054
+ rulesEvaluated
7040
7055
  };
7041
7056
  }
7057
+ function classifyRuleExecution(findings, threw) {
7058
+ if (findings.some((finding) => finding.status === "fail" && finding.severity === "error")) {
7059
+ return "fail";
7060
+ }
7061
+ if (findings.some(
7062
+ (finding) => finding.status === "warning" || finding.severity === "warning"
7063
+ )) {
7064
+ return "warning";
7065
+ }
7066
+ return "pass";
7067
+ }
7042
7068
  function stringAttr(event, keys) {
7043
7069
  for (const key of keys) {
7044
7070
  const value = event.attributes?.[key];
@@ -7135,6 +7161,9 @@ function finishedEvents(context, kind) {
7135
7161
  (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
7136
7162
  );
7137
7163
  }
7164
+ function toolInvocationEvents(context) {
7165
+ return semanticEvents(context).filter((event) => event.kind === "TOOL");
7166
+ }
7138
7167
  function isRecord8(value) {
7139
7168
  return typeof value === "object" && value !== null && !Array.isArray(value);
7140
7169
  }
@@ -7535,7 +7564,7 @@ function createToolUsageRule(options) {
7535
7564
  category: "tool",
7536
7565
  defaultSeverity: "error",
7537
7566
  evaluate(context) {
7538
- const tools = finishedEvents(context, "TOOL");
7567
+ const tools = toolInvocationEvents(context);
7539
7568
  const names = tools.map(toolName);
7540
7569
  const nameSet = new Set(names);
7541
7570
  const findings = [];
@@ -8185,12 +8214,24 @@ function createBaselineRegressionRule(options) {
8185
8214
  }
8186
8215
  function createObservedOutcomeRule(options = {}) {
8187
8216
  const failOn = options.failOn ?? ["failed"];
8217
+ const requireAny = options.requireAny === true;
8188
8218
  return {
8189
8219
  id: "outcome.status",
8190
8220
  category: "run",
8191
8221
  defaultSeverity: "error",
8192
8222
  evaluate(context) {
8193
8223
  const outcomes = extractOutcomesFromPersistedEvents(context.events);
8224
+ if (requireAny && outcomes.length === 0) {
8225
+ return [
8226
+ failFinding(
8227
+ "outcome.status",
8228
+ "Expected at least one observed outcome.",
8229
+ runEvidence(context.selectedRun),
8230
+ { requireAny: true, expected: "at least one observed outcome" },
8231
+ { code: "outcome.missing", actual: 0 }
8232
+ )
8233
+ ];
8234
+ }
8194
8235
  const matching = outcomesMatchingStatus(outcomes, failOn);
8195
8236
  if (matching.length === 0) return [];
8196
8237
  return [
@@ -8226,6 +8267,18 @@ function runTraceChecks(input3, options = {}) {
8226
8267
  if (rules.diagnostics.length > 0) {
8227
8268
  return errorResult(input3, rules.diagnostics, selected.run);
8228
8269
  }
8270
+ if (rules.rules.length === 0) {
8271
+ return errorResult(
8272
+ input3,
8273
+ [
8274
+ diagnostic3(
8275
+ "AI_CHECK_NO_RULES_EVALUATED",
8276
+ "No trace check rules were evaluated. Configure at least one rule, contract, or CLI check option."
8277
+ )
8278
+ ],
8279
+ selected.run
8280
+ );
8281
+ }
8229
8282
  const facts = buildFacts2(input3, selected.run);
8230
8283
  const context = {
8231
8284
  ...facts,
@@ -8234,22 +8287,38 @@ function runTraceChecks(input3, options = {}) {
8234
8287
  };
8235
8288
  const diagnostics = [];
8236
8289
  const findings = [];
8290
+ const ruleExecutions = [];
8237
8291
  for (const rule of rules.rules) {
8238
8292
  try {
8239
- findings.push(...rule.evaluate(context).map((finding) => normalizeFinding(rule, finding)));
8293
+ const ruleFindings = rule.evaluate(context).map((finding) => normalizeFinding(rule, finding));
8294
+ findings.push(...ruleFindings);
8295
+ ruleExecutions.push({
8296
+ ruleId: rule.id,
8297
+ category: rule.category,
8298
+ status: classifyRuleExecution(ruleFindings, false),
8299
+ findingCount: ruleFindings.length,
8300
+ ...selected.run ? { runId: selected.run.runId } : {}
8301
+ });
8240
8302
  } catch (error) {
8241
8303
  const message = error instanceof Error ? error.message : String(error);
8242
8304
  diagnostics.push(
8243
8305
  diagnostic3("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
8244
8306
  );
8307
+ ruleExecutions.push({
8308
+ ruleId: rule.id,
8309
+ category: rule.category,
8310
+ status: "error",
8311
+ findingCount: 0,
8312
+ ...selected.run ? { runId: selected.run.runId } : {}
8313
+ });
8245
8314
  }
8246
8315
  }
8247
8316
  if (diagnostics.length > 0) {
8248
- return errorResult(input3, diagnostics, selected.run);
8317
+ return errorResult(input3, diagnostics, selected.run, ruleExecutions);
8249
8318
  }
8250
8319
  const eventById = new Map(input3.read.events.map((event) => [event.eventId, event]));
8251
8320
  const sortedFindings = findings.sort(compareFindings(eventById));
8252
- const summary = summarize(sortedFindings, diagnostics);
8321
+ const summary = summarize(sortedFindings, diagnostics, ruleExecutions.length);
8253
8322
  const status = summary.failed > 0 ? "fail" : "pass";
8254
8323
  return {
8255
8324
  ok: status === "pass",
@@ -8258,7 +8327,8 @@ function runTraceChecks(input3, options = {}) {
8258
8327
  ...selected.run ? { runId: selected.run.runId } : {},
8259
8328
  summary,
8260
8329
  findings: sortedFindings,
8261
- diagnostics
8330
+ diagnostics,
8331
+ ruleExecutions
8262
8332
  };
8263
8333
  }
8264
8334
  var SEVERITY_RANK, STATUS_RANK, CONFIDENCE_RANK, DEFAULT_SENSITIVE_KEYS, DEFAULT_RAW_CONTENT_KEYS, DEFAULT_SAFE_RAW_CONTENT_PATH_PREFIXES, SAFE_USAGE_LEAF_KEYS, DEFAULT_SECRET_PATTERNS;
@@ -10707,8 +10777,7 @@ async function runSuiteCase(suiteCase, config, options) {
10707
10777
  status: "pass",
10708
10778
  format: read.format,
10709
10779
  findings: [],
10710
- diagnostics: []
10711
- };
10780
+ diagnostics: []};
10712
10781
  const observationResult = validateExpectedObservations(suiteCase, read);
10713
10782
  const diagnostics = [
10714
10783
  ...checkResult.diagnostics.map(
@@ -12511,7 +12580,7 @@ var init_src = __esm({
12511
12580
  });
12512
12581
 
12513
12582
  // package.json
12514
- var version = "6.17.4";
12583
+ var version = "6.17.5";
12515
12584
 
12516
12585
  // packages/cli/src/list.ts
12517
12586
  init_advanced();
@@ -14080,6 +14149,11 @@ async function followFile(filePath, options, onLine, shouldStop) {
14080
14149
  const msg = e instanceof Error ? e.message : String(e);
14081
14150
  throw new Error(`Failed to stat file: ${filePath} (${msg})`);
14082
14151
  }
14152
+ if (next.size < pos) {
14153
+ pos = 0;
14154
+ carry = "";
14155
+ lineNumber = 0;
14156
+ }
14083
14157
  if (next.size > pos) {
14084
14158
  const fh = await promises.open(filePath, "r");
14085
14159
  try {
@@ -17744,8 +17818,11 @@ function checkResultToEvidenceJson(result, runIds) {
17744
17818
  status,
17745
17819
  errors: result.summary.errors,
17746
17820
  warnings: result.summary.warnings,
17747
- findings: result.findings.length
17821
+ findings: result.findings.length,
17822
+ rulesEvaluated: result.summary.rulesEvaluated ?? 0
17748
17823
  })),
17824
+ rulesEvaluated: result.summary.rulesEvaluated ?? 0,
17825
+ evaluatedRuleIds: (result.ruleExecutions ?? []).map((item) => item.ruleId),
17749
17826
  findings: result.findings,
17750
17827
  diagnostics: result.diagnostics
17751
17828
  });
@@ -18370,9 +18447,11 @@ function mergeSafetyExtensions(result, read, options) {
18370
18447
  passed,
18371
18448
  failed,
18372
18449
  warnings,
18373
- errors: result.summary.errors
18450
+ errors: result.summary.errors,
18451
+ rulesEvaluated: result.summary.rulesEvaluated ?? 0
18374
18452
  },
18375
- findings
18453
+ findings,
18454
+ ruleExecutions: result.ruleExecutions ?? []
18376
18455
  };
18377
18456
  }
18378
18457
 
@@ -18492,10 +18571,12 @@ function errorResult2(code, message, format = "unknown") {
18492
18571
  passed: 0,
18493
18572
  failed: 0,
18494
18573
  warnings: 0,
18495
- errors: 1
18574
+ errors: 1,
18575
+ rulesEvaluated: 0
18496
18576
  },
18497
18577
  findings: [],
18498
- diagnostics
18578
+ diagnostics,
18579
+ ruleExecutions: []
18499
18580
  };
18500
18581
  }
18501
18582
  function parseNumber(value, label) {
@@ -18513,36 +18594,329 @@ function asStringArray2(value) {
18513
18594
  }
18514
18595
  return value;
18515
18596
  }
18516
- function asConfig(value) {
18597
+ var CHECKS_KEYS = /* @__PURE__ */ new Set(["select", "run", "tool", "llm", "structure", "safety"]);
18598
+ var RUN_KEYS = /* @__PURE__ */ new Set(["expected", "allowIncomplete", "maxDurationMs", "maxDepth"]);
18599
+ var TOOL_KEYS = /* @__PURE__ */ new Set(["required", "forbidden", "allowed", "minCount", "maxCount"]);
18600
+ var LLM_KEYS = /* @__PURE__ */ new Set([
18601
+ "allowedModels",
18602
+ "allowedProviders",
18603
+ "finishReasons",
18604
+ "maxCalls",
18605
+ "maxTotalTokens",
18606
+ "maxInputTokens",
18607
+ "maxOutputTokens",
18608
+ "maxCachedTokens"
18609
+ ]);
18610
+ var STRUCTURE_KEYS = /* @__PURE__ */ new Set([
18611
+ "minConfidence",
18612
+ "requireParentBeforeChild",
18613
+ "requireTraceParentSpan",
18614
+ "orphan",
18615
+ "cycle",
18616
+ "maxChildren",
18617
+ "maxConcurrent"
18618
+ ]);
18619
+ var SAFETY_KEYS = /* @__PURE__ */ new Set([
18620
+ "redaction",
18621
+ "rawContent",
18622
+ "secretPattern",
18623
+ "maxStringLength",
18624
+ "maxArrayLength",
18625
+ "maxObjectKeys",
18626
+ "maxSerializedBytes",
18627
+ "maxFindings"
18628
+ ]);
18629
+ var RUN_EXPECTED = /* @__PURE__ */ new Set(["ok", "error", "running"]);
18630
+ var CONFIDENCE_VALUES = /* @__PURE__ */ new Set(["exact", "high", "medium", "low", "inferred"]);
18631
+ var CheckConfigError = class extends Error {
18632
+ code;
18633
+ constructor(code, message) {
18634
+ super(message);
18635
+ this.name = "CheckConfigError";
18636
+ this.code = code;
18637
+ }
18638
+ };
18639
+ function closestKey(unknown, known) {
18640
+ const lower = unknown.toLowerCase();
18641
+ let best;
18642
+ let bestDistance = Number.POSITIVE_INFINITY;
18643
+ for (const candidate of known) {
18644
+ if (candidate.toLowerCase() === lower) return candidate;
18645
+ let distance = Math.abs(candidate.length - unknown.length);
18646
+ const max = Math.max(candidate.length, unknown.length);
18647
+ for (let i = 0; i < Math.min(candidate.length, unknown.length); i += 1) {
18648
+ if (candidate[i].toLowerCase() !== unknown[i].toLowerCase()) distance += 1;
18649
+ }
18650
+ if (distance < bestDistance && distance <= Math.max(2, Math.floor(max / 3))) {
18651
+ bestDistance = distance;
18652
+ best = candidate;
18653
+ }
18654
+ }
18655
+ return best;
18656
+ }
18657
+ function rejectUnknownKeys(record, allowed, pathPrefix) {
18658
+ for (const key of Object.keys(record)) {
18659
+ if (allowed.has(key)) continue;
18660
+ const suggestion = closestKey(key, [...allowed]);
18661
+ const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
18662
+ throw new CheckConfigError(
18663
+ "AI_CHECK_CONFIG_UNKNOWN_KEY",
18664
+ `Unknown check config key "${pathPrefix}.${key}".${hint}`
18665
+ );
18666
+ }
18667
+ }
18668
+ function requireObject(value, pathPrefix) {
18669
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
18670
+ throw new CheckConfigError(
18671
+ "AI_CHECK_CONFIG_INVALID_VALUE",
18672
+ `${pathPrefix} must be an object.`
18673
+ );
18674
+ }
18675
+ return value;
18676
+ }
18677
+ function requireStringArray(value, pathPrefix) {
18678
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
18679
+ throw new CheckConfigError(
18680
+ "AI_CHECK_CONFIG_INVALID_VALUE",
18681
+ `${pathPrefix} must be an array of non-empty strings.`
18682
+ );
18683
+ }
18684
+ return value;
18685
+ }
18686
+ function requireNonNegativeNumber(value, pathPrefix) {
18687
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
18688
+ throw new CheckConfigError(
18689
+ "AI_CHECK_CONFIG_INVALID_VALUE",
18690
+ `${pathPrefix} must be a finite non-negative number.`
18691
+ );
18692
+ }
18693
+ return value;
18694
+ }
18695
+ function requireBoolean(value, pathPrefix) {
18696
+ if (typeof value !== "boolean") {
18697
+ throw new CheckConfigError(
18698
+ "AI_CHECK_CONFIG_INVALID_VALUE",
18699
+ `${pathPrefix} must be a boolean.`
18700
+ );
18701
+ }
18702
+ return value;
18703
+ }
18704
+ function parseRunSection(value) {
18705
+ const record = requireObject(value, "checks.run");
18706
+ rejectUnknownKeys(record, RUN_KEYS, "checks.run");
18707
+ const out = {};
18708
+ if (record.expected !== void 0) {
18709
+ if (typeof record.expected !== "string" || !RUN_EXPECTED.has(record.expected)) {
18710
+ throw new CheckConfigError(
18711
+ "AI_CHECK_CONFIG_INVALID_VALUE",
18712
+ `checks.run.expected must be one of: ok, error, running.`
18713
+ );
18714
+ }
18715
+ out.expected = record.expected;
18716
+ }
18717
+ if (record.allowIncomplete !== void 0) {
18718
+ out.allowIncomplete = requireBoolean(record.allowIncomplete, "checks.run.allowIncomplete");
18719
+ }
18720
+ if (record.maxDurationMs !== void 0) {
18721
+ out.maxDurationMs = requireNonNegativeNumber(record.maxDurationMs, "checks.run.maxDurationMs");
18722
+ }
18723
+ if (record.maxDepth !== void 0) {
18724
+ out.maxDepth = requireNonNegativeNumber(record.maxDepth, "checks.run.maxDepth");
18725
+ }
18726
+ return out;
18727
+ }
18728
+ function parseToolSection(value) {
18729
+ const record = requireObject(value, "checks.tool");
18730
+ rejectUnknownKeys(record, TOOL_KEYS, "checks.tool");
18731
+ const out = {};
18732
+ if (record.required !== void 0) out.required = requireStringArray(record.required, "checks.tool.required");
18733
+ if (record.forbidden !== void 0) out.forbidden = requireStringArray(record.forbidden, "checks.tool.forbidden");
18734
+ if (record.allowed !== void 0) out.allowed = requireStringArray(record.allowed, "checks.tool.allowed");
18735
+ if (record.minCount !== void 0) out.minCount = requireNonNegativeNumber(record.minCount, "checks.tool.minCount");
18736
+ if (record.maxCount !== void 0) out.maxCount = requireNonNegativeNumber(record.maxCount, "checks.tool.maxCount");
18737
+ return out;
18738
+ }
18739
+ function parseLlmSection(value) {
18740
+ const record = requireObject(value, "checks.llm");
18741
+ rejectUnknownKeys(record, LLM_KEYS, "checks.llm");
18742
+ const out = {};
18743
+ if (record.allowedModels !== void 0) {
18744
+ out.allowedModels = requireStringArray(record.allowedModels, "checks.llm.allowedModels");
18745
+ }
18746
+ if (record.allowedProviders !== void 0) {
18747
+ out.allowedProviders = requireStringArray(record.allowedProviders, "checks.llm.allowedProviders");
18748
+ }
18749
+ if (record.finishReasons !== void 0) {
18750
+ out.finishReasons = requireStringArray(record.finishReasons, "checks.llm.finishReasons");
18751
+ }
18752
+ if (record.maxCalls !== void 0) out.maxCalls = requireNonNegativeNumber(record.maxCalls, "checks.llm.maxCalls");
18753
+ if (record.maxTotalTokens !== void 0) {
18754
+ out.maxTotalTokens = requireNonNegativeNumber(record.maxTotalTokens, "checks.llm.maxTotalTokens");
18755
+ }
18756
+ if (record.maxInputTokens !== void 0) {
18757
+ out.maxInputTokens = requireNonNegativeNumber(record.maxInputTokens, "checks.llm.maxInputTokens");
18758
+ }
18759
+ if (record.maxOutputTokens !== void 0) {
18760
+ out.maxOutputTokens = requireNonNegativeNumber(record.maxOutputTokens, "checks.llm.maxOutputTokens");
18761
+ }
18762
+ if (record.maxCachedTokens !== void 0) {
18763
+ out.maxCachedTokens = requireNonNegativeNumber(record.maxCachedTokens, "checks.llm.maxCachedTokens");
18764
+ }
18765
+ return out;
18766
+ }
18767
+ function parseStructureSection(value) {
18768
+ const record = requireObject(value, "checks.structure");
18769
+ rejectUnknownKeys(record, STRUCTURE_KEYS, "checks.structure");
18770
+ const out = {};
18771
+ if (record.minConfidence !== void 0) {
18772
+ if (typeof record.minConfidence !== "string" || !CONFIDENCE_VALUES.has(record.minConfidence)) {
18773
+ throw new CheckConfigError(
18774
+ "AI_CHECK_CONFIG_INVALID_VALUE",
18775
+ `checks.structure.minConfidence must be one of: exact, high, medium, low, inferred.`
18776
+ );
18777
+ }
18778
+ out.minConfidence = record.minConfidence;
18779
+ }
18780
+ if (record.requireParentBeforeChild !== void 0) {
18781
+ out.requireParentBeforeChild = requireBoolean(
18782
+ record.requireParentBeforeChild,
18783
+ "checks.structure.requireParentBeforeChild"
18784
+ );
18785
+ }
18786
+ if (record.requireTraceParentSpan !== void 0) {
18787
+ out.requireTraceParentSpan = requireBoolean(
18788
+ record.requireTraceParentSpan,
18789
+ "checks.structure.requireTraceParentSpan"
18790
+ );
18791
+ }
18792
+ if (record.orphan !== void 0) out.orphan = requireBoolean(record.orphan, "checks.structure.orphan");
18793
+ if (record.cycle !== void 0) out.cycle = requireBoolean(record.cycle, "checks.structure.cycle");
18794
+ if (record.maxChildren !== void 0) {
18795
+ out.maxChildren = requireNonNegativeNumber(record.maxChildren, "checks.structure.maxChildren");
18796
+ }
18797
+ if (record.maxConcurrent !== void 0) {
18798
+ out.maxConcurrent = requireNonNegativeNumber(record.maxConcurrent, "checks.structure.maxConcurrent");
18799
+ }
18800
+ return out;
18801
+ }
18802
+ function parseSafetySection(value) {
18803
+ const record = requireObject(value, "checks.safety");
18804
+ rejectUnknownKeys(record, SAFETY_KEYS, "checks.safety");
18805
+ const out = {};
18806
+ if (record.redaction !== void 0) out.redaction = requireBoolean(record.redaction, "checks.safety.redaction");
18807
+ if (record.rawContent !== void 0) out.rawContent = requireBoolean(record.rawContent, "checks.safety.rawContent");
18808
+ if (record.secretPattern !== void 0) {
18809
+ out.secretPattern = requireBoolean(record.secretPattern, "checks.safety.secretPattern");
18810
+ }
18811
+ if (record.maxStringLength !== void 0) {
18812
+ out.maxStringLength = requireNonNegativeNumber(record.maxStringLength, "checks.safety.maxStringLength");
18813
+ }
18814
+ if (record.maxArrayLength !== void 0) {
18815
+ out.maxArrayLength = requireNonNegativeNumber(record.maxArrayLength, "checks.safety.maxArrayLength");
18816
+ }
18817
+ if (record.maxObjectKeys !== void 0) {
18818
+ out.maxObjectKeys = requireNonNegativeNumber(record.maxObjectKeys, "checks.safety.maxObjectKeys");
18819
+ }
18820
+ if (record.maxSerializedBytes !== void 0) {
18821
+ out.maxSerializedBytes = requireNonNegativeNumber(
18822
+ record.maxSerializedBytes,
18823
+ "checks.safety.maxSerializedBytes"
18824
+ );
18825
+ }
18826
+ if (record.maxFindings !== void 0) {
18827
+ out.maxFindings = requireNonNegativeNumber(record.maxFindings, "checks.safety.maxFindings");
18828
+ }
18829
+ return out;
18830
+ }
18831
+ function sectionHasEffect(section) {
18832
+ if (section === void 0) return false;
18833
+ return Object.keys(section).length > 0;
18834
+ }
18835
+ function checkConfigHasEffect(config) {
18836
+ const checks2 = config.checks;
18837
+ if (checks2 === void 0) return false;
18838
+ if ((checks2.select?.length ?? 0) > 0) return true;
18839
+ if (sectionHasEffect(checks2.run)) return true;
18840
+ if (sectionHasEffect(checks2.tool)) return true;
18841
+ if (sectionHasEffect(checks2.llm)) return true;
18842
+ if (sectionHasEffect(checks2.structure)) return true;
18843
+ if (sectionHasEffect(checks2.safety)) return true;
18844
+ return false;
18845
+ }
18846
+ function parseCheckConfig(value) {
18517
18847
  if (value === void 0 || value === null) return {};
18518
18848
  if (typeof value !== "object" || Array.isArray(value)) {
18519
- throw new Error("Config must export an object.");
18849
+ throw new CheckConfigError("AI_CHECK_CONFIG_INVALID_VALUE", "Config must export an object.");
18520
18850
  }
18521
- return value;
18851
+ const root = value;
18852
+ if ("contract" in root) {
18853
+ throw new CheckConfigError(
18854
+ "AI_CHECK_CONFIG_UNKNOWN_KEY",
18855
+ 'Top-level "contract" is not supported by `agent-inspect check --config`. Use the TraceContract TypeScript API (`defineTraceContract` / `evaluateTraceContract` from `agent-inspect/checks`), or express tool/run/llm rules under `checks`.'
18856
+ );
18857
+ }
18858
+ const rootKeys = Object.keys(root);
18859
+ for (const key of rootKeys) {
18860
+ if (key === "checks") continue;
18861
+ const suggestion = closestKey(key, ["checks"]);
18862
+ const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
18863
+ throw new CheckConfigError(
18864
+ "AI_CHECK_CONFIG_UNKNOWN_KEY",
18865
+ `Unknown top-level check config key "${key}". Allowed: checks.${hint}`
18866
+ );
18867
+ }
18868
+ if (!("checks" in root)) return {};
18869
+ const checksRecord = requireObject(root.checks, "checks");
18870
+ rejectUnknownKeys(checksRecord, CHECKS_KEYS, "checks");
18871
+ const checks2 = {};
18872
+ if (checksRecord.select !== void 0) {
18873
+ checks2.select = requireStringArray(checksRecord.select, "checks.select");
18874
+ }
18875
+ if (checksRecord.run !== void 0) checks2.run = parseRunSection(checksRecord.run);
18876
+ if (checksRecord.tool !== void 0) checks2.tool = parseToolSection(checksRecord.tool);
18877
+ if (checksRecord.llm !== void 0) checks2.llm = parseLlmSection(checksRecord.llm);
18878
+ if (checksRecord.structure !== void 0) {
18879
+ checks2.structure = parseStructureSection(checksRecord.structure);
18880
+ }
18881
+ if (checksRecord.safety !== void 0) checks2.safety = parseSafetySection(checksRecord.safety);
18882
+ return { checks: checks2 };
18522
18883
  }
18523
18884
  async function loadConfig(configPath) {
18524
18885
  if (configPath === void 0) return {};
18525
18886
  const extension = path32__default.default.extname(configPath);
18526
18887
  if (TS_CONFIG_EXTENSIONS2.has(extension)) {
18527
- throw new Error(
18888
+ throw new CheckConfigError(
18889
+ "AI_CHECK_CONFIG_LOAD_FAILED",
18528
18890
  "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
18529
18891
  );
18530
18892
  }
18531
18893
  if (!CONFIG_EXTENSIONS2.has(extension)) {
18532
- throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
18894
+ throw new CheckConfigError(
18895
+ "AI_CHECK_CONFIG_LOAD_FAILED",
18896
+ "Unsupported check config extension. Use .json, .js, .mjs, or .cjs."
18897
+ );
18533
18898
  }
18534
18899
  const absolute = path32__default.default.resolve(configPath);
18535
- if (extension === ".json") {
18536
- const raw = await promises.readFile(absolute, "utf-8");
18537
- return asConfig(JSON.parse(raw));
18900
+ try {
18901
+ if (extension === ".json") {
18902
+ const raw = await promises.readFile(absolute, "utf-8");
18903
+ return parseCheckConfig(JSON.parse(raw));
18904
+ }
18905
+ const mod = await import(url.pathToFileURL(absolute).href);
18906
+ return parseCheckConfig("default" in mod ? mod.default : mod);
18907
+ } catch (error) {
18908
+ if (error instanceof CheckConfigError) throw error;
18909
+ const message = error instanceof Error ? error.message : String(error);
18910
+ throw new CheckConfigError(
18911
+ "AI_CHECK_CONFIG_LOAD_FAILED",
18912
+ `Failed to load check config: ${message}`
18913
+ );
18538
18914
  }
18539
- const mod = await import(url.pathToFileURL(absolute).href);
18540
- return asConfig("default" in mod ? mod.default : mod);
18541
18915
  }
18542
18916
  function normalizeConfig(config) {
18543
18917
  if (config.checks === void 0) return {};
18544
18918
  if (typeof config.checks !== "object" || Array.isArray(config.checks)) {
18545
- throw new Error("checks config must be an object.");
18919
+ throw new CheckConfigError("AI_CHECK_CONFIG_INVALID_VALUE", "checks config must be an object.");
18546
18920
  }
18547
18921
  return config.checks;
18548
18922
  }
@@ -18620,7 +18994,7 @@ function buildRules(config, options, presetSelect = []) {
18620
18994
  }
18621
18995
  throw new Error(`unsupported status "${value}"`);
18622
18996
  });
18623
- rules.push(createObservedOutcomeRule({ failOn: statuses }));
18997
+ rules.push(createObservedOutcomeRule({ failOn: statuses, requireAny: true }));
18624
18998
  } catch (error) {
18625
18999
  const message = error instanceof Error ? error.message : String(error);
18626
19000
  diagnostics.push(
@@ -18692,7 +19066,7 @@ function exitCodeFor(result) {
18692
19066
  return 3;
18693
19067
  }
18694
19068
  if (codes.some(
18695
- (code) => code === "AI_CHECK_INVALID_ARGUMENTS" || code === "AI_CHECK_INVALID_CONFIG" || code === "AI_CHECK_CONFIG_LOAD_FAILED" || code === "AI_CHECK_RUN_SELECTION_REQUIRED"
19069
+ (code) => code === "AI_CHECK_INVALID_ARGUMENTS" || code === "AI_CHECK_INVALID_CONFIG" || code === "AI_CHECK_CONFIG_LOAD_FAILED" || code === "AI_CHECK_CONFIG_UNKNOWN_KEY" || code === "AI_CHECK_CONFIG_INVALID_VALUE" || code === "AI_CHECK_CONFIG_NO_EFFECTIVE_RULES" || code === "AI_CHECK_NO_RULES_EVALUATED" || code === "AI_CHECK_RUN_SELECTION_REQUIRED"
18696
19070
  )) {
18697
19071
  return 2;
18698
19072
  }
@@ -18759,6 +19133,7 @@ function printHuman(result, options = {}) {
18759
19133
  console.log(
18760
19134
  `Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
18761
19135
  );
19136
+ console.log(`Rules evaluated: ${result.summary.rulesEvaluated ?? result.ruleExecutions?.length ?? 0}`);
18762
19137
  for (const diagnostic7 of result.diagnostics) {
18763
19138
  console.log(`- ${diagnostic7.code}: ${diagnostic7.message}`);
18764
19139
  }
@@ -18790,6 +19165,16 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
18790
19165
  const useSessionScope = Boolean(sessionId || groupId);
18791
19166
  try {
18792
19167
  let config = await loadConfig(options.config);
19168
+ if (options.config !== void 0 && !checkConfigHasEffect(config)) {
19169
+ result = errorResult2(
19170
+ "AI_CHECK_CONFIG_NO_EFFECTIVE_RULES",
19171
+ "Explicit --config has no effective check rules. Configure checks.select, checks.run, checks.tool, checks.llm, checks.structure, or checks.safety."
19172
+ );
19173
+ if (options.json) printJson(result);
19174
+ else printHuman(result, options);
19175
+ process.exitCode = exitCodeFor(result);
19176
+ return;
19177
+ }
18793
19178
  let effectiveOptions = options;
18794
19179
  const resolved = resolvePreset(options.preset, {
18795
19180
  hasToolRules: hasToolRulesConfigured(config, options)
@@ -18909,12 +19294,13 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
18909
19294
  }
18910
19295
  } catch (error) {
18911
19296
  if (phase === "config") {
18912
- const message = error instanceof Error ? error.message : String(error);
18913
- const code = message.startsWith("--") || message.includes("Unknown --preset") ? "AI_CHECK_INVALID_ARGUMENTS" : error instanceof SyntaxError || message.includes("Unsupported check config extension") || message.includes("TypeScript check configs") || message.includes("Config must") || message.includes("checks config") || message.includes("Expected an array") ? "AI_CHECK_INVALID_CONFIG" : "AI_CHECK_CONFIG_LOAD_FAILED";
18914
- result = errorResult2(
18915
- code,
18916
- message
18917
- );
19297
+ if (error instanceof CheckConfigError) {
19298
+ result = errorResult2(error.code, error.message);
19299
+ } else {
19300
+ const message = error instanceof Error ? error.message : String(error);
19301
+ const code = message.startsWith("--") || message.includes("Unknown --preset") ? "AI_CHECK_INVALID_ARGUMENTS" : error instanceof SyntaxError || message.includes("Unsupported check config extension") || message.includes("TypeScript check configs") || message.includes("Config must") || message.includes("checks config") || message.includes("Expected an array") ? "AI_CHECK_INVALID_CONFIG" : "AI_CHECK_CONFIG_LOAD_FAILED";
19302
+ result = errorResult2(code, message);
19303
+ }
18918
19304
  } else {
18919
19305
  result = readErrorResult(error);
18920
19306
  }
@@ -19845,6 +20231,42 @@ function startViewerServer(options = {}) {
19845
20231
  });
19846
20232
  });
19847
20233
  }
20234
+ function openInDefaultBrowser(target) {
20235
+ const platform = process.platform;
20236
+ const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
20237
+ const args = platform === "win32" ? ["/c", "start", "", target] : [target];
20238
+ return new Promise((resolve) => {
20239
+ try {
20240
+ const child = child_process.spawn(command, args, {
20241
+ detached: true,
20242
+ stdio: "ignore",
20243
+ windowsHide: platform === "win32"
20244
+ });
20245
+ child.once("spawn", () => {
20246
+ try {
20247
+ child.unref();
20248
+ resolve({ ok: true });
20249
+ } catch (error) {
20250
+ resolve({
20251
+ ok: false,
20252
+ detail: error instanceof Error ? error.message : String(error)
20253
+ });
20254
+ }
20255
+ });
20256
+ child.once("error", (error) => {
20257
+ resolve({
20258
+ ok: false,
20259
+ detail: error instanceof Error ? error.message : String(error)
20260
+ });
20261
+ });
20262
+ } catch (error) {
20263
+ resolve({
20264
+ ok: false,
20265
+ detail: error instanceof Error ? error.message : String(error)
20266
+ });
20267
+ }
20268
+ });
20269
+ }
19848
20270
 
19849
20271
  // packages/cli/src/serve.ts
19850
20272
  function parsePort(value) {
@@ -19874,9 +20296,11 @@ async function serveCommand(options = {}) {
19874
20296
  if (options.open === true && host !== "127.0.0.1" && host !== "localhost") {
19875
20297
  console.warn("Skipping browser open for non-local host binding.");
19876
20298
  } else if (options.open === true) {
19877
- const openMod = await import('child_process');
19878
- openMod.exec(`open ${info.url}`, () => {
19879
- });
20299
+ const opened = await openInDefaultBrowser(info.url);
20300
+ if (!opened.ok) {
20301
+ console.warn("Viewer started, but the browser could not be opened automatically.");
20302
+ console.warn(`Open manually: ${info.url}`);
20303
+ }
19880
20304
  }
19881
20305
  await new Promise(() => {
19882
20306
  });
@@ -20035,9 +20459,11 @@ async function studioCommand(options = {}) {
20035
20459
  if (options.open === true && info.host !== "127.0.0.1" && info.host !== "localhost") {
20036
20460
  console.warn("Skipping browser open for non-local host binding.");
20037
20461
  } else if (options.open === true) {
20038
- const openMod = await import('child_process');
20039
- openMod.exec(`open ${info.url}`, () => {
20040
- });
20462
+ const opened = await openInDefaultBrowser(info.url);
20463
+ if (!opened.ok) {
20464
+ console.warn("Studio started, but the browser could not be opened automatically.");
20465
+ console.warn(`Open manually: ${info.url}`);
20466
+ }
20041
20467
  }
20042
20468
  await new Promise(() => {
20043
20469
  });
@@ -20796,7 +21222,7 @@ function asStringArray3(value, label) {
20796
21222
  }
20797
21223
  return value;
20798
21224
  }
20799
- function asConfig2(value) {
21225
+ function asConfig(value) {
20800
21226
  if (value === void 0 || value === null) return {};
20801
21227
  if (typeof value !== "object" || Array.isArray(value)) {
20802
21228
  throw new Error("Config must export an object.");
@@ -20817,10 +21243,10 @@ async function loadConfig2(configPath) {
20817
21243
  const absolute = path32__default.default.resolve(configPath);
20818
21244
  if (extension === ".json") {
20819
21245
  const raw = await promises.readFile(absolute, "utf-8");
20820
- return asConfig2(JSON.parse(raw));
21246
+ return asConfig(JSON.parse(raw));
20821
21247
  }
20822
21248
  const mod = await import(url.pathToFileURL(absolute).href);
20823
- return asConfig2("default" in mod ? mod.default : mod);
21249
+ return asConfig("default" in mod ? mod.default : mod);
20824
21250
  }
20825
21251
  function normalizeConfig2(config) {
20826
21252
  if (config.eval === void 0) return {};
@@ -22335,33 +22761,6 @@ async function resolveHtmlPath(root) {
22335
22761
  `No evidence.html found under ${root}. Pass a bundle directory or .html path.`
22336
22762
  );
22337
22763
  }
22338
- function openLocalFile(filePath) {
22339
- const fileUrl = url.pathToFileURL(filePath).href;
22340
- const platform = process.platform;
22341
- const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
22342
- const args = platform === "darwin" ? [filePath] : platform === "win32" ? ["/c", "start", "", filePath] : [filePath];
22343
- return new Promise((resolve) => {
22344
- try {
22345
- const child = child_process.spawn(command, args, {
22346
- detached: true,
22347
- stdio: "ignore"
22348
- });
22349
- child.on("error", (error) => {
22350
- resolve({
22351
- ok: false,
22352
- detail: `${error instanceof Error ? error.message : String(error)}; open manually: ${fileUrl}`
22353
- });
22354
- });
22355
- child.unref();
22356
- resolve({ ok: true, detail: fileUrl });
22357
- } catch (error) {
22358
- resolve({
22359
- ok: false,
22360
- detail: `${error instanceof Error ? error.message : String(error)}; open manually: ${fileUrl}`
22361
- });
22362
- }
22363
- });
22364
- }
22365
22764
  async function bundleOpenCommand(targetPath, options = {}) {
22366
22765
  const root = path32__default.default.resolve(targetPath.trim() || ".");
22367
22766
  try {
@@ -22431,23 +22830,24 @@ async function bundleOpenCommand(targetPath, options = {}) {
22431
22830
  await promises.readFile(path32__default.default.join(path32__default.default.dirname(htmlPath), "evidence.json"), "utf-8");
22432
22831
  } catch {
22433
22832
  }
22434
- const opened = await openLocalFile(htmlPath);
22833
+ const fileUrl = url.pathToFileURL(htmlPath).href;
22834
+ const opened = await openInDefaultBrowser(fileUrl);
22435
22835
  if (options.json) {
22436
22836
  console.log(
22437
22837
  writeJson6({
22438
22838
  ok: opened.ok,
22439
22839
  path: htmlPath,
22440
22840
  opened: opened.ok,
22441
- detail: opened.detail
22841
+ detail: opened.ok ? fileUrl : `${opened.detail ?? "unknown error"}; open manually: ${fileUrl}`
22442
22842
  }).trimEnd()
22443
22843
  );
22444
22844
  } else if (opened.ok) {
22445
22845
  console.log(`Opened local Evidence: ${htmlPath}`);
22446
- console.log(`URL: ${opened.detail}`);
22846
+ console.log(`URL: ${fileUrl}`);
22447
22847
  } else {
22448
22848
  console.error(`[AgentInspect] Could not open browser automatically.`);
22449
22849
  console.error(`Open this file locally: ${htmlPath}`);
22450
- console.error(opened.detail);
22850
+ console.error(`${opened.detail ?? "unknown error"}; open manually: ${fileUrl}`);
22451
22851
  }
22452
22852
  if (!opened.ok) {
22453
22853
  process.exitCode = 0;