@agent-inspect/mcp-server 6.31.9 → 6.31.11

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.
package/dist/cli.cjs CHANGED
@@ -1692,7 +1692,7 @@ function stepMixLine(summary) {
1692
1692
  if (summary.logicSteps > 0) parts.push(`${summary.logicSteps} logic`);
1693
1693
  return parts.length > 0 ? parts.join(", ") : "none";
1694
1694
  }
1695
- function outcomeLine(summary) {
1695
+ function executionLine(summary) {
1696
1696
  if (summary.status === "success") {
1697
1697
  return summary.errorSteps > 0 ? "Completed with step errors recorded." : "Completed successfully.";
1698
1698
  }
@@ -1710,7 +1710,7 @@ function outcomeLine(summary) {
1710
1710
  if (summary.status === "running") {
1711
1711
  return "Run is still in progress (no run_completed).";
1712
1712
  }
1713
- return "Outcome unknown \u2014 inspect events may be incomplete.";
1713
+ return "Execution status unknown \u2014 inspect events may be incomplete.";
1714
1714
  }
1715
1715
  function buildRunWhatSummary(events) {
1716
1716
  const base = buildRunSummary(events);
@@ -1801,7 +1801,7 @@ function renderRunWhat(summary, options = {}) {
1801
1801
  lines.push(`Correlation: ${parts.join(", ")}`);
1802
1802
  }
1803
1803
  }
1804
- lines.push(`Outcome: ${outcomeLine(summary)}`);
1804
+ lines.push(`Execution: ${executionLine(summary)}`);
1805
1805
  if (summary.longestStep && summary.totalSteps > 0) {
1806
1806
  lines.push(
1807
1807
  `Slowest: ${summary.longestStep.name} (${formatDuration2(summary.longestStep.durationMs)}, ${summary.longestStep.type})`
@@ -1955,6 +1955,369 @@ function findFirstCausalFailure(events, options = {}) {
1955
1955
  );
1956
1956
  }
1957
1957
 
1958
+ // packages/core/src/diagnostics/programmatic.ts
1959
+ var PROGRAMMATIC_DIAGNOSTIC_SPECS = Object.freeze({
1960
+ AI_TRACE_INPUT_INVALID: {
1961
+ code: "AI_TRACE_INPUT_INVALID",
1962
+ summary: 'Expected { type: "file", path }, { type: "directory", path }, { type: "string", content }, { type: "buffer", content }, or { type: "stdin" }.',
1963
+ remediation: "For a file path, use openTraceFile(path).",
1964
+ relatedCodes: ["invalid_input"]
1965
+ },
1966
+ AI_TRACE_FORMAT_UNSUPPORTED: {
1967
+ code: "AI_TRACE_FORMAT_UNSUPPORTED",
1968
+ summary: "No trace reader could detect the input format.",
1969
+ remediation: "Pass an AgentInspect JSONL file via openTraceFile, or set options.format to a registered reader.",
1970
+ relatedCodes: ["unsupported_format"]
1971
+ },
1972
+ AI_TRACE_FORMAT_AMBIGUOUS: {
1973
+ code: "AI_TRACE_FORMAT_AMBIGUOUS",
1974
+ summary: "Multiple trace readers matched the input with equal confidence.",
1975
+ remediation: "Set options.format explicitly to disambiguate the reader.",
1976
+ relatedCodes: ["ambiguous_format"]
1977
+ },
1978
+ AI_TRACE_FACTS_INPUT_NOT_NORMALIZED: {
1979
+ code: "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED",
1980
+ summary: "TraceFacts requires TraceReadResult or PersistedInspectEvent[].",
1981
+ remediation: "Use openTraceFile() to normalize a JSONL trace first."
1982
+ },
1983
+ AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED: {
1984
+ code: "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
1985
+ summary: "Multiple runs are available; select a run before executing checks.",
1986
+ remediation: "Pass options.runId or TraceCheckInput.selectedRun.",
1987
+ relatedCodes: ["AI_CHECK_RUN_SELECTION_REQUIRED"]
1988
+ },
1989
+ AI_TRACE_RELATIONSHIP_SELF_PARENT: {
1990
+ code: "AI_TRACE_RELATIONSHIP_SELF_PARENT",
1991
+ summary: "A parentId equals its own event/step id.",
1992
+ remediation: "Reject at capture (AI_LANGGRAPH_SELF_PARENT_REJECTED) or drop via logical projection; do not invent replacement parents.",
1993
+ relatedCodes: [
1994
+ "AI_LANGGRAPH_SELF_PARENT_REJECTED",
1995
+ "AI_LOGICAL_SELF_PARENT_REMOVED"
1996
+ ]
1997
+ },
1998
+ AI_TRACE_RELATIONSHIP_CYCLE: {
1999
+ code: "AI_TRACE_RELATIONSHIP_CYCLE",
2000
+ summary: "Trace contains a parentId cycle.",
2001
+ remediation: "Use visibility-first tree linking for legacy fixtures; prefer acyclic capture for new adapter output.",
2002
+ relatedCodes: ["structure.cycle"]
2003
+ },
2004
+ AI_TRACE_CONTRACT_INPUT_INVALID: {
2005
+ code: "AI_TRACE_CONTRACT_INPUT_INVALID",
2006
+ summary: "evaluateTraceContract expects ({ read }, contract) or use evaluateTraceContractRead(read, contract).",
2007
+ remediation: "Pass TraceCheckInput as the first argument: evaluateTraceContract({ read }, contract).",
2008
+ relatedCodes: ["AI_CHECK_INVALID_ARGUMENTS"]
2009
+ },
2010
+ AI_INSPECT_RUN_UNSUPPORTED_OPTION: {
2011
+ code: "AI_INSPECT_RUN_UNSUPPORTED_OPTION",
2012
+ summary: 'inspectRun does not accept a "writer" option.',
2013
+ remediation: "Use createInspector({ writer }) then inspector.run(...). inspectRun persists via the global JSONL path (schema 0.1)."
2014
+ }
2015
+ });
2016
+ function formatProgrammaticDiagnostic(code, detail) {
2017
+ const spec = PROGRAMMATIC_DIAGNOSTIC_SPECS[code];
2018
+ const summary = detail?.trim() ? detail.trim() : spec.summary;
2019
+ return `${code}: ${summary} Remediation: ${spec.remediation}`;
2020
+ }
2021
+
2022
+ // packages/core/src/checks/logical-events.ts
2023
+ function isRecord6(value) {
2024
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2025
+ }
2026
+ function legacyEvent(event) {
2027
+ const value = event.attributes?.legacyEvent;
2028
+ return typeof value === "string" ? value : void 0;
2029
+ }
2030
+ function stepIdOf(event) {
2031
+ const value = event.attributes?.stepId;
2032
+ if (typeof value === "string" && value.trim() !== "") return value;
2033
+ return void 0;
2034
+ }
2035
+ function cloneEvent(event) {
2036
+ return {
2037
+ ...event,
2038
+ ...event.attributes !== void 0 ? { attributes: { ...event.attributes } } : {},
2039
+ ...event.error !== void 0 ? { error: { ...event.error } } : {},
2040
+ ...event.tokenUsage !== void 0 ? { tokenUsage: { ...event.tokenUsage } } : {},
2041
+ ...event.source !== void 0 ? { source: { ...event.source } } : {}
2042
+ };
2043
+ }
2044
+ function mergeAttributes(start, complete) {
2045
+ const merged = {
2046
+ ...isRecord6(complete.attributes) ? complete.attributes : {},
2047
+ ...isRecord6(start.attributes) ? start.attributes : {}
2048
+ };
2049
+ merged.legacyEvent = start.attributes?.legacyEvent ?? complete.attributes?.legacyEvent;
2050
+ merged.legacyCompleteEvent = complete.attributes?.legacyEvent;
2051
+ if (complete.attributes?.errorStack !== void 0) {
2052
+ merged.errorStack = complete.attributes.errorStack;
2053
+ }
2054
+ return Object.keys(merged).length > 0 ? merged : void 0;
2055
+ }
2056
+ function pairStartComplete(start, complete) {
2057
+ const attributes = mergeAttributes(start, complete);
2058
+ const paired = {
2059
+ ...cloneEvent(start),
2060
+ status: complete.status,
2061
+ timestamp: complete.timestamp ?? start.timestamp,
2062
+ ...complete.endedAt !== void 0 ? { endedAt: complete.endedAt } : {},
2063
+ ...complete.durationMs !== void 0 ? { durationMs: complete.durationMs } : {},
2064
+ ...complete.error !== void 0 ? { error: { ...complete.error } } : {},
2065
+ ...complete.tokenUsage !== void 0 && start.tokenUsage === void 0 ? { tokenUsage: { ...complete.tokenUsage } } : {},
2066
+ ...attributes !== void 0 ? { attributes } : {}
2067
+ };
2068
+ return {
2069
+ ...paired,
2070
+ sourceEventIds: Object.freeze([start.eventId, complete.eventId]),
2071
+ projection: {
2072
+ paired: true,
2073
+ absorbedEventIds: Object.freeze([complete.eventId]),
2074
+ parentNormalized: false,
2075
+ ...start.parentId !== void 0 ? { originalParentId: start.parentId } : {}
2076
+ }
2077
+ };
2078
+ }
2079
+ function asLogical(event, extras) {
2080
+ return {
2081
+ ...cloneEvent(event),
2082
+ sourceEventIds: Object.freeze([event.eventId]),
2083
+ projection: {
2084
+ paired: false,
2085
+ absorbedEventIds: Object.freeze([]),
2086
+ parentNormalized: extras?.parentNormalized === true,
2087
+ ...{}
2088
+ }
2089
+ };
2090
+ }
2091
+ function projectLogicalEvents(events) {
2092
+ const diagnostics = [];
2093
+ const byRun = /* @__PURE__ */ new Map();
2094
+ for (const event of events) {
2095
+ const list = byRun.get(event.runId) ?? [];
2096
+ list.push(event);
2097
+ byRun.set(event.runId, list);
2098
+ }
2099
+ const absorbedIds = /* @__PURE__ */ new Set();
2100
+ const logicalByRawId = /* @__PURE__ */ new Map();
2101
+ const stepIdToLogicalId = /* @__PURE__ */ new Map();
2102
+ const logical = [];
2103
+ const orderedRuns = [...byRun.keys()].sort((a, b) => a.localeCompare(b));
2104
+ for (const runId of orderedRuns) {
2105
+ const runEvents = byRun.get(runId) ?? [];
2106
+ const starts = [];
2107
+ const completes = [];
2108
+ const others = [];
2109
+ for (const event of runEvents) {
2110
+ const legacy = legacyEvent(event);
2111
+ if (legacy === "step_started" || legacy === "run_started" && event.status === "running") {
2112
+ starts.push(event);
2113
+ } else if (legacy === "step_completed" || legacy === "run_completed") {
2114
+ completes.push(event);
2115
+ } else {
2116
+ others.push(event);
2117
+ }
2118
+ }
2119
+ const usedCompletes = /* @__PURE__ */ new Set();
2120
+ for (const start of starts) {
2121
+ const stepId = stepIdOf(start);
2122
+ let match;
2123
+ if (legacyEvent(start) === "run_started") {
2124
+ const candidates = completes.filter(
2125
+ (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "run_completed"
2126
+ );
2127
+ if (candidates.length > 1) {
2128
+ diagnostics.push({
2129
+ code: "AI_LOGICAL_PAIR_AMBIGUOUS",
2130
+ message: `Multiple run_completed rows for run ${runId}; using first by eventId.`,
2131
+ eventIds: candidates.map((c) => c.eventId)
2132
+ });
2133
+ candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
2134
+ }
2135
+ match = candidates[0];
2136
+ } else if (stepId) {
2137
+ const candidates = completes.filter(
2138
+ (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "step_completed" && stepIdOf(c) === stepId
2139
+ );
2140
+ if (candidates.length > 1) {
2141
+ diagnostics.push({
2142
+ code: "AI_LOGICAL_PAIR_AMBIGUOUS",
2143
+ message: `Multiple step_completed rows for stepId ${stepId}; using first by eventId.`,
2144
+ eventIds: candidates.map((c) => c.eventId)
2145
+ });
2146
+ candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
2147
+ }
2148
+ match = candidates[0];
2149
+ }
2150
+ if (match) {
2151
+ usedCompletes.add(match.eventId);
2152
+ absorbedIds.add(match.eventId);
2153
+ const paired = pairStartComplete(start, match);
2154
+ logical.push(paired);
2155
+ logicalByRawId.set(start.eventId, paired);
2156
+ logicalByRawId.set(match.eventId, paired);
2157
+ if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, paired.eventId);
2158
+ } else {
2159
+ diagnostics.push({
2160
+ code: "AI_LOGICAL_PAIR_UNMATCHED_START",
2161
+ message: `No matching complete for start ${start.eventId}.`,
2162
+ eventIds: [start.eventId]
2163
+ });
2164
+ const alone = asLogical(start);
2165
+ logical.push(alone);
2166
+ logicalByRawId.set(start.eventId, alone);
2167
+ if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2168
+ }
2169
+ }
2170
+ for (const complete of completes) {
2171
+ if (usedCompletes.has(complete.eventId)) continue;
2172
+ diagnostics.push({
2173
+ code: "AI_LOGICAL_PAIR_UNMATCHED_COMPLETE",
2174
+ message: `No matching start for complete ${complete.eventId}.`,
2175
+ eventIds: [complete.eventId]
2176
+ });
2177
+ const alone = asLogical(complete);
2178
+ logical.push(alone);
2179
+ logicalByRawId.set(complete.eventId, alone);
2180
+ const stepId = stepIdOf(complete);
2181
+ if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
2182
+ stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2183
+ }
2184
+ }
2185
+ for (const event of others) {
2186
+ const alone = asLogical(event);
2187
+ logical.push(alone);
2188
+ logicalByRawId.set(event.eventId, alone);
2189
+ const stepId = stepIdOf(event);
2190
+ if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
2191
+ stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2192
+ }
2193
+ }
2194
+ }
2195
+ const logicalById = new Map(logical.map((e) => [e.eventId, e]));
2196
+ const normalized = [];
2197
+ for (const event of logical) {
2198
+ const originalParentId = event.parentId;
2199
+ if (!originalParentId) {
2200
+ normalized.push(event);
2201
+ continue;
2202
+ }
2203
+ let nextParent = originalParentId;
2204
+ let remapped = false;
2205
+ const viaAbsorbed = logicalByRawId.get(originalParentId);
2206
+ if (viaAbsorbed && viaAbsorbed.eventId !== originalParentId) {
2207
+ nextParent = viaAbsorbed.eventId;
2208
+ remapped = true;
2209
+ } else if (!logicalById.has(originalParentId)) {
2210
+ const viaStep = stepIdToLogicalId.get(`${event.runId}:${originalParentId}`);
2211
+ if (viaStep) {
2212
+ nextParent = viaStep;
2213
+ remapped = true;
2214
+ }
2215
+ }
2216
+ if (!remapped) {
2217
+ if (originalParentId === event.eventId) {
2218
+ diagnostics.push({
2219
+ code: "AI_LOGICAL_SELF_PARENT_REMOVED",
2220
+ message: formatProgrammaticDiagnostic(
2221
+ "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2222
+ `Removed self-parent edge on ${event.eventId}.`
2223
+ ),
2224
+ eventIds: [event.eventId]
2225
+ });
2226
+ const { parentId: _drop, ...rest } = event;
2227
+ normalized.push({
2228
+ ...rest,
2229
+ projection: {
2230
+ ...event.projection,
2231
+ parentNormalized: true,
2232
+ originalParentId
2233
+ }
2234
+ });
2235
+ continue;
2236
+ }
2237
+ if (!logicalById.has(originalParentId) && !logicalByRawId.has(originalParentId)) {
2238
+ const mapping = event.attributes?.parentMapping;
2239
+ const unresolved = mapping === "unresolved" || event.attributes?.parentUnresolved === true || event.attributes?.unresolvedParent === true || // LangGraph/framework scaffolding sentinel labels are not event ids.
2240
+ /^LangGraph$/i.test(originalParentId) || originalParentId.startsWith("unresolved:");
2241
+ if (!unresolved) {
2242
+ diagnostics.push({
2243
+ code: "AI_LOGICAL_PARENT_UNRESOLVED",
2244
+ message: `Parent ${originalParentId} unresolved for ${event.eventId}.`,
2245
+ eventIds: [event.eventId]
2246
+ });
2247
+ }
2248
+ }
2249
+ normalized.push(event);
2250
+ continue;
2251
+ }
2252
+ if (nextParent === event.eventId) {
2253
+ diagnostics.push({
2254
+ code: "AI_LOGICAL_SELF_PARENT_REMOVED",
2255
+ message: formatProgrammaticDiagnostic(
2256
+ "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2257
+ `Removed self-parent edge on ${event.eventId} (was ${originalParentId}).`
2258
+ ),
2259
+ eventIds: [event.eventId]
2260
+ });
2261
+ const { parentId: _drop, ...rest } = event;
2262
+ normalized.push({
2263
+ ...rest,
2264
+ projection: {
2265
+ ...event.projection,
2266
+ parentNormalized: true,
2267
+ originalParentId
2268
+ }
2269
+ });
2270
+ continue;
2271
+ }
2272
+ diagnostics.push({
2273
+ code: "AI_LOGICAL_PARENT_REMAPPED",
2274
+ message: `Remapped parent ${originalParentId} \u2192 ${nextParent} for ${event.eventId}.`,
2275
+ eventIds: [event.eventId]
2276
+ });
2277
+ normalized.push({
2278
+ ...event,
2279
+ parentId: nextParent,
2280
+ projection: {
2281
+ ...event.projection,
2282
+ parentNormalized: true,
2283
+ originalParentId
2284
+ }
2285
+ });
2286
+ }
2287
+ const rawIndex = new Map(events.map((e, i) => [e.eventId, i]));
2288
+ normalized.sort((a, b) => {
2289
+ const ai = rawIndex.get(a.sourceEventIds[0]) ?? 0;
2290
+ const bi = rawIndex.get(b.sourceEventIds[0]) ?? 0;
2291
+ return ai - bi || a.eventId.localeCompare(b.eventId);
2292
+ });
2293
+ return {
2294
+ logicalEvents: Object.freeze(normalized),
2295
+ diagnostics: Object.freeze(diagnostics)
2296
+ };
2297
+ }
2298
+ function resolveCanonicalToolName(event) {
2299
+ const attrs2 = event.attributes;
2300
+ const direct = pickString(attrs2, ["toolName", "tool"]);
2301
+ if (direct) return direct;
2302
+ const metadata = attrs2?.metadata;
2303
+ if (isRecord6(metadata)) {
2304
+ const nested = pickString(metadata, ["toolName", "tool"]);
2305
+ if (nested) return nested;
2306
+ }
2307
+ for (const prefix of ["tool:", "function:", "mcp-tools:"]) {
2308
+ if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
2309
+ }
2310
+ return event.name;
2311
+ }
2312
+ function pickString(record, keys) {
2313
+ if (!record) return void 0;
2314
+ for (const key of keys) {
2315
+ const value = record[key];
2316
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
2317
+ }
2318
+ return void 0;
2319
+ }
2320
+
1958
2321
  // packages/core/src/search.ts
1959
2322
  function parseDurationFilter(expr) {
1960
2323
  const raw = expr.trim();
@@ -2054,708 +2417,357 @@ async function searchTraces(metas, options) {
2054
2417
  statusFilter: options.status
2055
2418
  });
2056
2419
  results.push(...stepMatches);
2057
- if (observationStatus) {
2058
- const outcomes = extractOutcomesFromTraceEvents(events);
2059
- const matched = outcomes.filter((outcome) => outcome.status === observationStatus);
2060
- for (const outcome of matched) {
2061
- results.push({
2062
- runId: m.runId,
2063
- runName: m.name,
2064
- runStatus: m.status,
2065
- stepName: outcome.name,
2066
- timestamp: outcome.observedAt,
2067
- matchReason: `observation status=${outcome.status}`,
2068
- matchedFields: ["outcome.status", "outcome.name"],
2069
- filePath: m.filePath
2070
- });
2071
- }
2072
- }
2073
- }
2074
- results.sort((a, b) => {
2075
- const ta = a.timestamp ?? 0;
2076
- const tb = b.timestamp ?? 0;
2077
- if (ta !== tb) return tb - ta;
2078
- const runCmp = a.runId.localeCompare(b.runId);
2079
- if (runCmp !== 0) return runCmp;
2080
- return (a.stepName ?? "").localeCompare(b.stepName ?? "");
2081
- });
2082
- return results.slice(0, limit);
2083
- }
2084
- function matchRunLevel(m, opts) {
2085
- if (opts.stepTypeFilter || opts.toolQuery) return [];
2086
- if (opts.statusFilter && m.status !== opts.statusFilter) return [];
2087
- if (opts.nameQuery && !nameMatches(m.name ?? m.runId, opts.nameQuery)) {
2088
- return [];
2089
- }
2090
- if (opts.durationFilter && !durationMatches(m.durationMs, opts.durationFilter)) {
2091
- return [];
2092
- }
2093
- const fields = [];
2094
- if (opts.statusFilter) fields.push("run.status");
2095
- if (opts.nameQuery) fields.push("run.name");
2096
- if (opts.durationFilter) fields.push("run.durationMs");
2097
- if (fields.length === 0) return [];
2098
- return [
2099
- {
2100
- runId: m.runId,
2101
- runName: m.name,
2102
- runStatus: m.status,
2103
- timestamp: m.startedAt,
2104
- durationMs: m.durationMs,
2105
- matchReason: `run match: ${fields.join(", ")}`,
2106
- matchedFields: fields,
2107
- filePath: m.filePath
2108
- }
2109
- ];
2110
- }
2111
- function matchStepLevel(m, events, opts) {
2112
- const out = [];
2113
- const started = /* @__PURE__ */ new Map();
2114
- for (const e of events) {
2115
- if (e.event === "step_started") {
2116
- started.set(e.stepId, e);
2117
- }
2118
- }
2119
- for (const e of events) {
2120
- if (e.event !== "step_completed") continue;
2121
- const c = e;
2122
- const s = started.get(c.stepId);
2123
- if (!s) continue;
2124
- const fields = [];
2125
- const stepType = s.type;
2126
- if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
2127
- continue;
2128
- }
2129
- const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
2130
- if (!hasStepFilters) continue;
2131
- if (opts.statusFilter === "error" && c.status === "error") {
2132
- fields.push("step.status");
2133
- } else if (opts.statusFilter === "success" && c.status === "success") {
2134
- fields.push("step.status");
2135
- } else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
2136
- continue;
2137
- }
2138
- if (opts.nameQuery) {
2139
- if (!nameMatches(s.name, opts.nameQuery)) continue;
2140
- fields.push("step.name");
2141
- }
2142
- if (opts.toolQuery) {
2143
- const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
2144
- if (!nameMatches(toolName, opts.toolQuery)) continue;
2145
- fields.push("step.tool");
2146
- }
2147
- if (opts.durationFilter) {
2148
- if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
2149
- fields.push("step.durationMs");
2150
- }
2151
- if (opts.stepTypeFilter) {
2152
- fields.push("step.type");
2153
- }
2154
- if (fields.length === 0) continue;
2155
- out.push({
2156
- runId: m.runId,
2157
- runName: m.name,
2158
- runStatus: m.status,
2159
- stepId: c.stepId,
2160
- stepName: s.name,
2161
- stepType,
2162
- timestamp: s.startTime ?? s.timestamp,
2163
- durationMs: c.durationMs,
2164
- matchReason: `step match: ${fields.join(", ")}`,
2165
- matchedFields: fields,
2166
- filePath: m.filePath
2167
- });
2168
- }
2169
- return out;
2170
- }
2171
- async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
2172
- const metas = [];
2173
- for (const fileName of fileNames) {
2174
- try {
2175
- const filePath = getPath(fileName);
2176
- const meta = await extractMetadata(filePath);
2177
- metas.push(meta);
2178
- } catch {
2179
- }
2180
- }
2181
- return metas;
2182
- }
2183
-
2184
- // packages/core/src/sessions/metadata.ts
2185
- function isNonEmptyString3(value) {
2186
- return typeof value === "string" && value.trim() !== "";
2187
- }
2188
- function finitePositiveInt(value) {
2189
- if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
2190
- return void 0;
2191
- }
2192
- return Math.trunc(value);
2193
- }
2194
- function extractSessionWorkflowMetadata(record) {
2195
- if (!record) return void 0;
2196
- const out = {};
2197
- let found = false;
2198
- const assignString = (key, value) => {
2199
- if (isNonEmptyString3(value)) {
2200
- out[key] = value.trim();
2201
- found = true;
2202
- }
2203
- };
2204
- assignString("sessionId", record.sessionId);
2205
- assignString("conversationId", record.conversationId);
2206
- assignString("groupId", record.groupId);
2207
- assignString("parentGroupId", record.parentGroupId);
2208
- assignString("retryOf", record.retryOf);
2209
- assignString("retryReason", record.retryReason);
2210
- assignString("handoffFrom", record.handoffFrom);
2211
- assignString("handoffTo", record.handoffTo);
2212
- assignString("subAgentId", record.subAgentId);
2213
- assignString("subAgentName", record.subAgentName);
2214
- assignString("jobId", record.jobId);
2215
- assignString("queueName", record.queueName);
2216
- assignString("workflowName", record.workflowName);
2217
- assignString("workflowStep", record.workflowStep);
2218
- assignString("toolCallId", record.toolCallId);
2219
- assignString("mcpToolCallId", record.mcpToolCallId);
2220
- assignString("linkedStepId", record.linkedStepId);
2221
- assignString("operationId", record.operationId);
2222
- assignString("attemptId", record.attemptId);
2223
- assignString("fallbackOf", record.fallbackOf);
2224
- assignString("idempotencyKey", record.idempotencyKey);
2225
- assignString("correlationId", record.correlationId);
2226
- assignString("requestId", record.requestId);
2227
- assignString("decisionId", record.decisionId);
2228
- const attempt = finitePositiveInt(record.attempt);
2229
- if (attempt !== void 0) {
2230
- out.attempt = attempt;
2231
- found = true;
2232
- }
2233
- const attemptNumber = finitePositiveInt(record.attemptNumber);
2234
- if (attemptNumber !== void 0) {
2235
- out.attemptNumber = attemptNumber;
2236
- found = true;
2237
- }
2238
- return found ? out : void 0;
2239
- }
2240
-
2241
- // packages/core/src/bundle/safety-status.ts
2242
- function aggregateBundleSafeStatus(statuses) {
2243
- if (statuses.length === 0) return "UNKNOWN";
2244
- if (statuses.some((status) => status === "UNSAFE")) return "UNSAFE";
2245
- if (statuses.some((status) => status === "UNKNOWN")) return "UNKNOWN";
2246
- if (statuses.some((status) => status === "SAFE WITH WARNINGS")) return "SAFE WITH WARNINGS";
2247
- return "SAFE";
2248
- }
2249
- function toMetadataSafeStatus(status) {
2250
- if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
2251
- return status;
2252
- }
2253
- function bundleFailsOnSafety(status, allowUnsafe) {
2254
- return status === "UNSAFE" || status === "UNKNOWN";
2255
- }
2256
-
2257
- // packages/core/src/bundle/manifest.ts
2258
- var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
2259
- function buildBundleMetadata(parts) {
2260
- const aggregate = aggregateBundleSafeStatus(
2261
- parts.checks.runs.map((run) => run.status)
2262
- );
2263
- return {
2264
- createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2265
- agentInspectVersion: parts.agentInspectVersion,
2266
- redactionProfile: parts.profile,
2267
- sourceTraceCount: parts.resolve.runIds.length,
2268
- runIds: [...parts.resolve.runIds],
2269
- safeStatus: toMetadataSafeStatus(aggregate),
2270
- files: [...parts.files].sort((a, b) => a.localeCompare(b)),
2271
- note: BUNDLE_NOTE,
2272
- ...parts.resolve.sessionId !== void 0 ? { sessionId: parts.resolve.sessionId } : {},
2273
- ...parts.resolve.since !== void 0 ? { since: parts.resolve.since } : {}
2274
- };
2275
- }
2276
-
2277
- // packages/core/src/evidence/types.ts
2278
- var EVIDENCE_FORMAT_VERSION = "1.0";
2279
- var EVIDENCE_ASSESSMENT_NOTE = "Best-effort local safety verification only; not a compliance certification.";
2280
- var EVIDENCE_MANIFEST_FILENAME = "evidence.json";
2281
- function sha256Hex(data) {
2282
- const hash = crypto.createHash("sha256");
2283
- if (typeof data === "string") {
2284
- hash.update(data, "utf8");
2285
- } else {
2286
- hash.update(data);
2420
+ if (observationStatus) {
2421
+ const outcomes = extractOutcomesFromTraceEvents(events);
2422
+ const matched = outcomes.filter((outcome) => outcome.status === observationStatus);
2423
+ for (const outcome of matched) {
2424
+ results.push({
2425
+ runId: m.runId,
2426
+ runName: m.name,
2427
+ runStatus: m.status,
2428
+ stepName: outcome.name,
2429
+ timestamp: outcome.observedAt,
2430
+ matchReason: `observation status=${outcome.status}`,
2431
+ matchedFields: ["outcome.status", "outcome.name"],
2432
+ filePath: m.filePath
2433
+ });
2434
+ }
2435
+ }
2287
2436
  }
2288
- return hash.digest("hex");
2437
+ results.sort((a, b) => {
2438
+ const ta = a.timestamp ?? 0;
2439
+ const tb = b.timestamp ?? 0;
2440
+ if (ta !== tb) return tb - ta;
2441
+ const runCmp = a.runId.localeCompare(b.runId);
2442
+ if (runCmp !== 0) return runCmp;
2443
+ return (a.stepName ?? "").localeCompare(b.stepName ?? "");
2444
+ });
2445
+ return results.slice(0, limit);
2289
2446
  }
