agent-inspect 6.17.3 → 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 (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/docs/ADAPTER-CONFORMANCE.md +24 -0
  4. package/docs/ADAPTERS.md +3 -3
  5. package/docs/API.md +13 -7
  6. package/docs/CI-ARTIFACTS.md +2 -1
  7. package/docs/CLI.md +2 -0
  8. package/docs/LIMITATIONS.md +1 -0
  9. package/docs/OPENAI-AGENTS-LOCAL.md +14 -0
  10. package/docs/SAFE-TRACE-SHARING.md +1 -0
  11. package/docs/SELF-HOSTING.md +2 -0
  12. package/docs/STANDARDS.md +15 -0
  13. package/docs/SUPPORT-LEVELS.md +23 -0
  14. package/docs/TRACE-CONTRACTS.md +90 -0
  15. package/package.json +7 -3
  16. package/packages/cli/dist/{chunk-5GGYDIZD.mjs → chunk-GOHYSE7W.mjs} +93 -19
  17. package/packages/cli/dist/chunk-GOHYSE7W.mjs.map +1 -0
  18. package/packages/cli/dist/index.cjs +505 -88
  19. package/packages/cli/dist/index.cjs.map +1 -1
  20. package/packages/cli/dist/index.mjs +417 -74
  21. package/packages/cli/dist/index.mjs.map +1 -1
  22. package/packages/cli/dist/{src-IDMCWKRH.mjs → src-OYGG7QAZ.mjs} +3 -3
  23. package/packages/cli/dist/{src-IDMCWKRH.mjs.map → src-OYGG7QAZ.mjs.map} +1 -1
  24. package/packages/core/dist/advanced.cjs +86 -17
  25. package/packages/core/dist/advanced.cjs.map +1 -1
  26. package/packages/core/dist/advanced.d.cts +1 -1
  27. package/packages/core/dist/advanced.d.ts +1 -1
  28. package/packages/core/dist/advanced.mjs +15 -5
  29. package/packages/core/dist/advanced.mjs.map +1 -1
  30. package/packages/core/dist/checks.cjs +126 -28
  31. package/packages/core/dist/checks.cjs.map +1 -1
  32. package/packages/core/dist/checks.d.cts +28 -2
  33. package/packages/core/dist/checks.d.ts +28 -2
  34. package/packages/core/dist/checks.mjs +1 -1
  35. package/packages/core/dist/{chunk-UFP54T7F.mjs → chunk-DIZPIPY2.mjs} +128 -30
  36. package/packages/core/dist/chunk-DIZPIPY2.mjs.map +1 -0
  37. package/packages/core/dist/{index-Xk9X-yjY.d.cts → index-DWu54Y28.d.cts} +107 -122
  38. package/packages/core/dist/{index-BsCcOKxy.d.ts → index-DlwbVqEs.d.ts} +107 -122
  39. package/packages/cli/dist/chunk-5GGYDIZD.mjs.map +0 -1
  40. package/packages/core/dist/chunk-UFP54T7F.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createSafetyOversizedAttributeRule, resolveTraceDir, parseDuration, buildSessionIndex, resolveBundleRunIds, getTraceFilePath, openTrace, sha256Hex, collectTraceSchemaVersions, aggregateBundleSafeStatus, bundleFailsOnSafety, bundleRunAssetRelativePath, buildPlaceholderArtifact, EVIDENCE_HTML_FILENAME, EVIDENCE_MANIFEST_FILENAME, buildBundleMetadata, buildBundleSummaryMarkdown, buildEvidenceHtmlShell, buildEvidenceProvenanceViewHtml, EVIDENCE_FORMAT_VERSION, inferEvidenceFileRole, buildEvidenceSafetyViewHtml, buildEvidenceDiffViewHtml, buildEvidenceCircuitViewHtml, buildEvidenceOutcomesViewHtml, buildEvidenceContractsViewHtml, buildEvidenceToolsLlmViewHtml, buildEvidenceCausalFailureViewHtml, buildEvidenceTimelineViewHtml, buildEvidenceTreeViewHtml, buildEvidenceManifest, serializeEvidenceManifest, buildZipArchive, assertBundlePathContained, enrichSessionRunRecord, normalizeBundleOutputPath, sanitizeBundleRunId, defaultBundleOutputPath, runTraceChecks, assertEvidenceRelativePath, TraceDirectory, loadTraceMetadataList, loadSessionRunRecords, TraceReadError, resolveRedactionProfile, applyProfileMetadataCaps, Redactor, flattenTree, escapeHtml, safeString, compactAttributes, stableJson, escapeMarkdown, truncateStringForProfile, extractMetadata, filterTraces, truncateName, buildRunSummary, formatDuration, formatTimestamp, isAgentInspectTrace, filterMetasBySessionScope, aggregateSessionCheckResults, createBaselineRegressionRule, summarizeSemanticParity, buildEvidenceCiPackage, verifyEvidenceDirectory, diffTraceEvents, renderRunDiff, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, formatStepLabel, buildActivitySummary, renderActivitySummaryHuman, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, resolveSuiteTemplate, defaultSuiteConfigTemplate, loadSuiteConfig, validateSuiteConfig, runSuite, renderSuiteReport, analyzeCohort, renderCohortReport, gateHasThresholds, runGate, renderGateReport, persistedInspectEventsToTraceEvents, renderStepLine, renderErrorLine, getIndent, TreeBuilder, zeroKinds, validateEvent, traceEventToPersistedInspectEvent, isPersistedInspectEvent, createRunStatusRule, createStructureOrphanRule, createStructureCycleRule, createRunDurationRule, createMaxStepDurationRule, createRequireCompletedRule, createStallDetectionRule, createObservedOutcomeRule, createRunDepthRule, createToolUsageRule, createLlmUsageRule, createStructureRelationshipRule, createStructureParallelWidthRule, summarizeObservedOutcomes, extractOutcomesFromTraceEvents, renderObservedOutcomesMarkdown, renderObservedOutcomesHtml, DEFAULT_SUITE_ARTIFACTS_DIR, parseCohortMetricList, COHORT_METRIC_IDS, parseGateList, nanoid, diffRuns, manualTraceEventsToComparableRun } from './chunk-5GGYDIZD.mjs';
2
+ import { createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createSafetyOversizedAttributeRule, resolveTraceDir, parseDuration, buildSessionIndex, resolveBundleRunIds, getTraceFilePath, openTrace, sha256Hex, collectTraceSchemaVersions, aggregateBundleSafeStatus, bundleFailsOnSafety, bundleRunAssetRelativePath, buildPlaceholderArtifact, EVIDENCE_HTML_FILENAME, EVIDENCE_MANIFEST_FILENAME, buildBundleMetadata, buildBundleSummaryMarkdown, buildEvidenceHtmlShell, buildEvidenceProvenanceViewHtml, EVIDENCE_FORMAT_VERSION, inferEvidenceFileRole, buildEvidenceSafetyViewHtml, buildEvidenceDiffViewHtml, buildEvidenceCircuitViewHtml, buildEvidenceOutcomesViewHtml, buildEvidenceContractsViewHtml, buildEvidenceToolsLlmViewHtml, buildEvidenceCausalFailureViewHtml, buildEvidenceTimelineViewHtml, buildEvidenceTreeViewHtml, buildEvidenceManifest, serializeEvidenceManifest, buildZipArchive, assertBundlePathContained, enrichSessionRunRecord, normalizeBundleOutputPath, sanitizeBundleRunId, defaultBundleOutputPath, runTraceChecks, assertEvidenceRelativePath, TraceDirectory, loadTraceMetadataList, loadSessionRunRecords, TraceReadError, resolveRedactionProfile, applyProfileMetadataCaps, Redactor, flattenTree, escapeHtml, safeString, compactAttributes, stableJson, escapeMarkdown, truncateStringForProfile, extractMetadata, filterTraces, truncateName, buildRunSummary, formatDuration, formatTimestamp, isAgentInspectTrace, filterMetasBySessionScope, aggregateSessionCheckResults, createBaselineRegressionRule, summarizeSemanticParity, buildEvidenceCiPackage, verifyEvidenceDirectory, diffTraceEvents, renderRunDiff, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, formatStepLabel, buildActivitySummary, renderActivitySummaryHuman, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, resolveSuiteTemplate, defaultSuiteConfigTemplate, loadSuiteConfig, validateSuiteConfig, runSuite, renderSuiteReport, analyzeCohort, renderCohortReport, gateHasThresholds, runGate, renderGateReport, persistedInspectEventsToTraceEvents, renderStepLine, renderErrorLine, getIndent, TreeBuilder, zeroKinds, validateEvent, traceEventToPersistedInspectEvent, isPersistedInspectEvent, createRunStatusRule, createStructureOrphanRule, createStructureCycleRule, createRunDurationRule, createMaxStepDurationRule, createRequireCompletedRule, createStallDetectionRule, createObservedOutcomeRule, createRunDepthRule, createToolUsageRule, createLlmUsageRule, createStructureRelationshipRule, createStructureParallelWidthRule, summarizeObservedOutcomes, extractOutcomesFromTraceEvents, renderObservedOutcomesMarkdown, renderObservedOutcomesHtml, DEFAULT_SUITE_ARTIFACTS_DIR, parseCohortMetricList, COHORT_METRIC_IDS, parseGateList, nanoid, diffRuns, manualTraceEventsToComparableRun } from './chunk-GOHYSE7W.mjs';
3
3
  import { realpathSync, constants as constants$1, existsSync, readFileSync } from 'fs';
4
4
  import path18 from 'path';
5
5
  import { fileURLToPath, pathToFileURL } from 'url';
@@ -12,7 +12,7 @@ import { spawn } from 'child_process';
12
12
  import { createRequire } from 'module';
13
13
 
14
14
  // package.json
15
- var version = "6.17.3";
15
+ var version = "6.17.5";
16
16
 
17
17
  // packages/cli/src/trace-dir-scale.ts
18
18
  var TRACE_COUNT_WARN = 1e3;
@@ -1540,6 +1540,11 @@ async function followFile(filePath, options, onLine, shouldStop) {
1540
1540
  const msg = e instanceof Error ? e.message : String(e);
1541
1541
  throw new Error(`Failed to stat file: ${filePath} (${msg})`);
1542
1542
  }