2290
- function assertEvidenceRelativePath(relativePath) {
2291
- if (typeof relativePath !== "string" || relativePath.trim() === "") {
2292
- throw new Error("Evidence file path must be a non-empty relative path.");
2293
- }
2294
- const trimmed = relativePath.trim().replaceAll("\\", "/");
2295
- if (path14__default.default.isAbsolute(trimmed) || trimmed.startsWith("/")) {
2296
- throw new Error(`Evidence file path must be relative: ${relativePath}`);
2447
+ function matchRunLevel(m, opts) {
2448
+ if (opts.stepTypeFilter || opts.toolQuery) return [];
2449
+ if (opts.statusFilter && m.status !== opts.statusFilter) return [];
2450
+ if (opts.nameQuery && !nameMatches(m.name ?? m.runId, opts.nameQuery)) {
2451
+ return [];
2297
2452
  }
2298
- const parts = trimmed.split("/").filter((part) => part !== "");
2299
- if (parts.length === 0) {
2300
- throw new Error(`Evidence file path must be relative: ${relativePath}`);
2453
+ if (opts.durationFilter && !durationMatches(m.durationMs, opts.durationFilter)) {
2454
+ return [];
2301
2455
  }
2302
- for (const part of parts) {
2303
- if (part === "." || part === "..") {
2304
- throw new Error(`Evidence file path must not contain "." or "..": ${relativePath}`);
2456
+ const fields = [];
2457
+ if (opts.statusFilter) fields.push("run.status");
2458
+ if (opts.nameQuery) fields.push("run.name");
2459
+ if (opts.durationFilter) fields.push("run.durationMs");
2460
+ if (fields.length === 0) return [];
2461
+ return [
2462
+ {
2463
+ runId: m.runId,
2464
+ runName: m.name,
2465
+ runStatus: m.status,
2466
+ timestamp: m.startedAt,
2467
+ durationMs: m.durationMs,
2468
+ matchReason: `run match: ${fields.join(", ")}`,
2469
+ matchedFields: fields,
2470
+ filePath: m.filePath
2305
2471
  }
2306
- }
2307
- return parts.join("/");
2308
- }
2309
-
2310
- // packages/core/src/evidence/manifest.ts
2311
- function stable(value) {
2312
- if (Array.isArray(value)) return value.map(stable);
2313
- if (value === null || typeof value !== "object") return value;
2314
- const record = value;
2315
- return Object.fromEntries(
2316
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
2317
- );
2318
- }
2319
- function serializeEvidenceManifest(manifest) {
2320
- return `${JSON.stringify(stable(manifest), null, 2)}
2321
- `;
2472
+ ];
2322
2473
  }
2323
- function inferEvidenceFileRole(relativePath) {
2324
- const normalized = assertEvidenceRelativePath(relativePath);
2325
- const base = normalized.includes("/") ? normalized.slice(normalized.lastIndexOf("/") + 1) : normalized;
2326
- if (base === "evidence.html" || base === "trace.html" || base.endsWith(".html")) {
2327
- return "report";
2328
- }
2329
- if (base === "trace.jsonl" || base.endsWith(".jsonl")) {
2330
- return "redacted-trace";
2331
- }
2332
- if (base === "check-results.json") {
2333
- return "checks";
2334
- }
2335
- if (base === "contract.resolved.json") {
2336
- return "contract";
2337
- }
2338
- if (base === "redaction-report.json") {
2339
- return "redaction-report";
2340
- }
2341
- if (base === "summary.md") {
2342
- return "summary";
2474
+ function matchStepLevel(m, events, opts) {
2475
+ const out = [];
2476
+ const started = /* @__PURE__ */ new Map();
2477
+ for (const e of events) {
2478
+ if (e.event === "step_started") {
2479
+ started.set(e.stepId, e);
2480
+ }
2343
2481
  }
2344
- return "other";
2345
- }
2346
- function buildEvidenceFileEntries(files) {
2347
- const byPath = /* @__PURE__ */ new Map();
2348
- for (const file of files) {
2349
- const relativePath = assertEvidenceRelativePath(file.path);
2350
- if (relativePath === EVIDENCE_MANIFEST_FILENAME) {
2351
- throw new Error(
2352
- `Do not include ${EVIDENCE_MANIFEST_FILENAME} in packaged file hashes (self-hash is undefined).`
2353
- );
2482
+ for (const e of events) {
2483
+ if (e.event !== "step_completed") continue;
2484
+ const c = e;
2485
+ const s = started.get(c.stepId);
2486
+ if (!s) continue;
2487
+ const fields = [];
2488
+ const stepType = s.type;
2489
+ if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
2490
+ continue;
2354
2491
  }
2355
- if (byPath.has(relativePath)) {
2356
- throw new Error(`Duplicate evidence file path: ${relativePath}`);
2492
+ const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
2493
+ if (!hasStepFilters) continue;
2494
+ if (opts.statusFilter === "error" && c.status === "error") {
2495
+ fields.push("step.status");
2496
+ } else if (opts.statusFilter === "success" && c.status === "success") {
2497
+ fields.push("step.status");
2498
+ } else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
2499
+ continue;
2357
2500
  }
2358
- byPath.set(relativePath, {
2359
- path: relativePath,
2360
- sha256: sha256Hex(file.content),
2361
- role: file.role ?? inferEvidenceFileRole(relativePath)
2362
- });
2363
- }
2364
- return [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
2365
- }
2366
- function buildEvidenceManifest(parts) {
2367
- const runIds = [...parts.runIds];
2368
- if (runIds.length === 0) {
2369
- throw new Error("Evidence manifest requires at least one run id.");
2370
- }
2371
- for (const item of parts.sourceHashes) {
2372
- if (!runIds.includes(item.runId)) {
2373
- throw new Error(`sourceHashes runId "${item.runId}" is not listed in source.runIds.`);
2501
+ if (opts.nameQuery) {
2502
+ if (!nameMatches(s.name, opts.nameQuery)) continue;
2503
+ fields.push("step.name");
2374
2504
  }
2375
- if (item.algorithm !== "sha256") {
2376
- throw new Error(`Unsupported source hash algorithm: ${item.algorithm}`);
2505
+ if (opts.toolQuery) {
2506
+ const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
2507
+ if (!nameMatches(toolName, opts.toolQuery)) continue;
2508
+ fields.push("step.tool");
2377
2509
  }
2510
+ if (opts.durationFilter) {
2511
+ if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
2512
+ fields.push("step.durationMs");
2513
+ }
2514
+ if (opts.stepTypeFilter) {
2515
+ fields.push("step.type");
2516
+ }
2517
+ if (fields.length === 0) continue;
2518
+ out.push({
2519
+ runId: m.runId,
2520
+ runName: m.name,
2521
+ runStatus: m.status,
2522
+ stepId: c.stepId,
2523
+ stepName: s.name,
2524
+ stepType,
2525
+ timestamp: s.startTime ?? s.timestamp,
2526
+ durationMs: c.durationMs,
2527
+ matchReason: `step match: ${fields.join(", ")}`,
2528
+ matchedFields: fields,
2529
+ filePath: m.filePath
2530
+ });
2378
2531
  }
2379
- const assessment = {
2380
- status: parts.assessmentStatus,
2381
- note: parts.note ?? EVIDENCE_ASSESSMENT_NOTE
2382
- };
2383
- if (parts.sourceStatus !== void 0) {
2384
- assessment.sourceStatus = parts.sourceStatus;
2385
- }
2386
- return {
2387
- evidenceFormatVersion: EVIDENCE_FORMAT_VERSION,
2388
- generator: {
2389
- name: parts.generatorName ?? "agent-inspect",
2390
- version: parts.generatorVersion
2391
- },
2392
- createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2393
- source: {
2394
- runIds,
2395
- traceSchemaVersions: [...parts.traceSchemaVersions].sort((a, b) => a.localeCompare(b)),
2396
- sourceHashes: [...parts.sourceHashes].sort((a, b) => a.runId.localeCompare(b.runId))
2397
- },
2398
- policy: {
2399
- redactionProfile: parts.redactionProfile,
2400
- verificationPolicy: parts.verificationPolicy ?? parts.redactionProfile
2401
- },
2402
- assessment,
2403
- ...parts.semantics !== void 0 ? { semantics: { ...parts.semantics } } : {},
2404
- ...parts.contract !== void 0 ? { contract: { ...parts.contract } } : {},
2405
- files: buildEvidenceFileEntries(parts.files)
2406
- };
2407
- }
2408
-
2409
- // packages/core/src/diagnostics/programmatic.ts
2410
- var PROGRAMMATIC_DIAGNOSTIC_SPECS = Object.freeze({
2411
- AI_TRACE_INPUT_INVALID: {
2412
- code: "AI_TRACE_INPUT_INVALID",
2413
- summary: 'Expected { type: "file", path }, { type: "directory", path }, { type: "string", content }, { type: "buffer", content }, or { type: "stdin" }.',
2414
- remediation: "For a file path, use openTraceFile(path).",
2415
- relatedCodes: ["invalid_input"]
2416
- },
2417
- AI_TRACE_FORMAT_UNSUPPORTED: {
2418
- code: "AI_TRACE_FORMAT_UNSUPPORTED",
2419
- summary: "No trace reader could detect the input format.",
2420
- remediation: "Pass an AgentInspect JSONL file via openTraceFile, or set options.format to a registered reader.",
2421
- relatedCodes: ["unsupported_format"]
2422
- },
2423
- AI_TRACE_FORMAT_AMBIGUOUS: {
2424
- code: "AI_TRACE_FORMAT_AMBIGUOUS",
2425
- summary: "Multiple trace readers matched the input with equal confidence.",
2426
- remediation: "Set options.format explicitly to disambiguate the reader.",
2427
- relatedCodes: ["ambiguous_format"]
2428
- },
2429
- AI_TRACE_FACTS_INPUT_NOT_NORMALIZED: {
2430
- code: "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED",
2431
- summary: "TraceFacts requires TraceReadResult or PersistedInspectEvent[].",
2432
- remediation: "Use openTraceFile() to normalize a JSONL trace first."
2433
- },
2434
- AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED: {
2435
- code: "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
2436
- summary: "Multiple runs are available; select a run before executing checks.",
2437
- remediation: "Pass options.runId or TraceCheckInput.selectedRun.",
2438
- relatedCodes: ["AI_CHECK_RUN_SELECTION_REQUIRED"]
2439
- },
2440
- AI_TRACE_RELATIONSHIP_SELF_PARENT: {
2441
- code: "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2442
- summary: "A parentId equals its own event/step id.",
2443
- remediation: "Reject at capture (AI_LANGGRAPH_SELF_PARENT_REJECTED) or drop via logical projection; do not invent replacement parents.",
2444
- relatedCodes: [
2445
- "AI_LANGGRAPH_SELF_PARENT_REJECTED",
2446
- "AI_LOGICAL_SELF_PARENT_REMOVED"
2447
- ]
2448
- },
2449
- AI_TRACE_RELATIONSHIP_CYCLE: {
2450
- code: "AI_TRACE_RELATIONSHIP_CYCLE",
2451
- summary: "Trace contains a parentId cycle.",
2452
- remediation: "Use visibility-first tree linking for legacy fixtures; prefer acyclic capture for new adapter output.",
2453
- relatedCodes: ["structure.cycle"]
2454
- }
2455
- });
2456
- function formatProgrammaticDiagnostic(code, detail) {
2457
- const spec = PROGRAMMATIC_DIAGNOSTIC_SPECS[code];
2458
- const summary = detail?.trim() ? detail.trim() : spec.summary;
2459
- return `${code}: ${summary} Remediation: ${spec.remediation}`;
2460
- }
2461
-
2462
- // packages/core/src/checks/logical-events.ts
2463
- function isRecord6(value) {
2464
- return typeof value === "object" && value !== null && !Array.isArray(value);
2465
- }
2466
- function legacyEvent(event) {
2467
- const value = event.attributes?.legacyEvent;
2468
- return typeof value === "string" ? value : void 0;
2532
+ return out;
2469
2533
  }
2470
- function stepIdOf(event) {
2471
- const value = event.attributes?.stepId;
2472
- if (typeof value === "string" && value.trim() !== "") return value;
2473
- return void 0;
2534
+ async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
2535
+ const metas = [];
2536
+ for (const fileName of fileNames) {
2537
+ try {
2538
+ const filePath = getPath(fileName);
2539
+ const meta = await extractMetadata(filePath);
2540
+ metas.push(meta);
2541
+ } catch {
2542
+ }
2543
+ }
2544
+ return metas;
2474
2545
  }
2475
- function cloneEvent(event) {
2476
- return {
2477
- ...event,
2478
- ...event.attributes !== void 0 ? { attributes: { ...event.attributes } } : {},
2479
- ...event.error !== void 0 ? { error: { ...event.error } } : {},
2480
- ...event.tokenUsage !== void 0 ? { tokenUsage: { ...event.tokenUsage } } : {},
2481
- ...event.source !== void 0 ? { source: { ...event.source } } : {}
2482
- };
2546
+
2547
+ // packages/core/src/sessions/metadata.ts
2548
+ function isNonEmptyString3(value) {
2549
+ return typeof value === "string" && value.trim() !== "";
2483
2550
  }
2484
- function mergeAttributes(start, complete) {
2485
- const merged = {
2486
- ...isRecord6(complete.attributes) ? complete.attributes : {},
2487
- ...isRecord6(start.attributes) ? start.attributes : {}
2488
- };
2489
- merged.legacyEvent = start.attributes?.legacyEvent ?? complete.attributes?.legacyEvent;
2490
- merged.legacyCompleteEvent = complete.attributes?.legacyEvent;
2491
- if (complete.attributes?.errorStack !== void 0) {
2492
- merged.errorStack = complete.attributes.errorStack;
2551
+ function finitePositiveInt(value) {
2552
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
2553
+ return void 0;
2493
2554
  }
2494
- return Object.keys(merged).length > 0 ? merged : void 0;
2555
+ return Math.trunc(value);
2495
2556
  }
2496
- function pairStartComplete(start, complete) {
2497
- const attributes = mergeAttributes(start, complete);
2498
- const paired = {
2499
- ...cloneEvent(start),
2500
- status: complete.status,
2501
- timestamp: complete.timestamp ?? start.timestamp,
2502
- ...complete.endedAt !== void 0 ? { endedAt: complete.endedAt } : {},
2503
- ...complete.durationMs !== void 0 ? { durationMs: complete.durationMs } : {},
2504
- ...complete.error !== void 0 ? { error: { ...complete.error } } : {},
2505
- ...complete.tokenUsage !== void 0 && start.tokenUsage === void 0 ? { tokenUsage: { ...complete.tokenUsage } } : {},
2506
- ...attributes !== void 0 ? { attributes } : {}
2507
- };
2508
- return {
2509
- ...paired,
2510
- sourceEventIds: Object.freeze([start.eventId, complete.eventId]),
2511
- projection: {
2512
- paired: true,
2513
- absorbedEventIds: Object.freeze([complete.eventId]),
2514
- parentNormalized: false,
2515
- ...start.parentId !== void 0 ? { originalParentId: start.parentId } : {}
2557
+ function extractSessionWorkflowMetadata(record) {
2558
+ if (!record) return void 0;
2559
+ const out = {};
2560
+ let found = false;
2561
+ const assignString = (key, value) => {
2562
+ if (isNonEmptyString3(value)) {
2563
+ out[key] = value.trim();
2564
+ found = true;
2516
2565
  }
2517
2566
  };
2567
+ assignString("sessionId", record.sessionId);
2568
+ assignString("conversationId", record.conversationId);
2569
+ assignString("groupId", record.groupId);
2570
+ assignString("parentGroupId", record.parentGroupId);
2571
+ assignString("retryOf", record.retryOf);
2572
+ assignString("retryReason", record.retryReason);
2573
+ assignString("handoffFrom", record.handoffFrom);
2574
+ assignString("handoffTo", record.handoffTo);
2575
+ assignString("subAgentId", record.subAgentId);
2576
+ assignString("subAgentName", record.subAgentName);
2577
+ assignString("jobId", record.jobId);
2578
+ assignString("queueName", record.queueName);
2579
+ assignString("workflowName", record.workflowName);
2580
+ assignString("workflowStep", record.workflowStep);
2581
+ assignString("toolCallId", record.toolCallId);
2582
+ assignString("mcpToolCallId", record.mcpToolCallId);
2583
+ assignString("linkedStepId", record.linkedStepId);
2584
+ assignString("operationId", record.operationId);
2585
+ assignString("attemptId", record.attemptId);
2586
+ assignString("fallbackOf", record.fallbackOf);
2587
+ assignString("idempotencyKey", record.idempotencyKey);
2588
+ assignString("correlationId", record.correlationId);
2589
+ assignString("requestId", record.requestId);
2590
+ assignString("decisionId", record.decisionId);
2591
+ const attempt = finitePositiveInt(record.attempt);
2592
+ if (attempt !== void 0) {
2593
+ out.attempt = attempt;
2594
+ found = true;
2595
+ }
2596
+ const attemptNumber = finitePositiveInt(record.attemptNumber);
2597
+ if (attemptNumber !== void 0) {
2598
+ out.attemptNumber = attemptNumber;
2599
+ found = true;
2600
+ }
2601
+ return found ? out : void 0;
2518
2602
  }
2519
- function asLogical(event, extras) {
2520
- return {
2521
- ...cloneEvent(event),
2522
- sourceEventIds: Object.freeze([event.eventId]),
2523
- projection: {
2524
- paired: false,
2525
- absorbedEventIds: Object.freeze([]),
2526
- parentNormalized: extras?.parentNormalized === true,
2527
- ...{}
2528
- }
2529
- };
2603
+
2604
+ // packages/core/src/bundle/safety-status.ts
2605
+ function aggregateBundleSafeStatus(statuses) {
2606
+ if (statuses.length === 0) return "UNKNOWN";
2607
+ if (statuses.some((status) => status === "UNSAFE")) return "UNSAFE";
2608
+ if (statuses.some((status) => status === "UNKNOWN")) return "UNKNOWN";
2609
+ if (statuses.some((status) => status === "SAFE WITH WARNINGS")) return "SAFE WITH WARNINGS";
2610
+ return "SAFE";
2530
2611
  }
2531
- function projectLogicalEvents(events) {
2532
- const diagnostics = [];
2533
- const byRun = /* @__PURE__ */ new Map();
2534
- for (const event of events) {
2535
- const list = byRun.get(event.runId) ?? [];
2536
- list.push(event);
2537
- byRun.set(event.runId, list);
2538
- }
2539
- const absorbedIds = /* @__PURE__ */ new Set();
2540
- const logicalByRawId = /* @__PURE__ */ new Map();
2541
- const stepIdToLogicalId = /* @__PURE__ */ new Map();
2542
- const logical = [];
2543
- const orderedRuns = [...byRun.keys()].sort((a, b) => a.localeCompare(b));
2544
- for (const runId of orderedRuns) {
2545
- const runEvents = byRun.get(runId) ?? [];
2546
- const starts = [];
2547
- const completes = [];
2548
- const others = [];
2549
- for (const event of runEvents) {
2550
- const legacy = legacyEvent(event);
2551
- if (legacy === "step_started" || legacy === "run_started" && event.status === "running") {
2552
- starts.push(event);
2553
- } else if (legacy === "step_completed" || legacy === "run_completed") {
2554
- completes.push(event);
2555
- } else {
2556
- others.push(event);
2557
- }
2558
- }
2559
- const usedCompletes = /* @__PURE__ */ new Set();
2560
- for (const start of starts) {
2561
- const stepId = stepIdOf(start);
2562
- let match;
2563
- if (legacyEvent(start) === "run_started") {
2564
- const candidates = completes.filter(
2565
- (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "run_completed"
2566
- );
2567
- if (candidates.length > 1) {
2568
- diagnostics.push({
2569
- code: "AI_LOGICAL_PAIR_AMBIGUOUS",
2570
- message: `Multiple run_completed rows for run ${runId}; using first by eventId.`,
2571
- eventIds: candidates.map((c) => c.eventId)
2572
- });
2573
- candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
2574
- }
2575
- match = candidates[0];
2576
- } else if (stepId) {
2577
- const candidates = completes.filter(
2578
- (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "step_completed" && stepIdOf(c) === stepId
2579
- );
2580
- if (candidates.length > 1) {
2581
- diagnostics.push({
2582
- code: "AI_LOGICAL_PAIR_AMBIGUOUS",
2583
- message: `Multiple step_completed rows for stepId ${stepId}; using first by eventId.`,
2584
- eventIds: candidates.map((c) => c.eventId)
2585
- });
2586
- candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
2587
- }
2588
- match = candidates[0];
2589
- }
2590
- if (match) {
2591
- usedCompletes.add(match.eventId);
2592
- absorbedIds.add(match.eventId);
2593
- const paired = pairStartComplete(start, match);
2594
- logical.push(paired);
2595
- logicalByRawId.set(start.eventId, paired);
2596
- logicalByRawId.set(match.eventId, paired);
2597
- if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, paired.eventId);
2598
- } else {
2599
- diagnostics.push({
2600
- code: "AI_LOGICAL_PAIR_UNMATCHED_START",
2601
- message: `No matching complete for start ${start.eventId}.`,
2602
- eventIds: [start.eventId]
2603
- });
2604
- const alone = asLogical(start);
2605
- logical.push(alone);
2606
- logicalByRawId.set(start.eventId, alone);
2607
- if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2608
- }
2609
- }
2610
- for (const complete of completes) {
2611
- if (usedCompletes.has(complete.eventId)) continue;
2612
- diagnostics.push({
2613
- code: "AI_LOGICAL_PAIR_UNMATCHED_COMPLETE",
2614
- message: `No matching start for complete ${complete.eventId}.`,
2615
- eventIds: [complete.eventId]
2616
- });
2617
- const alone = asLogical(complete);
2618
- logical.push(alone);
2619
- logicalByRawId.set(complete.eventId, alone);
2620
- const stepId = stepIdOf(complete);
2621
- if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
2622
- stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2623
- }
2624
- }
2625
- for (const event of others) {
2626
- const alone = asLogical(event);
2627
- logical.push(alone);
2628
- logicalByRawId.set(event.eventId, alone);
2629
- const stepId = stepIdOf(event);
2630
- if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
2631
- stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2632
- }
2633
- }
2612
+ function toMetadataSafeStatus(status) {
2613
+ if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
2614
+ return status;
2615
+ }
2616
+ function bundleFailsOnSafety(status, allowUnsafe) {
2617
+ return status === "UNSAFE" || status === "UNKNOWN";
2618
+ }
2619
+
2620
+ // packages/core/src/bundle/manifest.ts
2621
+ var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
2622
+ function buildBundleMetadata(parts) {
2623
+ const aggregate = aggregateBundleSafeStatus(
2624
+ parts.checks.runs.map((run) => run.status)
2625
+ );
2626
+ return {
2627
+ createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2628
+ agentInspectVersion: parts.agentInspectVersion,
2629
+ redactionProfile: parts.profile,
2630
+ sourceTraceCount: parts.resolve.runIds.length,
2631
+ runIds: [...parts.resolve.runIds],
2632
+ safeStatus: toMetadataSafeStatus(aggregate),
2633
+ files: [...parts.files].sort((a, b) => a.localeCompare(b)),
2634
+ note: BUNDLE_NOTE,
2635
+ ...parts.resolve.sessionId !== void 0 ? { sessionId: parts.resolve.sessionId } : {},
2636
+ ...parts.resolve.since !== void 0 ? { since: parts.resolve.since } : {}
2637
+ };
2638
+ }
2639
+
2640
+ // packages/core/src/evidence/types.ts
2641
+ var EVIDENCE_FORMAT_VERSION = "1.0";
2642
+ var EVIDENCE_ASSESSMENT_NOTE = "Best-effort local safety verification only; not a compliance certification.";
2643
+ var EVIDENCE_MANIFEST_FILENAME = "evidence.json";
2644
+ function sha256Hex(data) {
2645
+ const hash = crypto.createHash("sha256");
2646
+ if (typeof data === "string") {
2647
+ hash.update(data, "utf8");
2648
+ } else {
2649
+ hash.update(data);
2634
2650
  }
2635
- const logicalById = new Map(logical.map((e) => [e.eventId, e]));
2636
- const normalized = [];
2637
- for (const event of logical) {
2638
- const originalParentId = event.parentId;
2639
- if (!originalParentId) {
2640
- normalized.push(event);
2641
- continue;
2642
- }
2643
- let nextParent = originalParentId;
2644
- let remapped = false;
2645
- const viaAbsorbed = logicalByRawId.get(originalParentId);
2646
- if (viaAbsorbed && viaAbsorbed.eventId !== originalParentId) {
2647
- nextParent = viaAbsorbed.eventId;
2648
- remapped = true;
2649
- } else if (!logicalById.has(originalParentId)) {
2650
- const viaStep = stepIdToLogicalId.get(`${event.runId}:${originalParentId}`);
2651
- if (viaStep) {
2652
- nextParent = viaStep;
2653
- remapped = true;
2654
- }
2651
+ return hash.digest("hex");
2652
+ }
2653
+ function assertEvidenceRelativePath(relativePath) {
2654
+ if (typeof relativePath !== "string" || relativePath.trim() === "") {
2655
+ throw new Error("Evidence file path must be a non-empty relative path.");
2656
+ }
2657
+ const trimmed = relativePath.trim().replaceAll("\\", "/");
2658
+ if (path14__default.default.isAbsolute(trimmed) || trimmed.startsWith("/")) {
2659
+ throw new Error(`Evidence file path must be relative: ${relativePath}`);
2660
+ }
2661
+ const parts = trimmed.split("/").filter((part) => part !== "");
2662
+ if (parts.length === 0) {
2663
+ throw new Error(`Evidence file path must be relative: ${relativePath}`);
2664
+ }
2665
+ for (const part of parts) {
2666
+ if (part === "." || part === "..") {
2667
+ throw new Error(`Evidence file path must not contain "." or "..": ${relativePath}`);
2655
2668
  }
2656
- if (!remapped) {
2657
- if (originalParentId === event.eventId) {
2658
- diagnostics.push({
2659
- code: "AI_LOGICAL_SELF_PARENT_REMOVED",
2660
- message: formatProgrammaticDiagnostic(
2661
- "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2662
- `Removed self-parent edge on ${event.eventId}.`
2663
- ),
2664
- eventIds: [event.eventId]
2665
- });
2666
- const { parentId: _drop, ...rest } = event;
2667
- normalized.push({
2668
- ...rest,
2669
- projection: {
2670
- ...event.projection,
2671
- parentNormalized: true,
2672
- originalParentId
2673
- }
2674
- });
2675
- continue;
2676
- }
2677
- if (!logicalById.has(originalParentId) && !logicalByRawId.has(originalParentId)) {
2678
- const mapping = event.attributes?.parentMapping;
2679
- const unresolved = mapping === "unresolved" || event.attributes?.parentUnresolved === true || event.attributes?.unresolvedParent === true || // LangGraph/framework scaffolding sentinel labels are not event ids.
2680
- /^LangGraph$/i.test(originalParentId) || originalParentId.startsWith("unresolved:");
2681
- if (!unresolved) {
2682
- diagnostics.push({
2683
- code: "AI_LOGICAL_PARENT_UNRESOLVED",
2684
- message: `Parent ${originalParentId} unresolved for ${event.eventId}.`,
2685
- eventIds: [event.eventId]
2686
- });
2687
- }
2688
- }
2689
- normalized.push(event);
2690
- continue;
2669
+ }
2670
+ return parts.join("/");
2671
+ }
2672
+
2673
+ // packages/core/src/evidence/manifest.ts
2674
+ function stable(value) {
2675
+ if (Array.isArray(value)) return value.map(stable);
2676
+ if (value === null || typeof value !== "object") return value;
2677
+ const record = value;
2678
+ return Object.fromEntries(
2679
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
2680
+ );
2681
+ }
2682
+ function serializeEvidenceManifest(manifest) {
2683
+ return `${JSON.stringify(stable(manifest), null, 2)}
2684
+ `;
2685
+ }
2686
+ function inferEvidenceFileRole(relativePath) {
2687
+ const normalized = assertEvidenceRelativePath(relativePath);
2688
+ const base = normalized.includes("/") ? normalized.slice(normalized.lastIndexOf("/") + 1) : normalized;
2689
+ if (base === "evidence.html" || base === "trace.html" || base.endsWith(".html")) {
2690
+ return "report";
2691
+ }
2692
+ if (base === "trace.jsonl" || base.endsWith(".jsonl")) {
2693
+ return "redacted-trace";
2694
+ }
2695
+ if (base === "check-results.json") {
2696
+ return "checks";
2697
+ }
2698
+ if (base === "contract.resolved.json") {
2699
+ return "contract";
2700
+ }
2701
+ if (base === "redaction-report.json") {
2702
+ return "redaction-report";
2703
+ }
2704
+ if (base === "summary.md") {
2705
+ return "summary";
2706
+ }
2707
+ return "other";
2708
+ }
2709
+ function buildEvidenceFileEntries(files) {
2710
+ const byPath = /* @__PURE__ */ new Map();
2711
+ for (const file of files) {
2712
+ const relativePath = assertEvidenceRelativePath(file.path);
2713
+ if (relativePath === EVIDENCE_MANIFEST_FILENAME) {
2714
+ throw new Error(
2715
+ `Do not include ${EVIDENCE_MANIFEST_FILENAME} in packaged file hashes (self-hash is undefined).`
2716
+ );
2691
2717
  }
2692
- if (nextParent === event.eventId) {
2693
- diagnostics.push({
2694
- code: "AI_LOGICAL_SELF_PARENT_REMOVED",
2695
- message: formatProgrammaticDiagnostic(
2696
- "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2697
- `Removed self-parent edge on ${event.eventId} (was ${originalParentId}).`
2698
- ),
2699
- eventIds: [event.eventId]
2700
- });
2701
- const { parentId: _drop, ...rest } = event;
2702
- normalized.push({
2703
- ...rest,
2704
- projection: {
2705
- ...event.projection,
2706
- parentNormalized: true,
2707
- originalParentId
2708
- }
2709
- });
2710
- continue;
2718
+ if (byPath.has(relativePath)) {
2719
+ throw new Error(`Duplicate evidence file path: ${relativePath}`);
2711
2720
  }
2712
- diagnostics.push({
2713
- code: "AI_LOGICAL_PARENT_REMAPPED",
2714
- message: `Remapped parent ${originalParentId} \u2192 ${nextParent} for ${event.eventId}.`,
2715
- eventIds: [event.eventId]
2716
- });
2717
- normalized.push({
2718
- ...event,
2719
- parentId: nextParent,
2720
- projection: {
2721
- ...event.projection,
2722
- parentNormalized: true,
2723
- originalParentId
2724
- }
2721
+ byPath.set(relativePath, {
2722
+ path: relativePath,
2723
+ sha256: sha256Hex(file.content),
2724
+ role: file.role ?? inferEvidenceFileRole(relativePath)
2725
2725
  });
2726
2726
  }
2727
- const rawIndex = new Map(events.map((e, i) => [e.eventId, i]));
2728
- normalized.sort((a, b) => {
2729
- const ai = rawIndex.get(a.sourceEventIds[0]) ?? 0;
2730
- const bi = rawIndex.get(b.sourceEventIds[0]) ?? 0;
2731
- return ai - bi || a.eventId.localeCompare(b.eventId);
2732
- });
2733
- return {
2734
- logicalEvents: Object.freeze(normalized),
2735
- diagnostics: Object.freeze(diagnostics)
2736
- };
2727
+ return [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
2737
2728
  }
2738
- function resolveCanonicalToolName(event) {
2739
- const attrs2 = event.attributes;
2740
- const direct = pickString(attrs2, ["toolName", "tool"]);
2741
- if (direct) return direct;
2742
- const metadata = attrs2?.metadata;
2743
- if (isRecord6(metadata)) {
2744
- const nested = pickString(metadata, ["toolName", "tool"]);
2745
- if (nested) return nested;
2729
+ function buildEvidenceManifest(parts) {
2730
+ const runIds = [...parts.runIds];
2731
+ if (runIds.length === 0) {
2732
+ throw new Error("Evidence manifest requires at least one run id.");
2746
2733
  }
2747
- for (const prefix of ["tool:", "function:", "mcp-tools:"]) {
2748
- if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
2734
+ for (const item of parts.sourceHashes) {
2735
+ if (!runIds.includes(item.runId)) {
2736
+ throw new Error(`sourceHashes runId "${item.runId}" is not listed in source.runIds.`);
2737
+ }
2738
+ if (item.algorithm !== "sha256") {
2739
+ throw new Error(`Unsupported source hash algorithm: ${item.algorithm}`);
2740
+ }
2749
2741
  }
2750
- return event.name;
2751
- }
2752
- function pickString(record, keys) {
2753
- if (!record) return void 0;
2754
- for (const key of keys) {
2755
- const value = record[key];
2756
- if (typeof value === "string" && value.trim() !== "") return value.trim();
2742
+ const assessment = {
2743
+ status: parts.assessmentStatus,
2744
+ note: parts.note ?? EVIDENCE_ASSESSMENT_NOTE
2745
+ };
2746
+ if (parts.sourceStatus !== void 0) {
2747
+ assessment.sourceStatus = parts.sourceStatus;
2757
2748
  }
2758
- return void 0;
2749
+ return {
2750
+ evidenceFormatVersion: EVIDENCE_FORMAT_VERSION,
2751
+ generator: {
2752
+ name: parts.generatorName ?? "agent-inspect",
2753
+ version: parts.generatorVersion
2754
+ },
2755
+ createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2756
+ source: {
2757
+ runIds,
2758
+ traceSchemaVersions: [...parts.traceSchemaVersions].sort((a, b) => a.localeCompare(b)),
2759
+ sourceHashes: [...parts.sourceHashes].sort((a, b) => a.runId.localeCompare(b.runId))
2760
+ },
2761
+ policy: {
2762
+ redactionProfile: parts.redactionProfile,
2763
+ verificationPolicy: parts.verificationPolicy ?? parts.redactionProfile
2764
+ },
2765
+ assessment,
2766
+ ...parts.semantics !== void 0 ? { semantics: { ...parts.semantics } } : {},
2767
+ ...parts.contract !== void 0 ? { contract: { ...parts.contract } } : {},
2768
+ ...parts.inputs !== void 0 ? { inputs: { ...parts.inputs } } : {},
2769
+ files: buildEvidenceFileEntries(parts.files)
2770
+ };
2759
2771
  }
2760
2772
 
2761
2773
  // packages/core/src/checks/derived-failure.ts
@@ -3429,7 +3441,11 @@ function emptySummary() {
3429
3441
  failed: 0,
3430
3442
  warnings: 0,
3431
3443
  errors: 0,
3432
- rulesEvaluated: 0
3444
+ rulesEvaluated: 0,
3445
+ rulesPassed: 0,
3446
+ rulesWarning: 0,
3447
+ rulesFailed: 0,
3448
+ rulesError: 0
3433
3449
  };
3434
3450
  }
3435
3451
  function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
@@ -3441,7 +3457,11 @@ function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
3441
3457
  summary: {
3442
3458
  ...emptySummary(),
3443
3459
  errors: diagnostics.filter((item) => item.severity === "error").length,
3444
- rulesEvaluated: ruleExecutions.length
3460
+ rulesEvaluated: ruleExecutions.length,
3461
+ rulesPassed: ruleExecutions.filter((item) => item.status === "pass").length,
3462
+ rulesWarning: ruleExecutions.filter((item) => item.status === "warning").length,
3463
+ rulesFailed: ruleExecutions.filter((item) => item.status === "fail").length,
3464
+ rulesError: ruleExecutions.filter((item) => item.status === "error").length
3445
3465
  },
3446
3466
  findings: [],
3447
3467
  diagnostics: [...diagnostics],
@@ -3601,7 +3621,7 @@ function normalizeFinding(rule, finding) {
3601
3621
  ...finding.action !== void 0 ? { action: finding.action } : {}
3602
3622
  };
3603
3623
  }
3604
- function summarize(findings, diagnostics, rulesEvaluated) {
3624
+ function summarize(findings, diagnostics, ruleExecutions) {
3605
3625
  return {
3606
3626
  passed: findings.filter((finding) => finding.status === "pass").length,
3607
3627
  failed: findings.filter(
@@ -3611,7 +3631,11 @@ function summarize(findings, diagnostics, rulesEvaluated) {
3611
3631
  (finding) => finding.status === "warning" || finding.severity === "warning"
3612
3632
  ).length,
3613
3633
  errors: diagnostics.filter((item) => item.severity === "error").length,
3614
- rulesEvaluated
3634
+ rulesEvaluated: ruleExecutions.length,
3635
+ rulesPassed: ruleExecutions.filter((item) => item.status === "pass").length,
3636
+ rulesWarning: ruleExecutions.filter((item) => item.status === "warning").length,
3637
+ rulesFailed: ruleExecutions.filter((item) => item.status === "fail").length,
3638
+ rulesError: ruleExecutions.filter((item) => item.status === "error").length
3615
3639
  };
3616
3640
  }
3617
3641
  function classifyRuleExecution(findings, threw) {
@@ -4052,7 +4076,7 @@ function runTraceChecks(input, options = {}) {
4052
4076
  }
4053
4077
  const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
4054
4078
  const sortedFindings = findings.sort(compareFindings(eventById));
4055
- const summary = summarize(sortedFindings, diagnostics, ruleExecutions.length);
4079
+ const summary = summarize(sortedFindings, diagnostics, ruleExecutions);
4056
4080
  const status = summary.failed > 0 ? "fail" : "pass";
4057
4081
  return {
4058
4082
  ok: status === "pass",
@@ -8195,6 +8219,15 @@ function actionForRule(rule) {
8195
8219
  if (rule.strategy === "full") return "replace";
8196
8220
  return rule.strategy;
8197
8221
  }
8222
+ function isCompleteRedactionMarker(value, replacement = "[REDACTED]") {
8223
+ if (typeof value !== "string") return false;
8224
+ const trimmed = value.trim();
8225
+ if (trimmed.length === 0) return false;
8226
+ if (trimmed === replacement) return true;
8227
+ if (/^\[HASH:[A-Za-z0-9_-]+\]$/.test(trimmed)) return true;
8228
+ if (/^\[REDACTED:[^\]]*\]$/.test(trimmed)) return true;
8229
+ return false;
8230
+ }
8198
8231
  function applyRule(rule, value, replacement) {
8199
8232
  if (rule.strategy === "full") return replacement;
8200
8233
  const asString = stringifyScalar(value);
@@ -8309,6 +8342,9 @@ var Redactor2 = class {
8309
8342
  if (key !== void 0) {
8310
8343
  const rule = findCompiledKeyRule(key, this.#rules);
8311
8344
  if (rule) {
8345
+ if (isCompleteRedactionMarker(value, this.#replacement)) {
8346
+ return value;
8347
+ }
8312
8348
  this.#recordFinding(
8313
8349
  state,
8314
8350
  makeFinding(path17, `key.${rule.key}`, actionForRule(rule), "key", "warning")