1543
+ if (next.size < pos) {
1544
+ pos = 0;
1545
+ carry = "";
1546
+ lineNumber = 0;
1547
+ }
1543
1548
  if (next.size > pos) {
1544
1549
  const fh = await open(filePath, "r");
1545
1550
  try {
@@ -3347,7 +3352,7 @@ async function newestTraceMtimeMs(traceDir) {
3347
3352
  }
3348
3353
  async function loadSessionRuns(traceDir) {
3349
3354
  try {
3350
- const mod = await import('./src-IDMCWKRH.mjs');
3355
+ const mod = await import('./src-OYGG7QAZ.mjs');
3351
3356
  const dbPath = mod.resolveIndexDbPath(traceDir);
3352
3357
  const status = mod.indexStatus(dbPath);
3353
3358
  if (!status.healthy) {
@@ -3820,8 +3825,11 @@ async function reportCommand(runId, options = {}) {
3820
3825
  }
3821
3826
 
3822
3827
  // packages/redact/src/sensitive-key.ts
3828
+ function keyHasExplicitSeparator(value) {
3829
+ return /[_\-.]/.test(value);
3830
+ }
3823
3831
  function normalizeSensitiveKey(value) {
3824
- return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
3832
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
3825
3833
  }
3826
3834
  var NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
3827
3835
  [
@@ -3851,6 +3859,7 @@ function isCredentialSensitiveKey(key, sensitiveKeys) {
3851
3859
  const normalized = normalizeSensitiveKey(key);
3852
3860
  if (!normalized) return false;
3853
3861
  if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
3862
+ const allowPrefixCompound = keyHasExplicitSeparator(key);
3854
3863
  for (const sensitive of sensitiveKeys) {
3855
3864
  const s = normalizeSensitiveKey(sensitive);
3856
3865
  if (!s) continue;
@@ -3859,7 +3868,8 @@ function isCredentialSensitiveKey(key, sensitiveKeys) {
3859
3868
  continue;
3860
3869
  }
3861
3870
  if (normalized === s) return true;
3862
- if (normalized.endsWith(`_${s}`) || normalized.startsWith(`${s}_`)) return true;
3871
+ if (normalized.endsWith(`_${s}`)) return true;
3872
+ if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
3863
3873
  }
3864
3874
  return false;
3865
3875
  }
@@ -5138,8 +5148,11 @@ function checkResultToEvidenceJson(result, runIds) {
5138
5148
  status,
5139
5149
  errors: result.summary.errors,
5140
5150
  warnings: result.summary.warnings,
5141
- findings: result.findings.length
5151
+ findings: result.findings.length,
5152
+ rulesEvaluated: result.summary.rulesEvaluated ?? 0
5142
5153
  })),
5154
+ rulesEvaluated: result.summary.rulesEvaluated ?? 0,
5155
+ evaluatedRuleIds: (result.ruleExecutions ?? []).map((item) => item.ruleId),
5143
5156
  findings: result.findings,
5144
5157
  diagnostics: result.diagnostics
5145
5158
  });
@@ -5764,9 +5777,11 @@ function mergeSafetyExtensions(result, read, options) {
5764
5777
  passed,
5765
5778
  failed,
5766
5779
  warnings,
5767
- errors: result.summary.errors
5780
+ errors: result.summary.errors,
5781
+ rulesEvaluated: result.summary.rulesEvaluated ?? 0
5768
5782
  },
5769
- findings
5783
+ findings,
5784
+ ruleExecutions: result.ruleExecutions ?? []
5770
5785
  };
5771
5786
  }
5772
5787
 
@@ -5886,10 +5901,12 @@ function errorResult(code, message, format = "unknown") {
5886
5901
  passed: 0,
5887
5902
  failed: 0,
5888
5903
  warnings: 0,
5889
- errors: 1
5904
+ errors: 1,
5905
+ rulesEvaluated: 0
5890
5906
  },
5891
5907
  findings: [],
5892
- diagnostics
5908
+ diagnostics,
5909
+ ruleExecutions: []
5893
5910
  };
5894
5911
  }
5895
5912
  function parseNumber(value, label) {
@@ -5907,36 +5924,329 @@ function asStringArray(value) {
5907
5924
  }
5908
5925
  return value;
5909
5926
  }
5910
- function asConfig(value) {
5927
+ var CHECKS_KEYS = /* @__PURE__ */ new Set(["select", "run", "tool", "llm", "structure", "safety"]);
5928
+ var RUN_KEYS = /* @__PURE__ */ new Set(["expected", "allowIncomplete", "maxDurationMs", "maxDepth"]);
5929
+ var TOOL_KEYS = /* @__PURE__ */ new Set(["required", "forbidden", "allowed", "minCount", "maxCount"]);
5930
+ var LLM_KEYS = /* @__PURE__ */ new Set([
5931
+ "allowedModels",
5932
+ "allowedProviders",
5933
+ "finishReasons",
5934
+ "maxCalls",
5935
+ "maxTotalTokens",
5936
+ "maxInputTokens",
5937
+ "maxOutputTokens",
5938
+ "maxCachedTokens"
5939
+ ]);
5940
+ var STRUCTURE_KEYS = /* @__PURE__ */ new Set([
5941
+ "minConfidence",
5942
+ "requireParentBeforeChild",
5943
+ "requireTraceParentSpan",
5944
+ "orphan",
5945
+ "cycle",
5946
+ "maxChildren",
5947
+ "maxConcurrent"
5948
+ ]);
5949
+ var SAFETY_KEYS = /* @__PURE__ */ new Set([
5950
+ "redaction",
5951
+ "rawContent",
5952
+ "secretPattern",
5953
+ "maxStringLength",
5954
+ "maxArrayLength",
5955
+ "maxObjectKeys",
5956
+ "maxSerializedBytes",
5957
+ "maxFindings"
5958
+ ]);
5959
+ var RUN_EXPECTED = /* @__PURE__ */ new Set(["ok", "error", "running"]);
5960
+ var CONFIDENCE_VALUES = /* @__PURE__ */ new Set(["exact", "high", "medium", "low", "inferred"]);
5961
+ var CheckConfigError = class extends Error {
5962
+ code;
5963
+ constructor(code, message) {
5964
+ super(message);
5965
+ this.name = "CheckConfigError";
5966
+ this.code = code;
5967
+ }
5968
+ };
5969
+ function closestKey(unknown, known) {
5970
+ const lower = unknown.toLowerCase();
5971
+ let best;
5972
+ let bestDistance = Number.POSITIVE_INFINITY;
5973
+ for (const candidate of known) {
5974
+ if (candidate.toLowerCase() === lower) return candidate;
5975
+ let distance = Math.abs(candidate.length - unknown.length);
5976
+ const max = Math.max(candidate.length, unknown.length);
5977
+ for (let i = 0; i < Math.min(candidate.length, unknown.length); i += 1) {
5978
+ if (candidate[i].toLowerCase() !== unknown[i].toLowerCase()) distance += 1;
5979
+ }
5980
+ if (distance < bestDistance && distance <= Math.max(2, Math.floor(max / 3))) {
5981
+ bestDistance = distance;
5982
+ best = candidate;
5983
+ }
5984
+ }
5985
+ return best;
5986
+ }
5987
+ function rejectUnknownKeys(record, allowed, pathPrefix) {
5988
+ for (const key of Object.keys(record)) {
5989
+ if (allowed.has(key)) continue;
5990
+ const suggestion = closestKey(key, [...allowed]);
5991
+ const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
5992
+ throw new CheckConfigError(
5993
+ "AI_CHECK_CONFIG_UNKNOWN_KEY",
5994
+ `Unknown check config key "${pathPrefix}.${key}".${hint}`
5995
+ );
5996
+ }
5997
+ }
5998
+ function requireObject(value, pathPrefix) {
5999
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
6000
+ throw new CheckConfigError(
6001
+ "AI_CHECK_CONFIG_INVALID_VALUE",
6002
+ `${pathPrefix} must be an object.`
6003
+ );
6004
+ }
6005
+ return value;
6006
+ }
6007
+ function requireStringArray(value, pathPrefix) {
6008
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "")) {
6009
+ throw new CheckConfigError(
6010
+ "AI_CHECK_CONFIG_INVALID_VALUE",
6011
+ `${pathPrefix} must be an array of non-empty strings.`
6012
+ );
6013
+ }
6014
+ return value;
6015
+ }
6016
+ function requireNonNegativeNumber(value, pathPrefix) {
6017
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
6018
+ throw new CheckConfigError(
6019
+ "AI_CHECK_CONFIG_INVALID_VALUE",
6020
+ `${pathPrefix} must be a finite non-negative number.`
6021
+ );
6022
+ }
6023
+ return value;
6024
+ }
6025
+ function requireBoolean(value, pathPrefix) {
6026
+ if (typeof value !== "boolean") {
6027
+ throw new CheckConfigError(
6028
+ "AI_CHECK_CONFIG_INVALID_VALUE",
6029
+ `${pathPrefix} must be a boolean.`
6030
+ );
6031
+ }
6032
+ return value;
6033
+ }
6034
+ function parseRunSection(value) {
6035
+ const record = requireObject(value, "checks.run");
6036
+ rejectUnknownKeys(record, RUN_KEYS, "checks.run");
6037
+ const out = {};
6038
+ if (record.expected !== void 0) {
6039
+ if (typeof record.expected !== "string" || !RUN_EXPECTED.has(record.expected)) {
6040
+ throw new CheckConfigError(
6041
+ "AI_CHECK_CONFIG_INVALID_VALUE",
6042
+ `checks.run.expected must be one of: ok, error, running.`
6043
+ );
6044
+ }
6045
+ out.expected = record.expected;
6046
+ }
6047
+ if (record.allowIncomplete !== void 0) {
6048
+ out.allowIncomplete = requireBoolean(record.allowIncomplete, "checks.run.allowIncomplete");
6049
+ }
6050
+ if (record.maxDurationMs !== void 0) {
6051
+ out.maxDurationMs = requireNonNegativeNumber(record.maxDurationMs, "checks.run.maxDurationMs");
6052
+ }
6053
+ if (record.maxDepth !== void 0) {
6054
+ out.maxDepth = requireNonNegativeNumber(record.maxDepth, "checks.run.maxDepth");
6055
+ }
6056
+ return out;
6057
+ }
6058
+ function parseToolSection(value) {
6059
+ const record = requireObject(value, "checks.tool");
6060
+ rejectUnknownKeys(record, TOOL_KEYS, "checks.tool");
6061
+ const out = {};
6062
+ if (record.required !== void 0) out.required = requireStringArray(record.required, "checks.tool.required");
6063
+ if (record.forbidden !== void 0) out.forbidden = requireStringArray(record.forbidden, "checks.tool.forbidden");
6064
+ if (record.allowed !== void 0) out.allowed = requireStringArray(record.allowed, "checks.tool.allowed");
6065
+ if (record.minCount !== void 0) out.minCount = requireNonNegativeNumber(record.minCount, "checks.tool.minCount");
6066
+ if (record.maxCount !== void 0) out.maxCount = requireNonNegativeNumber(record.maxCount, "checks.tool.maxCount");
6067
+ return out;
6068
+ }
6069
+ function parseLlmSection(value) {
6070
+ const record = requireObject(value, "checks.llm");
6071
+ rejectUnknownKeys(record, LLM_KEYS, "checks.llm");
6072
+ const out = {};
6073
+ if (record.allowedModels !== void 0) {
6074
+ out.allowedModels = requireStringArray(record.allowedModels, "checks.llm.allowedModels");
6075
+ }
6076
+ if (record.allowedProviders !== void 0) {
6077
+ out.allowedProviders = requireStringArray(record.allowedProviders, "checks.llm.allowedProviders");
6078
+ }
6079
+ if (record.finishReasons !== void 0) {
6080
+ out.finishReasons = requireStringArray(record.finishReasons, "checks.llm.finishReasons");
6081
+ }
6082
+ if (record.maxCalls !== void 0) out.maxCalls = requireNonNegativeNumber(record.maxCalls, "checks.llm.maxCalls");
6083
+ if (record.maxTotalTokens !== void 0) {
6084
+ out.maxTotalTokens = requireNonNegativeNumber(record.maxTotalTokens, "checks.llm.maxTotalTokens");
6085
+ }
6086
+ if (record.maxInputTokens !== void 0) {
6087
+ out.maxInputTokens = requireNonNegativeNumber(record.maxInputTokens, "checks.llm.maxInputTokens");
6088
+ }
6089
+ if (record.maxOutputTokens !== void 0) {
6090
+ out.maxOutputTokens = requireNonNegativeNumber(record.maxOutputTokens, "checks.llm.maxOutputTokens");
6091
+ }
6092
+ if (record.maxCachedTokens !== void 0) {
6093
+ out.maxCachedTokens = requireNonNegativeNumber(record.maxCachedTokens, "checks.llm.maxCachedTokens");
6094
+ }
6095
+ return out;
6096
+ }
6097
+ function parseStructureSection(value) {
6098
+ const record = requireObject(value, "checks.structure");
6099
+ rejectUnknownKeys(record, STRUCTURE_KEYS, "checks.structure");
6100
+ const out = {};
6101
+ if (record.minConfidence !== void 0) {
6102
+ if (typeof record.minConfidence !== "string" || !CONFIDENCE_VALUES.has(record.minConfidence)) {
6103
+ throw new CheckConfigError(
6104
+ "AI_CHECK_CONFIG_INVALID_VALUE",
6105
+ `checks.structure.minConfidence must be one of: exact, high, medium, low, inferred.`
6106
+ );
6107
+ }
6108
+ out.minConfidence = record.minConfidence;
6109
+ }
6110
+ if (record.requireParentBeforeChild !== void 0) {
6111
+ out.requireParentBeforeChild = requireBoolean(
6112
+ record.requireParentBeforeChild,
6113
+ "checks.structure.requireParentBeforeChild"
6114
+ );
6115
+ }
6116
+ if (record.requireTraceParentSpan !== void 0) {
6117
+ out.requireTraceParentSpan = requireBoolean(
6118
+ record.requireTraceParentSpan,
6119
+ "checks.structure.requireTraceParentSpan"
6120
+ );
6121
+ }
6122
+ if (record.orphan !== void 0) out.orphan = requireBoolean(record.orphan, "checks.structure.orphan");
6123
+ if (record.cycle !== void 0) out.cycle = requireBoolean(record.cycle, "checks.structure.cycle");
6124
+ if (record.maxChildren !== void 0) {
6125
+ out.maxChildren = requireNonNegativeNumber(record.maxChildren, "checks.structure.maxChildren");
6126
+ }
6127
+ if (record.maxConcurrent !== void 0) {
6128
+ out.maxConcurrent = requireNonNegativeNumber(record.maxConcurrent, "checks.structure.maxConcurrent");
6129
+ }
6130
+ return out;
6131
+ }
6132
+ function parseSafetySection(value) {
6133
+ const record = requireObject(value, "checks.safety");
6134
+ rejectUnknownKeys(record, SAFETY_KEYS, "checks.safety");
6135
+ const out = {};
6136
+ if (record.redaction !== void 0) out.redaction = requireBoolean(record.redaction, "checks.safety.redaction");
6137
+ if (record.rawContent !== void 0) out.rawContent = requireBoolean(record.rawContent, "checks.safety.rawContent");
6138
+ if (record.secretPattern !== void 0) {
6139
+ out.secretPattern = requireBoolean(record.secretPattern, "checks.safety.secretPattern");
6140
+ }
6141
+ if (record.maxStringLength !== void 0) {
6142
+ out.maxStringLength = requireNonNegativeNumber(record.maxStringLength, "checks.safety.maxStringLength");
6143
+ }
6144
+ if (record.maxArrayLength !== void 0) {
6145
+ out.maxArrayLength = requireNonNegativeNumber(record.maxArrayLength, "checks.safety.maxArrayLength");
6146
+ }
6147
+ if (record.maxObjectKeys !== void 0) {
6148
+ out.maxObjectKeys = requireNonNegativeNumber(record.maxObjectKeys, "checks.safety.maxObjectKeys");
6149
+ }
6150
+ if (record.maxSerializedBytes !== void 0) {
6151
+ out.maxSerializedBytes = requireNonNegativeNumber(
6152
+ record.maxSerializedBytes,
6153
+ "checks.safety.maxSerializedBytes"
6154
+ );
6155
+ }
6156
+ if (record.maxFindings !== void 0) {
6157
+ out.maxFindings = requireNonNegativeNumber(record.maxFindings, "checks.safety.maxFindings");
6158
+ }
6159
+ return out;
6160
+ }
6161
+ function sectionHasEffect(section) {
6162
+ if (section === void 0) return false;
6163
+ return Object.keys(section).length > 0;
6164
+ }
6165
+ function checkConfigHasEffect(config) {
6166
+ const checks2 = config.checks;
6167
+ if (checks2 === void 0) return false;
6168
+ if ((checks2.select?.length ?? 0) > 0) return true;
6169
+ if (sectionHasEffect(checks2.run)) return true;
6170
+ if (sectionHasEffect(checks2.tool)) return true;
6171
+ if (sectionHasEffect(checks2.llm)) return true;
6172
+ if (sectionHasEffect(checks2.structure)) return true;
6173
+ if (sectionHasEffect(checks2.safety)) return true;
6174
+ return false;
6175
+ }
6176
+ function parseCheckConfig(value) {
5911
6177
  if (value === void 0 || value === null) return {};
5912
6178
  if (typeof value !== "object" || Array.isArray(value)) {
5913
- throw new Error("Config must export an object.");
6179
+ throw new CheckConfigError("AI_CHECK_CONFIG_INVALID_VALUE", "Config must export an object.");
5914
6180
  }
5915
- return value;
6181
+ const root = value;
6182
+ if ("contract" in root) {
6183
+ throw new CheckConfigError(
6184
+ "AI_CHECK_CONFIG_UNKNOWN_KEY",
6185
+ '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`.'
6186
+ );
6187
+ }
6188
+ const rootKeys = Object.keys(root);
6189
+ for (const key of rootKeys) {
6190
+ if (key === "checks") continue;
6191
+ const suggestion = closestKey(key, ["checks"]);
6192
+ const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
6193
+ throw new CheckConfigError(
6194
+ "AI_CHECK_CONFIG_UNKNOWN_KEY",
6195
+ `Unknown top-level check config key "${key}". Allowed: checks.${hint}`
6196
+ );
6197
+ }
6198
+ if (!("checks" in root)) return {};
6199
+ const checksRecord = requireObject(root.checks, "checks");
6200
+ rejectUnknownKeys(checksRecord, CHECKS_KEYS, "checks");
6201
+ const checks2 = {};
6202
+ if (checksRecord.select !== void 0) {
6203
+ checks2.select = requireStringArray(checksRecord.select, "checks.select");
6204
+ }
6205
+ if (checksRecord.run !== void 0) checks2.run = parseRunSection(checksRecord.run);
6206
+ if (checksRecord.tool !== void 0) checks2.tool = parseToolSection(checksRecord.tool);
6207
+ if (checksRecord.llm !== void 0) checks2.llm = parseLlmSection(checksRecord.llm);
6208
+ if (checksRecord.structure !== void 0) {
6209
+ checks2.structure = parseStructureSection(checksRecord.structure);
6210
+ }
6211
+ if (checksRecord.safety !== void 0) checks2.safety = parseSafetySection(checksRecord.safety);
6212
+ return { checks: checks2 };
5916
6213
  }
5917
6214
  async function loadConfig(configPath) {
5918
6215
  if (configPath === void 0) return {};
5919
6216
  const extension = path18.extname(configPath);
5920
6217
  if (TS_CONFIG_EXTENSIONS.has(extension)) {
5921
- throw new Error(
6218
+ throw new CheckConfigError(
6219
+ "AI_CHECK_CONFIG_LOAD_FAILED",
5922
6220
  "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
5923
6221
  );
5924
6222
  }
5925
6223
  if (!CONFIG_EXTENSIONS.has(extension)) {
5926
- throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
6224
+ throw new CheckConfigError(
6225
+ "AI_CHECK_CONFIG_LOAD_FAILED",
6226
+ "Unsupported check config extension. Use .json, .js, .mjs, or .cjs."
6227
+ );
5927
6228
  }
5928
6229
  const absolute = path18.resolve(configPath);
5929
- if (extension === ".json") {
5930
- const raw = await readFile(absolute, "utf-8");
5931
- return asConfig(JSON.parse(raw));
6230
+ try {
6231
+ if (extension === ".json") {
6232
+ const raw = await readFile(absolute, "utf-8");
6233
+ return parseCheckConfig(JSON.parse(raw));
6234
+ }
6235
+ const mod = await import(pathToFileURL(absolute).href);
6236
+ return parseCheckConfig("default" in mod ? mod.default : mod);
6237
+ } catch (error) {
6238
+ if (error instanceof CheckConfigError) throw error;
6239
+ const message = error instanceof Error ? error.message : String(error);
6240
+ throw new CheckConfigError(
6241
+ "AI_CHECK_CONFIG_LOAD_FAILED",
6242
+ `Failed to load check config: ${message}`
6243
+ );
5932
6244
  }
5933
- const mod = await import(pathToFileURL(absolute).href);
5934
- return asConfig("default" in mod ? mod.default : mod);
5935
6245
  }
5936
6246
  function normalizeConfig(config) {
5937
6247
  if (config.checks === void 0) return {};
5938
6248
  if (typeof config.checks !== "object" || Array.isArray(config.checks)) {
5939
- throw new Error("checks config must be an object.");
6249
+ throw new CheckConfigError("AI_CHECK_CONFIG_INVALID_VALUE", "checks config must be an object.");
5940
6250
  }
5941
6251
  return config.checks;
5942
6252
  }
@@ -6014,7 +6324,7 @@ function buildRules(config, options, presetSelect = []) {
6014
6324
  }
6015
6325
  throw new Error(`unsupported status "${value}"`);
6016
6326
  });
6017
- rules.push(createObservedOutcomeRule({ failOn: statuses }));
6327
+ rules.push(createObservedOutcomeRule({ failOn: statuses, requireAny: true }));
6018
6328
  } catch (error) {
6019
6329
  const message = error instanceof Error ? error.message : String(error);
6020
6330
  diagnostics.push(
@@ -6086,7 +6396,7 @@ function exitCodeFor(result) {
6086
6396
  return 3;
6087
6397
  }
6088
6398
  if (codes.some(
6089
- (code) => code === "AI_CHECK_INVALID_ARGUMENTS" || code === "AI_CHECK_INVALID_CONFIG" || code === "AI_CHECK_CONFIG_LOAD_FAILED" || code === "AI_CHECK_RUN_SELECTION_REQUIRED"
6399
+ (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"
6090
6400
  )) {
6091
6401
  return 2;
6092
6402
  }
@@ -6153,6 +6463,7 @@ function printHuman(result, options = {}) {
6153
6463
  console.log(
6154
6464
  `Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
6155
6465
  );
6466
+ console.log(`Rules evaluated: ${result.summary.rulesEvaluated ?? result.ruleExecutions?.length ?? 0}`);
6156
6467
  for (const diagnostic3 of result.diagnostics) {
6157
6468
  console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
6158
6469
  }
@@ -6184,6 +6495,16 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
6184
6495
  const useSessionScope = Boolean(sessionId || groupId);
6185
6496
  try {
6186
6497
  let config = await loadConfig(options.config);
6498
+ if (options.config !== void 0 && !checkConfigHasEffect(config)) {
6499
+ result = errorResult(
6500
+ "AI_CHECK_CONFIG_NO_EFFECTIVE_RULES",
6501
+ "Explicit --config has no effective check rules. Configure checks.select, checks.run, checks.tool, checks.llm, checks.structure, or checks.safety."
6502
+ );
6503
+ if (options.json) printJson(result);
6504
+ else printHuman(result, options);
6505
+ process.exitCode = exitCodeFor(result);
6506
+ return;
6507
+ }
6187
6508
  let effectiveOptions = options;
6188
6509
  const resolved = resolvePreset(options.preset, {
6189
6510
  hasToolRules: hasToolRulesConfigured(config, options)
@@ -6303,12 +6624,13 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
6303
6624
  }
6304
6625
  } catch (error) {
6305
6626
  if (phase === "config") {
6306
- const message = error instanceof Error ? error.message : String(error);
6307
- 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";
6308
- result = errorResult(
6309
- code,
6310
- message
6311
- );
6627
+ if (error instanceof CheckConfigError) {
6628
+ result = errorResult(error.code, error.message);
6629
+ } else {
6630
+ const message = error instanceof Error ? error.message : String(error);
6631
+ 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";
6632
+ result = errorResult(code, message);
6633
+ }
6312
6634
  } else {
6313
6635
  result = readErrorResult(error);
6314
6636
  }
@@ -7230,6 +7552,42 @@ function startViewerServer(options = {}) {
7230
7552
  });
7231
7553
  });
7232
7554
  }
7555
+ function openInDefaultBrowser(target) {
7556
+ const platform = process.platform;
7557
+ const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
7558
+ const args = platform === "win32" ? ["/c", "start", "", target] : [target];
7559
+ return new Promise((resolve) => {
7560
+ try {
7561
+ const child = spawn(command, args, {
7562
+ detached: true,
7563
+ stdio: "ignore",
7564
+ windowsHide: platform === "win32"
7565
+ });
7566
+ child.once("spawn", () => {
7567
+ try {
7568
+ child.unref();
7569
+ resolve({ ok: true });
7570
+ } catch (error) {
7571
+ resolve({
7572
+ ok: false,
7573
+ detail: error instanceof Error ? error.message : String(error)
7574
+ });
7575
+ }
7576
+ });
7577
+ child.once("error", (error) => {
7578
+ resolve({
7579
+ ok: false,
7580
+ detail: error instanceof Error ? error.message : String(error)
7581
+ });
7582
+ });
7583
+ } catch (error) {
7584
+ resolve({
7585
+ ok: false,
7586
+ detail: error instanceof Error ? error.message : String(error)
7587
+ });
7588
+ }
7589
+ });
7590
+ }
7233
7591
 
7234
7592
  // packages/cli/src/serve.ts
7235
7593
  function parsePort(value) {
@@ -7259,9 +7617,11 @@ async function serveCommand(options = {}) {
7259
7617
  if (options.open === true && host !== "127.0.0.1" && host !== "localhost") {
7260
7618
  console.warn("Skipping browser open for non-local host binding.");
7261
7619
  } else if (options.open === true) {
7262
- const openMod = await import('child_process');
7263
- openMod.exec(`open ${info.url}`, () => {
7264
- });
7620
+ const opened = await openInDefaultBrowser(info.url);
7621
+ if (!opened.ok) {
7622
+ console.warn("Viewer started, but the browser could not be opened automatically.");
7623
+ console.warn(`Open manually: ${info.url}`);
7624
+ }
7265
7625
  }
7266
7626
  await new Promise(() => {
7267
7627
  });
@@ -7420,9 +7780,11 @@ async function studioCommand(options = {}) {
7420
7780
  if (options.open === true && info.host !== "127.0.0.1" && info.host !== "localhost") {
7421
7781
  console.warn("Skipping browser open for non-local host binding.");
7422
7782
  } else if (options.open === true) {
7423
- const openMod = await import('child_process');
7424
- openMod.exec(`open ${info.url}`, () => {
7425
- });
7783
+ const opened = await openInDefaultBrowser(info.url);
7784
+ if (!opened.ok) {
7785
+ console.warn("Studio started, but the browser could not be opened automatically.");
7786
+ console.warn(`Open manually: ${info.url}`);
7787
+ }
7426
7788
  }
7427
7789
  await new Promise(() => {
7428
7790
  });
@@ -8181,7 +8543,7 @@ function asStringArray2(value, label) {
8181
8543
  }
8182
8544
  return value;
8183
8545
  }
8184
- function asConfig2(value) {
8546
+ function asConfig(value) {
8185
8547
  if (value === void 0 || value === null) return {};
8186
8548
  if (typeof value !== "object" || Array.isArray(value)) {
8187
8549
  throw new Error("Config must export an object.");
@@ -8202,10 +8564,10 @@ async function loadConfig2(configPath) {
8202
8564
  const absolute = path18.resolve(configPath);
8203
8565
  if (extension === ".json") {
8204
8566
  const raw = await readFile(absolute, "utf-8");
8205
- return asConfig2(JSON.parse(raw));
8567
+ return asConfig(JSON.parse(raw));
8206
8568
  }
8207
8569
  const mod = await import(pathToFileURL(absolute).href);
8208
- return asConfig2("default" in mod ? mod.default : mod);
8570
+ return asConfig("default" in mod ? mod.default : mod);
8209
8571
  }
8210
8572
  function normalizeConfig2(config) {
8211
8573
  if (config.eval === void 0) return {};
@@ -9710,33 +10072,6 @@ async function resolveHtmlPath(root) {
9710
10072
  `No evidence.html found under ${root}. Pass a bundle directory or .html path.`
9711
10073
  );
9712
10074
  }
9713
- function openLocalFile(filePath) {
9714
- const fileUrl = pathToFileURL(filePath).href;
9715
- const platform = process.platform;
9716
- const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
9717
- const args = platform === "darwin" ? [filePath] : platform === "win32" ? ["/c", "start", "", filePath] : [filePath];
9718
- return new Promise((resolve) => {
9719
- try {
9720
- const child = spawn(command, args, {
9721
- detached: true,
9722
- stdio: "ignore"
9723
- });
9724
- child.on("error", (error) => {
9725
- resolve({
9726
- ok: false,
9727
- detail: `${error instanceof Error ? error.message : String(error)}; open manually: ${fileUrl}`
9728
- });
9729
- });
9730
- child.unref();
9731
- resolve({ ok: true, detail: fileUrl });
9732
- } catch (error) {
9733
- resolve({
9734
- ok: false,
9735
- detail: `${error instanceof Error ? error.message : String(error)}; open manually: ${fileUrl}`
9736
- });
9737
- }
9738
- });
9739
- }
9740
10075
  async function bundleOpenCommand(targetPath, options = {}) {
9741
10076
  const root = path18.resolve(targetPath.trim() || ".");
9742
10077
  try {
@@ -9806,23 +10141,24 @@ async function bundleOpenCommand(targetPath, options = {}) {
9806
10141
  await readFile(path18.join(path18.dirname(htmlPath), "evidence.json"), "utf-8");
9807
10142
  } catch {
9808
10143
  }
9809
- const opened = await openLocalFile(htmlPath);
10144
+ const fileUrl = pathToFileURL(htmlPath).href;
10145
+ const opened = await openInDefaultBrowser(fileUrl);
9810
10146
  if (options.json) {
9811
10147
  console.log(
9812
10148
  writeJson6({
9813
10149
  ok: opened.ok,
9814
10150
  path: htmlPath,
9815
10151
  opened: opened.ok,
9816
- detail: opened.detail
10152
+ detail: opened.ok ? fileUrl : `${opened.detail ?? "unknown error"}; open manually: ${fileUrl}`
9817
10153
  }).trimEnd()
9818
10154
  );
9819
10155
  } else if (opened.ok) {
9820
10156
  console.log(`Opened local Evidence: ${htmlPath}`);
9821
- console.log(`URL: ${opened.detail}`);
10157
+ console.log(`URL: ${fileUrl}`);
9822
10158
  } else {
9823
10159
  console.error(`[AgentInspect] Could not open browser automatically.`);
9824
10160
  console.error(`Open this file locally: ${htmlPath}`);
9825
- console.error(opened.detail);
10161
+ console.error(`${opened.detail ?? "unknown error"}; open manually: ${fileUrl}`);
9826
10162
  }
9827
10163
  if (!opened.ok) {
9828
10164
  process.exitCode = 0;
@@ -11255,7 +11591,7 @@ function isModuleNotFound4(e) {
11255
11591
  }
11256
11592
  async function loadIndexSqlite() {
11257
11593
  try {
11258
- return await import('./src-IDMCWKRH.mjs');
11594
+ return await import('./src-OYGG7QAZ.mjs');
11259
11595
  } catch (e) {
11260
11596
  if (isModuleNotFound4(e)) {
11261
11597
  console.error(
@@ -11752,9 +12088,15 @@ function normalizeMetrics(raw) {
11752
12088
  const parsed = parseCohortMetricList(raw);
11753
12089
  if (parsed.length === 0) return void 0;
11754
12090
  const allowed = new Set(COHORT_METRIC_IDS);
11755
- return parsed.filter(
11756
- (item) => allowed.has(item)
11757
- );
12091
+ const invalid = parsed.filter((metric) => !allowed.has(metric));
12092
+ if (invalid.length > 0) {
12093
+ const valueLabel = invalid.length === 1 ? "value" : "values";
12094
+ const invalidList = invalid.map((metric) => `"${metric}"`).join(", ");
12095
+ throw new Error(
12096
+ `Unsupported cohort --metric ${valueLabel}: ${invalidList}. Valid metrics: ${COHORT_METRIC_IDS.join(", ")}.`
12097
+ );
12098
+ }
12099
+ return parsed;
11758
12100
  }
11759
12101
  async function writeArtifacts(result, outputDir) {
11760
12102
  await mkdir(outputDir, { recursive: true });
@@ -11775,6 +12117,7 @@ async function writeArtifacts(result, outputDir) {
11775
12117
  }
11776
12118
  async function cohortCommand(options = {}) {
11777
12119
  try {
12120
+ const metrics = normalizeMetrics(options.metric);
11778
12121
  const traceDir = resolveTraceDir({ dir: options.dir });
11779
12122
  const { runs } = await loadSessionRuns(traceDir);
11780
12123
  const result = await analyzeCohort(runs, {
@@ -11783,7 +12126,7 @@ async function cohortCommand(options = {}) {
11783
12126
  candidate: options.candidate,
11784
12127
  cohortKey: options.cohortKey,
11785
12128
  groupBy: options.groupBy,
11786
- metrics: normalizeMetrics(options.metric)
12129
+ metrics
11787
12130
  });
11788
12131
  const format = options.format ?? (options.json ? "json" : "markdown");
11789
12132
  let artifacts;