@agent-inspect/mcp-server 6.1.0 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import readline from 'readline';
2
2
  import path from 'path';
3
3
  import { AsyncLocalStorage } from 'async_hooks';
4
- import 'crypto';
4
+ import crypto from 'crypto';
5
5
  import { readdir, stat, readFile } from 'fs/promises';
6
6
  import os from 'os';
7
7
  import 'nanoid';
@@ -36,6 +36,183 @@ function extractCorrelationMetadata(record) {
36
36
  }
37
37
  return found ? out : void 0;
38
38
  }
39
+ var DEFAULT_REDACT_KEYS = [
40
+ "authorization",
41
+ "cookie",
42
+ "token",
43
+ "apiKey",
44
+ "password",
45
+ "secret",
46
+ "email"
47
+ ];
48
+ function isRecord(v) {
49
+ return typeof v === "object" && v !== null && !Array.isArray(v);
50
+ }
51
+ function toKey(s) {
52
+ return s.toLowerCase();
53
+ }
54
+ function stableHash(value) {
55
+ const h = crypto.createHash("sha256").update(value, "utf8").digest("hex");
56
+ return h.slice(0, 8);
57
+ }
58
+ function compileRules(rules, extraKeys) {
59
+ const out = /* @__PURE__ */ new Map();
60
+ const set = (r) => {
61
+ const k = toKey(r.key);
62
+ out.set(k, { ...r, key: k });
63
+ };
64
+ for (const k of DEFAULT_REDACT_KEYS) {
65
+ set({ key: k, strategy: "full" });
66
+ }
67
+ for (const k of extraKeys ?? []) {
68
+ if (typeof k === "string" && k.length > 0) {
69
+ set({ key: k, strategy: "full" });
70
+ }
71
+ }
72
+ for (const r of rules ?? []) {
73
+ if (typeof r === "string") {
74
+ set({ key: r, strategy: "full" });
75
+ continue;
76
+ }
77
+ const key = r.key;
78
+ if (r.strategy === "full") set({ key, strategy: "full" });
79
+ if (r.strategy === "hash") set({ key, strategy: "hash" });
80
+ if (r.strategy === "prefix") {
81
+ set({ key, strategy: "prefix", keep: typeof r.keep === "number" ? r.keep : 8 });
82
+ }
83
+ }
84
+ return [...out.values()];
85
+ }
86
+ var Redactor = class {
87
+ #rules;
88
+ constructor(options) {
89
+ this.#rules = compileRules(options?.rules, options?.extraKeys);
90
+ }
91
+ redactValue(key, value) {
92
+ const k = toKey(key);
93
+ const rule = this.#rules.find((r) => r.key === k);
94
+ if (!rule) {
95
+ return this.#redactNested(value);
96
+ }
97
+ if (rule.strategy === "full") return "[REDACTED]";
98
+ const asString = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : void 0;
99
+ if (rule.strategy === "prefix") {
100
+ if (asString === void 0) return "[REDACTED]";
101
+ const keep = Math.max(0, Math.floor(rule.keep));
102
+ return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
103
+ }
104
+ if (rule.strategy === "hash") {
105
+ if (asString === void 0) return "[HASH:unknown]";
106
+ return `[HASH:${stableHash(asString)}]`;
107
+ }
108
+ return this.#redactNested(value);
109
+ }
110
+ redactRecord(record) {
111
+ const out = {};
112
+ for (const [k, v] of Object.entries(record)) {
113
+ out[k] = this.redactValue(k, v);
114
+ }
115
+ return out;
116
+ }
117
+ #redactNested(value) {
118
+ if (Array.isArray(value)) {
119
+ return value.map((v) => this.#redactNested(v));
120
+ }
121
+ if (isRecord(value)) {
122
+ const out = {};
123
+ for (const [k, v] of Object.entries(value)) {
124
+ out[k] = this.redactValue(k, v);
125
+ }
126
+ return out;
127
+ }
128
+ return value;
129
+ }
130
+ };
131
+
132
+ // packages/core/src/redaction-profiles.ts
133
+ var SHARE_PROFILE_EXTRA_KEYS = [
134
+ "userEmail",
135
+ "customerEmail",
136
+ "phone",
137
+ "phoneNumber",
138
+ "address",
139
+ "ip",
140
+ "ipAddress",
141
+ "sessionId",
142
+ "requestId",
143
+ "correlationId",
144
+ "decisionId",
145
+ "groupId",
146
+ "customerId",
147
+ "userId",
148
+ "accountId",
149
+ "tenantId",
150
+ "orgId",
151
+ "organizationId",
152
+ "traceId",
153
+ "spanId",
154
+ "parentSpanId"
155
+ ];
156
+ var STRICT_PROFILE_EXTRA_KEYS = [
157
+ "prompt",
158
+ "completion",
159
+ "input",
160
+ "output",
161
+ "inputPreview",
162
+ "outputPreview",
163
+ "message",
164
+ "messages",
165
+ "transcript",
166
+ "context",
167
+ "document",
168
+ "documents",
169
+ "chunk",
170
+ "chunks",
171
+ "retrieval",
172
+ "query"
173
+ ];
174
+ function resolveRedactionProfile(profile = "local") {
175
+ switch (profile) {
176
+ case "local":
177
+ return { profile: "local", extraKeys: [] };
178
+ case "share":
179
+ return {
180
+ profile: "share",
181
+ extraKeys: SHARE_PROFILE_EXTRA_KEYS,
182
+ maxMetadataValueLengthCap: 500,
183
+ maxPreviewLengthCap: 200
184
+ };
185
+ case "strict":
186
+ return {
187
+ profile: "strict",
188
+ extraKeys: [...SHARE_PROFILE_EXTRA_KEYS, ...STRICT_PROFILE_EXTRA_KEYS],
189
+ maxMetadataValueLengthCap: 200,
190
+ maxPreviewLengthCap: 80
191
+ };
192
+ default:
193
+ return { profile: "local", extraKeys: [] };
194
+ }
195
+ }
196
+ function isPreviewKey(key) {
197
+ return key.toLowerCase().includes("preview");
198
+ }
199
+ function applyProfileMetadataCaps(maxMetadataValueLength, maxPreviewLength, resolved) {
200
+ let meta = maxMetadataValueLength;
201
+ let preview = maxPreviewLength;
202
+ if (resolved.maxMetadataValueLengthCap !== void 0) {
203
+ meta = Math.min(meta, resolved.maxMetadataValueLengthCap);
204
+ }
205
+ if (resolved.maxPreviewLengthCap !== void 0) {
206
+ preview = Math.min(preview, resolved.maxPreviewLengthCap);
207
+ }
208
+ return { maxMetadataValueLength: meta, maxPreviewLength: preview };
209
+ }
210
+ function truncateStringForProfile(value, key, maxMetadataValueLength, maxPreviewLength) {
211
+ const max = isPreviewKey(key) ? maxPreviewLength : maxMetadataValueLength;
212
+ if (max <= 0) return "\u2026";
213
+ if (value.length <= max) return value;
214
+ return `${value.slice(0, max)}\u2026`;
215
+ }
39
216
 
40
217
  // packages/core/src/types.ts
41
218
  var STEP_TYPES = [
@@ -47,14 +224,14 @@ var STEP_TYPES = [
47
224
  "state",
48
225
  "custom"
49
226
  ];
50
- function isRecord(value) {
227
+ function isRecord2(value) {
51
228
  return typeof value === "object" && value !== null && !Array.isArray(value);
52
229
  }
53
230
  function isStepType(value) {
54
231
  return typeof value === "string" && STEP_TYPES.includes(value);
55
232
  }
56
233
  function isTraceEvent(value) {
57
- if (!isRecord(value)) return false;
234
+ if (!isRecord2(value)) return false;
58
235
  if (value.schemaVersion !== "0.1") return false;
59
236
  if (typeof value.timestamp !== "number") return false;
60
237
  if (typeof value.event !== "string") return false;
@@ -114,7 +291,7 @@ var PERSISTED_EVENT_STATUSES = [
114
291
  "error",
115
292
  "unknown"
116
293
  ];
117
- function isRecord2(value) {
294
+ function isRecord3(value) {
118
295
  return typeof value === "object" && value !== null && !Array.isArray(value);
119
296
  }
120
297
  function isString(value) {
@@ -145,21 +322,21 @@ function isPersistedEventStatus(value) {
145
322
  return typeof value === "string" && PERSISTED_EVENT_STATUSES.includes(value);
146
323
  }
147
324
  function isPersistedEventSource(value) {
148
- if (!isRecord2(value)) return false;
325
+ if (!isRecord3(value)) return false;
149
326
  if (!isPersistedEventSourceType(value.type)) return false;
150
327
  if (!isOptionalString(value.name)) return false;
151
328
  if (!isOptionalString(value.version)) return false;
152
329
  return true;
153
330
  }
154
331
  function isPersistedInspectError(value) {
155
- if (!isRecord2(value)) return false;
332
+ if (!isRecord3(value)) return false;
156
333
  if (!isNonEmptyString2(value.message)) return false;
157
334
  if (!isOptionalString(value.name)) return false;
158
335
  if (!isOptionalString(value.code)) return false;
159
336
  return true;
160
337
  }
161
338
  function isPersistedTokenUsage(value) {
162
- if (!isRecord2(value)) return false;
339
+ if (!isRecord3(value)) return false;
163
340
  if (!isOptionalNonNegativeNumber(value.input)) return false;
164
341
  if (!isOptionalNonNegativeNumber(value.output)) return false;
165
342
  if (!isOptionalNonNegativeNumber(value.total)) return false;
@@ -167,14 +344,14 @@ function isPersistedTokenUsage(value) {
167
344
  return true;
168
345
  }
169
346
  function isPersistedTraceContext(value) {
170
- if (!isRecord2(value)) return false;
347
+ if (!isRecord3(value)) return false;
171
348
  if (!isOptionalString(value.traceId)) return false;
172
349
  if (!isOptionalString(value.spanId)) return false;
173
350
  if (!isOptionalString(value.parentSpanId)) return false;
174
351
  return true;
175
352
  }
176
353
  function isPersistedInspectEvent(value) {
177
- if (!isRecord2(value)) return false;
354
+ if (!isRecord3(value)) return false;
178
355
  if (value.schemaVersion !== "0.2" && value.schemaVersion !== "1.0") {
179
356
  return false;
180
357
  }
@@ -196,7 +373,7 @@ function isPersistedInspectEvent(value) {
196
373
  if (value.durationMs !== void 0 && !isNonNegativeNumber(value.durationMs)) {
197
374
  return false;
198
375
  }
199
- if (value.attributes !== void 0 && !isRecord2(value.attributes)) {
376
+ if (value.attributes !== void 0 && !isRecord3(value.attributes)) {
200
377
  return false;
201
378
  }
202
379
  if (value.error !== void 0 && !isPersistedInspectError(value.error)) {
@@ -548,6 +725,24 @@ function parseDuration(duration) {
548
725
  }
549
726
  }
550
727
  }
728
+ function formatDuration(ms) {
729
+ if (!Number.isFinite(ms)) {
730
+ return "0ms";
731
+ }
732
+ if (ms < 0) {
733
+ throw new Error(`formatDuration: ms must be non-negative (got ${ms})`);
734
+ }
735
+ if (ms < 1e3) {
736
+ return `${Math.floor(ms)}ms`;
737
+ }
738
+ if (ms < 6e4) {
739
+ return `${(ms / 1e3).toFixed(2)}s`;
740
+ }
741
+ if (ms < 36e5) {
742
+ return `${(ms / 6e4).toFixed(1)}m`;
743
+ }
744
+ return `${(ms / 36e5).toFixed(1)}h`;
745
+ }
551
746
 
552
747
  // packages/core/src/utils.ts
553
748
  var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
@@ -557,6 +752,9 @@ var FALLBACK_TRACE_DIR = path.join(
557
752
  "agent-inspect",
558
753
  RUNS_DIR_NAME
559
754
  );
755
+ function formatDuration2(ms) {
756
+ return formatDuration(ms);
757
+ }
560
758
  function getDefaultTraceDir() {
561
759
  const envDir = process.env.AGENT_INSPECT_TRACE_DIR;
562
760
  if (typeof envDir === "string" && envDir.trim() !== "") {
@@ -611,11 +809,11 @@ function warn(message, error) {
611
809
  }
612
810
 
613
811
  // packages/core/src/read-trace.ts
614
- function isRecord3(value) {
812
+ function isRecord4(value) {
615
813
  return typeof value === "object" && value !== null && !Array.isArray(value);
616
814
  }
617
815
  function detectLineFormat(parsed) {
618
- if (!isRecord3(parsed)) return "unknown";
816
+ if (!isRecord4(parsed)) return "unknown";
619
817
  if (parsed.schemaVersion === "0.1") return "0.1";
620
818
  if (parsed.schemaVersion === "0.2") return "0.2";
621
819
  if (parsed.schemaVersion === "1.0") return "1.0";
@@ -687,7 +885,7 @@ function parseTraceJsonl(raw, options = {}) {
687
885
  }
688
886
 
689
887
  // packages/core/src/storage.ts
690
- function isRecord4(value) {
888
+ function isRecord5(value) {
691
889
  return typeof value === "object" && value !== null && !Array.isArray(value);
692
890
  }
693
891
  function nonEmptyString(value) {
@@ -698,7 +896,7 @@ function finiteNumber(value) {
698
896
  }
699
897
  function optionalErrorInfo(value) {
700
898
  if (value === void 0) return true;
701
- if (!isRecord4(value)) return false;
899
+ if (!isRecord5(value)) return false;
702
900
  if (typeof value.message !== "string") return false;
703
901
  if ("stack" in value && value.stack !== void 0) {
704
902
  if (typeof value.stack !== "string") return false;
@@ -706,7 +904,7 @@ function optionalErrorInfo(value) {
706
904
  return true;
707
905
  }
708
906
  function validateEvent(event) {
709
- if (!isRecord4(event)) return false;
907
+ if (!isRecord5(event)) return false;
710
908
  if (event.schemaVersion !== "0.1") return false;
711
909
  if (!finiteNumber(event.timestamp)) return false;
712
910
  if (typeof event.event !== "string") return false;
@@ -715,7 +913,7 @@ function validateEvent(event) {
715
913
  if (!nonEmptyString(event.runId) || !nonEmptyString(event.name) || !finiteNumber(event.startTime)) {
716
914
  return false;
717
915
  }
718
- if (event.metadata !== void 0 && !isRecord4(event.metadata)) {
916
+ if (event.metadata !== void 0 && !isRecord5(event.metadata)) {
719
917
  return false;
720
918
  }
721
919
  return true;
@@ -730,7 +928,7 @@ function validateEvent(event) {
730
928
  if (event.parentId !== void 0 && typeof event.parentId !== "string") {
731
929
  return false;
732
930
  }
733
- if (event.metadata !== void 0 && !isRecord4(event.metadata)) {
931
+ if (event.metadata !== void 0 && !isRecord5(event.metadata)) {
734
932
  return false;
735
933
  }
736
934
  return true;
@@ -952,6 +1150,129 @@ async function extractMetadata(filePath, _quickScan) {
952
1150
  createdAt: stats.birthtime
953
1151
  };
954
1152
  }
1153
+ function isNonNegativeFiniteNumber(value) {
1154
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1155
+ }
1156
+ function buildRunSummary(events) {
1157
+ const started = events.find(
1158
+ (e) => e.event === "run_started"
1159
+ );
1160
+ const completed = events.filter(
1161
+ (e) => e.event === "run_completed"
1162
+ );
1163
+ const lastCompleted = completed[completed.length - 1];
1164
+ const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
1165
+ const name = typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0;
1166
+ const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
1167
+ const durationMs = lastCompleted && isFiniteNumber(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
1168
+ started && isFiniteNumber(started.startTime) ? started.startTime : void 0;
1169
+ const steps = /* @__PURE__ */ new Map();
1170
+ for (const e of events) {
1171
+ if (e.event === "step_started") {
1172
+ const s = e;
1173
+ steps.set(s.stepId, {
1174
+ type: s.type,
1175
+ name: s.name,
1176
+ status: "running",
1177
+ parentId: s.parentId,
1178
+ tokensInput: isNonNegativeFiniteNumber(s.metadata?.tokens?.input) ? s.metadata.tokens.input : void 0,
1179
+ tokensOutput: isNonNegativeFiniteNumber(s.metadata?.tokens?.output) ? s.metadata.tokens.output : void 0,
1180
+ tokensTotal: isNonNegativeFiniteNumber(s.metadata?.tokens?.total) ? s.metadata.tokens.total : void 0,
1181
+ tokensCached: isNonNegativeFiniteNumber(s.metadata?.tokens?.cached) ? s.metadata.tokens.cached : void 0
1182
+ });
1183
+ }
1184
+ }
1185
+ for (const e of events) {
1186
+ if (e.event === "step_completed") {
1187
+ const c = e;
1188
+ const existing = steps.get(c.stepId);
1189
+ if (!existing) continue;
1190
+ existing.status = c.status;
1191
+ existing.durationMs = c.durationMs;
1192
+ }
1193
+ }
1194
+ let totalSteps = 0;
1195
+ let llmSteps = 0;
1196
+ let toolSteps = 0;
1197
+ let logicSteps = 0;
1198
+ let errorSteps = 0;
1199
+ let maxDepth = 0;
1200
+ let longestStep;
1201
+ let totalTokensInput = 0;
1202
+ let totalTokensOutput = 0;
1203
+ let totalTokensTotal = 0;
1204
+ let totalTokensCached = 0;
1205
+ let tokenBearingSteps = 0;
1206
+ let stepsWithKnownTotal = 0;
1207
+ let hasCachedTokens = false;
1208
+ const depthCache = /* @__PURE__ */ new Map();
1209
+ const computeDepth = (stepId) => {
1210
+ const cached = depthCache.get(stepId);
1211
+ if (cached !== void 0) return cached;
1212
+ const node = steps.get(stepId);
1213
+ if (!node) return 0;
1214
+ const parent = node.parentId;
1215
+ if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
1216
+ depthCache.set(stepId, 0);
1217
+ return 0;
1218
+ }
1219
+ const d = Math.min(1e3, computeDepth(parent) + 1);
1220
+ depthCache.set(stepId, d);
1221
+ return d;
1222
+ };
1223
+ for (const [id, s] of steps.entries()) {
1224
+ totalSteps += 1;
1225
+ if (s.type === "llm") llmSteps += 1;
1226
+ else if (s.type === "tool") toolSteps += 1;
1227
+ else logicSteps += 1;
1228
+ if (s.status === "error") errorSteps += 1;
1229
+ const depth = computeDepth(id);
1230
+ if (depth > maxDepth) maxDepth = depth;
1231
+ if (typeof s.durationMs === "number" && Number.isFinite(s.durationMs)) {
1232
+ if (!longestStep || s.durationMs > longestStep.durationMs) {
1233
+ longestStep = { name: s.name, durationMs: s.durationMs, type: s.type };
1234
+ }
1235
+ }
1236
+ if (s.tokensInput !== void 0 || s.tokensOutput !== void 0 || s.tokensTotal !== void 0 || s.tokensCached !== void 0) {
1237
+ tokenBearingSteps += 1;
1238
+ if (s.tokensInput !== void 0) totalTokensInput += s.tokensInput;
1239
+ if (s.tokensOutput !== void 0) totalTokensOutput += s.tokensOutput;
1240
+ if (s.tokensTotal !== void 0) {
1241
+ totalTokensTotal += s.tokensTotal;
1242
+ stepsWithKnownTotal += 1;
1243
+ } else if (s.tokensInput !== void 0 && s.tokensOutput !== void 0) {
1244
+ totalTokensTotal += s.tokensInput + s.tokensOutput;
1245
+ stepsWithKnownTotal += 1;
1246
+ }
1247
+ if (s.tokensCached !== void 0) {
1248
+ totalTokensCached += s.tokensCached;
1249
+ hasCachedTokens = true;
1250
+ }
1251
+ }
1252
+ }
1253
+ const summary = {
1254
+ runId,
1255
+ name,
1256
+ status,
1257
+ durationMs,
1258
+ totalSteps,
1259
+ llmSteps,
1260
+ toolSteps,
1261
+ logicSteps,
1262
+ errorSteps,
1263
+ maxDepth,
1264
+ ...longestStep ? { longestStep } : {},
1265
+ ...tokenBearingSteps > 0 ? {
1266
+ totalTokens: {
1267
+ input: totalTokensInput,
1268
+ output: totalTokensOutput,
1269
+ ...stepsWithKnownTotal === tokenBearingSteps ? { total: totalTokensTotal } : {},
1270
+ ...hasCachedTokens ? { cached: totalTokensCached } : {}
1271
+ }
1272
+ } : {}
1273
+ };
1274
+ return summary;
1275
+ }
955
1276
 
956
1277
  // packages/core/src/trace-filter.ts
957
1278
  function toLower(s) {
@@ -1110,6 +1431,145 @@ function buildRunTimeline(events, options = {}) {
1110
1431
  };
1111
1432
  }
1112
1433
 
1434
+ // packages/core/src/what.ts
1435
+ function pickCorrelation2(metadata) {
1436
+ if (!metadata) return void 0;
1437
+ const out = {};
1438
+ for (const key of [
1439
+ "correlationId",
1440
+ "requestId",
1441
+ "decisionId",
1442
+ "groupId"
1443
+ ]) {
1444
+ const value = metadata[key];
1445
+ if (typeof value === "string" && value.trim() !== "") {
1446
+ out[key] = value;
1447
+ }
1448
+ }
1449
+ return Object.keys(out).length > 0 ? out : void 0;
1450
+ }
1451
+ function stepMixLine(summary) {
1452
+ const parts = [];
1453
+ if (summary.llmSteps > 0) parts.push(`${summary.llmSteps} LLM`);
1454
+ if (summary.toolSteps > 0) parts.push(`${summary.toolSteps} tool`);
1455
+ if (summary.logicSteps > 0) parts.push(`${summary.logicSteps} logic`);
1456
+ return parts.length > 0 ? parts.join(", ") : "none";
1457
+ }
1458
+ function outcomeLine(summary) {
1459
+ if (summary.status === "success") {
1460
+ return summary.errorSteps > 0 ? "Completed with step errors recorded." : "Completed successfully.";
1461
+ }
1462
+ if (summary.status === "error") {
1463
+ if (summary.failedStepNames.length > 0) {
1464
+ const names = summary.failedStepNames.slice(0, 3).join(", ");
1465
+ const suffix = summary.failedStepNames.length > 3 ? ` (+${summary.failedStepNames.length - 3} more)` : "";
1466
+ return `Failed at step(s): ${names}${suffix}.`;
1467
+ }
1468
+ if (summary.runErrorMessage) {
1469
+ return `Run failed: ${summary.runErrorMessage}`;
1470
+ }
1471
+ return "Run failed.";
1472
+ }
1473
+ if (summary.status === "running") {
1474
+ return "Run is still in progress (no run_completed).";
1475
+ }
1476
+ return "Outcome unknown \u2014 inspect events may be incomplete.";
1477
+ }
1478
+ function buildRunWhatSummary(events) {
1479
+ const base = buildRunSummary(events);
1480
+ const started = events.find(
1481
+ (e) => e.event === "run_started"
1482
+ );
1483
+ const completed = events.filter(
1484
+ (e) => e.event === "run_completed"
1485
+ );
1486
+ const lastCompleted = completed[completed.length - 1];
1487
+ const failedStepNames = [];
1488
+ const stepNames = /* @__PURE__ */ new Map();
1489
+ for (const e of events) {
1490
+ if (e.event === "step_started") {
1491
+ const s = e;
1492
+ stepNames.set(s.stepId, s.name);
1493
+ }
1494
+ }
1495
+ for (const e of events) {
1496
+ if (e.event === "step_completed") {
1497
+ const sc = e;
1498
+ if (sc.status === "error") {
1499
+ failedStepNames.push(stepNames.get(sc.stepId) ?? sc.stepId);
1500
+ }
1501
+ }
1502
+ }
1503
+ return {
1504
+ runId: base.runId,
1505
+ name: base.name,
1506
+ status: base.status,
1507
+ durationMs: base.durationMs,
1508
+ totalSteps: base.totalSteps,
1509
+ llmSteps: base.llmSteps,
1510
+ toolSteps: base.toolSteps,
1511
+ logicSteps: base.logicSteps,
1512
+ errorSteps: base.errorSteps,
1513
+ maxDepth: base.maxDepth,
1514
+ longestStep: base.longestStep,
1515
+ totalTokens: base.totalTokens,
1516
+ correlation: pickCorrelation2(started?.metadata),
1517
+ failedStepNames,
1518
+ runErrorMessage: lastCompleted?.error?.message
1519
+ };
1520
+ }
1521
+ function renderRunWhat(summary, options = {}) {
1522
+ const showCorrelation = options.correlation !== false;
1523
+ const lines = [];
1524
+ const label = summary.name ?? summary.runId;
1525
+ lines.push(`What: ${label}`);
1526
+ const duration = summary.durationMs !== void 0 ? formatDuration2(summary.durationMs) : "\u2014";
1527
+ lines.push(
1528
+ `Status: ${summary.status} \xB7 Duration: ${duration} \xB7 Steps: ${summary.totalSteps} (${stepMixLine(summary)})`
1529
+ );
1530
+ if (summary.totalTokens) {
1531
+ const tokenParts = [
1532
+ `${summary.totalTokens.input} in`,
1533
+ `${summary.totalTokens.output} out`
1534
+ ];
1535
+ if (summary.totalTokens.total !== void 0) {
1536
+ tokenParts.push(`${summary.totalTokens.total} total`);
1537
+ }
1538
+ if (summary.totalTokens.cached !== void 0) {
1539
+ tokenParts.push(`${summary.totalTokens.cached} cached`);
1540
+ }
1541
+ lines.push(`Tokens: ${tokenParts.join(" / ")}`);
1542
+ }
1543
+ if (showCorrelation && summary.correlation) {
1544
+ const parts = [];
1545
+ if (summary.correlation.correlationId) {
1546
+ parts.push(`correlationId=${summary.correlation.correlationId}`);
1547
+ }
1548
+ if (summary.correlation.requestId) {
1549
+ parts.push(`requestId=${summary.correlation.requestId}`);
1550
+ }
1551
+ if (summary.correlation.decisionId) {
1552
+ parts.push(`decisionId=${summary.correlation.decisionId}`);
1553
+ }
1554
+ if (summary.correlation.groupId) {
1555
+ parts.push(`groupId=${summary.correlation.groupId}`);
1556
+ }
1557
+ if (parts.length > 0) {
1558
+ lines.push(`Correlation: ${parts.join(", ")}`);
1559
+ }
1560
+ }
1561
+ lines.push(`Outcome: ${outcomeLine(summary)}`);
1562
+ if (summary.longestStep && summary.totalSteps > 0) {
1563
+ lines.push(
1564
+ `Slowest: ${summary.longestStep.name} (${formatDuration2(summary.longestStep.durationMs)}, ${summary.longestStep.type})`
1565
+ );
1566
+ }
1567
+ if (summary.maxDepth > 0) {
1568
+ lines.push(`Max depth: ${summary.maxDepth}`);
1569
+ }
1570
+ return lines.join("\n");
1571
+ }
1572
+
1113
1573
  // packages/core/src/search.ts
1114
1574
  function parseDurationFilter(expr) {
1115
1575
  const raw = expr.trim();
@@ -1329,6 +1789,39 @@ async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
1329
1789
  return metas;
1330
1790
  }
1331
1791
 
1792
+ // packages/core/src/bundle/safety-status.ts
1793
+ function aggregateBundleSafeStatus(statuses) {
1794
+ if (statuses.length === 0) return "UNKNOWN";
1795
+ if (statuses.some((status) => status === "UNSAFE")) return "UNSAFE";
1796
+ if (statuses.some((status) => status === "UNKNOWN")) return "UNKNOWN";
1797
+ if (statuses.some((status) => status === "SAFE WITH WARNINGS")) return "SAFE WITH WARNINGS";
1798
+ return "SAFE";
1799
+ }
1800
+ function toMetadataSafeStatus(status) {
1801
+ if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
1802
+ return status;
1803
+ }
1804
+
1805
+ // packages/core/src/bundle/manifest.ts
1806
+ var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
1807
+ function buildBundleMetadata(parts) {
1808
+ const aggregate = aggregateBundleSafeStatus(
1809
+ parts.checks.runs.map((run) => run.status)
1810
+ );
1811
+ return {
1812
+ createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1813
+ agentInspectVersion: parts.agentInspectVersion,
1814
+ redactionProfile: parts.profile,
1815
+ sourceTraceCount: parts.resolve.runIds.length,
1816
+ runIds: [...parts.resolve.runIds],
1817
+ safeStatus: toMetadataSafeStatus(aggregate),
1818
+ files: [...parts.files].sort((a, b) => a.localeCompare(b)),
1819
+ note: BUNDLE_NOTE,
1820
+ ...parts.resolve.sessionId !== void 0 ? { sessionId: parts.resolve.sessionId } : {},
1821
+ ...parts.resolve.since !== void 0 ? { since: parts.resolve.since } : {}
1822
+ };
1823
+ }
1824
+
1332
1825
  // packages/core/src/checks/index.ts
1333
1826
  var SEVERITY_RANK = {
1334
1827
  error: 0,
@@ -1637,14 +2130,14 @@ function runTraceChecks(input, options = {}) {
1637
2130
  }
1638
2131
 
1639
2132
  // packages/core/src/persisted/token-usage.ts
1640
- function isRecord5(value) {
2133
+ function isRecord6(value) {
1641
2134
  return typeof value === "object" && value !== null && !Array.isArray(value);
1642
2135
  }
1643
2136
  function nonNegativeFinite(value) {
1644
2137
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1645
2138
  }
1646
2139
  function normalizeTokenUsage(value) {
1647
- if (!isRecord5(value)) return void 0;
2140
+ if (!isRecord6(value)) return void 0;
1648
2141
  const input = nonNegativeFinite(value.input);
1649
2142
  const output = nonNegativeFinite(value.output);
1650
2143
  const suppliedTotal = nonNegativeFinite(value.total);
@@ -2402,7 +2895,7 @@ function persistedEventsForParsedTrace(parsed) {
2402
2895
  sourceName: "agent-inspect-jsonl-reader"
2403
2896
  });
2404
2897
  }
2405
- function isRecord6(value) {
2898
+ function isRecord7(value) {
2406
2899
  return typeof value === "object" && value !== null && !Array.isArray(value);
2407
2900
  }
2408
2901
  function isNonEmptyString3(value) {
@@ -2417,13 +2910,13 @@ function readStringField(record, keys) {
2417
2910
  }
2418
2911
  function readRecordField(record, key) {
2419
2912
  const value = record[key];
2420
- return isRecord6(value) ? value : void 0;
2913
+ return isRecord7(value) ? value : void 0;
2421
2914
  }
2422
2915
  function parseJsonDocument(content) {
2423
2916
  return JSON.parse(content);
2424
2917
  }
2425
2918
  function looksLikeOpenInferenceSpan(value) {
2426
- if (!isRecord6(value)) return false;
2919
+ if (!isRecord7(value)) return false;
2427
2920
  const attributes = readRecordField(value, "attributes");
2428
2921
  return readStringField(value, ["trace_id", "traceId"]) !== void 0 && readStringField(value, ["span_id", "spanId"]) !== void 0 && (readStringField(value, ["name"]) !== void 0 || attributes?.["openinference.span.kind"] !== void 0);
2429
2922
  }
@@ -2448,7 +2941,7 @@ function extractOpenInferenceDocument(root) {
2448
2941
  unsupportedFields
2449
2942
  };
2450
2943
  }
2451
- if (!isRecord6(root)) return void 0;
2944
+ if (!isRecord7(root)) return void 0;
2452
2945
  const rootFormat = root.format;
2453
2946
  const rootCompatibility = root.compatibility;
2454
2947
  const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
@@ -2588,7 +3081,7 @@ function summarizeAttributeValue(value) {
2588
3081
  if (Array.isArray(value)) {
2589
3082
  return { type: "array", length: value.length };
2590
3083
  }
2591
- if (isRecord6(value)) {
3084
+ if (isRecord7(value)) {
2592
3085
  return { type: "object", keyCount: Object.keys(value).length };
2593
3086
  }
2594
3087
  if (value === null) {
@@ -2675,7 +3168,7 @@ function mapOpenInferenceKind(span, attributes, pathPrefix) {
2675
3168
  }
2676
3169
  }
2677
3170
  function mapOpenInferenceStatus(status) {
2678
- if (!isRecord6(status)) return void 0;
3171
+ if (!isRecord7(status)) return void 0;
2679
3172
  const rawCode = status.code;
2680
3173
  if (typeof rawCode !== "string") return void 0;
2681
3174
  switch (rawCode.toUpperCase()) {
@@ -2775,7 +3268,7 @@ function mapOpenInferenceSpan(span, index, version) {
2775
3268
  warnings.push(...kindWarnings);
2776
3269
  const status = mapOpenInferenceStatus(span.status);
2777
3270
  const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
2778
- const errorMessage = isRecord6(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3271
+ const errorMessage = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
2779
3272
  const event = {
2780
3273
  schemaVersion: "0.2",
2781
3274
  eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
@@ -2921,7 +3414,7 @@ var openInferenceJsonReader = {
2921
3414
  }
2922
3415
  };
2923
3416
  function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
2924
- if (!isRecord6(value)) {
3417
+ if (!isRecord7(value)) {
2925
3418
  unsupportedFields.push(field);
2926
3419
  warnings.push({
2927
3420
  code: "otlp_attribute_value_invalid",
@@ -2943,15 +3436,15 @@ function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
2943
3436
  if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
2944
3437
  return value.doubleValue;
2945
3438
  }
2946
- if (isRecord6(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
3439
+ if (isRecord7(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
2947
3440
  return value.arrayValue.values.map(
2948
3441
  (item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
2949
3442
  );
2950
3443
  }
2951
- if (isRecord6(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
3444
+ if (isRecord7(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
2952
3445
  const out = {};
2953
3446
  for (const [index, item] of value.kvlistValue.values.entries()) {
2954
- if (!isRecord6(item) || typeof item.key !== "string") {
3447
+ if (!isRecord7(item) || typeof item.key !== "string") {
2955
3448
  unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
2956
3449
  continue;
2957
3450
  }
@@ -3002,7 +3495,7 @@ function parseOtlpAttributes(value, pathPrefix) {
3002
3495
  }
3003
3496
  for (const [index, item] of value.entries()) {
3004
3497
  const field = `${pathPrefix}[${index}]`;
3005
- if (!isRecord6(item) || typeof item.key !== "string") {
3498
+ if (!isRecord7(item) || typeof item.key !== "string") {
3006
3499
  unsupportedFields.push(field);
3007
3500
  warnings.push({
3008
3501
  code: "otlp_attribute_invalid",
@@ -3025,16 +3518,16 @@ function parseOtlpAttributes(value, pathPrefix) {
3025
3518
  return { attributes, warnings, unsupportedFields };
3026
3519
  }
3027
3520
  function looksLikeOtlpSpan(value) {
3028
- return isRecord6(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
3521
+ return isRecord7(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
3029
3522
  }
3030
3523
  function extractOtlpDocument(root) {
3031
- if (!isRecord6(root) || !Array.isArray(root.resourceSpans)) return void 0;
3524
+ if (!isRecord7(root) || !Array.isArray(root.resourceSpans)) return void 0;
3032
3525
  const spans = [];
3033
3526
  const warnings = [];
3034
3527
  const unsupportedFields = [];
3035
3528
  for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
3036
3529
  const resourcePath = `resourceSpans[${resourceIndex}]`;
3037
- if (!isRecord6(resourceSpan)) {
3530
+ if (!isRecord7(resourceSpan)) {
3038
3531
  unsupportedFields.push(resourcePath);
3039
3532
  continue;
3040
3533
  }
@@ -3057,7 +3550,7 @@ function extractOtlpDocument(root) {
3057
3550
  }
3058
3551
  for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
3059
3552
  const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
3060
- if (!isRecord6(scopeSpan)) {
3553
+ if (!isRecord7(scopeSpan)) {
3061
3554
  unsupportedFields.push(scopePath);
3062
3555
  continue;
3063
3556
  }
@@ -3124,7 +3617,7 @@ function extractOtlpDocument(root) {
3124
3617
  };
3125
3618
  }
3126
3619
  function mapOtlpStatus(status) {
3127
- if (!isRecord6(status)) return void 0;
3620
+ if (!isRecord7(status)) return void 0;
3128
3621
  const rawCode = status.code;
3129
3622
  if (typeof rawCode !== "string") return void 0;
3130
3623
  switch (rawCode.toUpperCase()) {
@@ -3224,7 +3717,7 @@ function mapOtlpEvents(value, pathPrefix) {
3224
3717
  const events = [];
3225
3718
  for (const [index, event] of value.entries()) {
3226
3719
  const eventPath = `${pathPrefix}[${index}]`;
3227
- if (!isRecord6(event)) {
3720
+ if (!isRecord7(event)) {
3228
3721
  unsupportedFields.push(eventPath);
3229
3722
  continue;
3230
3723
  }
@@ -3362,7 +3855,7 @@ function mapOtlpSpan(context) {
3362
3855
  warnings.push(...kindWarnings);
3363
3856
  const status = mapOtlpStatus(span.status);
3364
3857
  const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
3365
- const errorMessage = isRecord6(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3858
+ const errorMessage = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3366
3859
  const event = {
3367
3860
  schemaVersion: "0.2",
3368
3861
  eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
@@ -3735,6 +4228,9 @@ function safeString(value, maxLength) {
3735
4228
  function escapeMarkdown(value) {
3736
4229
  return value.replace(/\|/g, "\\|").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, " ");
3737
4230
  }
4231
+ function escapeHtml(value) {
4232
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4233
+ }
3738
4234
  function sortKeysDeep(input) {
3739
4235
  if (input === null || typeof input !== "object") return input;
3740
4236
  if (Array.isArray(input)) return input.map(sortKeysDeep);
@@ -3752,14 +4248,15 @@ function stableJson(value, pretty) {
3752
4248
  function compactAttributes3(attrs, options) {
3753
4249
  if (attrs === void 0) return {};
3754
4250
  const maxLen = options?.maxLength ?? 500;
4251
+ const redacted = options?.redacted ?? true;
3755
4252
  const out = {};
3756
4253
  for (const key of Object.keys(attrs).sort()) {
3757
- if (shouldRedactKey(key)) {
4254
+ if (redacted && shouldRedactKey(key)) {
3758
4255
  out[key] = "[REDACTED]";
3759
4256
  continue;
3760
4257
  }
3761
4258
  const v = attrs[key];
3762
- out[key] = compactValue(v, maxLen);
4259
+ out[key] = compactValue(v, maxLen, redacted);
3763
4260
  }
3764
4261
  return out;
3765
4262
  }
@@ -3768,15 +4265,15 @@ function compactValue(value, maxLen, redacted) {
3768
4265
  return typeof value === "string" ? safeString(value, maxLen) : value;
3769
4266
  }
3770
4267
  if (Array.isArray(value)) {
3771
- const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen));
4268
+ const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen, redacted));
3772
4269
  if (value.length > 20) arr.push(`\u2026(+${value.length - 20} more)`);
3773
4270
  return arr;
3774
4271
  }
3775
4272
  const o = value;
3776
4273
  const inner = {};
3777
4274
  for (const k of Object.keys(o)) {
3778
- if (shouldRedactKey(k)) inner[k] = "[REDACTED]";
3779
- else inner[k] = compactValue(o[k], maxLen);
4275
+ if (redacted && shouldRedactKey(k)) inner[k] = "[REDACTED]";
4276
+ else inner[k] = compactValue(o[k], maxLen, redacted);
3780
4277
  }
3781
4278
  return inner;
3782
4279
  }
@@ -4150,6 +4647,274 @@ function diffRuns(left, right, options) {
4150
4647
  return { summary, differences };
4151
4648
  }
4152
4649
 
4650
+ // packages/core/src/exporters/types.ts
4651
+ var EXPORT_PAYLOAD_VERSION = "0.1.2";
4652
+
4653
+ // packages/core/src/exporters/redact-export.ts
4654
+ function isRecord8(value) {
4655
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4656
+ }
4657
+ function deepClone(value) {
4658
+ if (value === null || typeof value !== "object") {
4659
+ return value;
4660
+ }
4661
+ if (Array.isArray(value)) {
4662
+ return value.map((item) => deepClone(item));
4663
+ }
4664
+ const out = {};
4665
+ for (const [k, v] of Object.entries(value)) {
4666
+ out[k] = deepClone(v);
4667
+ }
4668
+ return out;
4669
+ }
4670
+ function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
4671
+ if (depth > 32) {
4672
+ return { truncated: true, reason: "maxDepth" };
4673
+ }
4674
+ const out = {};
4675
+ for (const [key, value] of Object.entries(record)) {
4676
+ out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
4677
+ }
4678
+ return out;
4679
+ }
4680
+ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
4681
+ if (value === null || typeof value !== "object") {
4682
+ if (typeof value === "string") {
4683
+ return truncateStringForProfile(
4684
+ value,
4685
+ key,
4686
+ maxMetadataValueLength,
4687
+ maxPreviewLength
4688
+ );
4689
+ }
4690
+ return value;
4691
+ }
4692
+ if (seen.has(value)) return "[Circular]";
4693
+ seen.add(value);
4694
+ if (Array.isArray(value)) {
4695
+ return value.slice(0, 50).map(
4696
+ (item, index) => boundValue(
4697
+ item,
4698
+ String(index),
4699
+ maxMetadataValueLength,
4700
+ maxPreviewLength,
4701
+ seen,
4702
+ depth + 1
4703
+ )
4704
+ );
4705
+ }
4706
+ return boundAttributeValues(
4707
+ value,
4708
+ maxMetadataValueLength,
4709
+ maxPreviewLength,
4710
+ seen,
4711
+ depth + 1
4712
+ );
4713
+ }
4714
+ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPreviewLength) {
4715
+ if (!attrs || Object.keys(attrs).length === 0) {
4716
+ return attrs;
4717
+ }
4718
+ const redacted = redactor.redactRecord(attrs);
4719
+ const seen = /* @__PURE__ */ new WeakSet();
4720
+ const bounded = boundAttributeValues(
4721
+ redacted,
4722
+ maxMetadataValueLength,
4723
+ maxPreviewLength,
4724
+ seen,
4725
+ 0
4726
+ );
4727
+ const err = bounded.error;
4728
+ if (isRecord8(err) && typeof err.message === "string") {
4729
+ bounded.error = {
4730
+ ...err,
4731
+ message: truncateStringForProfile(
4732
+ err.message,
4733
+ "message",
4734
+ maxMetadataValueLength,
4735
+ maxPreviewLength
4736
+ ),
4737
+ ...typeof err.stack === "string" ? {
4738
+ stack: truncateStringForProfile(
4739
+ err.stack,
4740
+ "stack",
4741
+ maxMetadataValueLength,
4742
+ maxPreviewLength
4743
+ )
4744
+ } : {}
4745
+ };
4746
+ }
4747
+ return bounded;
4748
+ }
4749
+ function redactRunTreeForExport(tree, options) {
4750
+ const profile = options?.redactionProfile ?? "local";
4751
+ if (profile === "local") {
4752
+ return deepClone(tree);
4753
+ }
4754
+ const resolved = resolveRedactionProfile(profile);
4755
+ const { maxMetadataValueLength, maxPreviewLength } = applyProfileMetadataCaps(
4756
+ 2e3,
4757
+ 500,
4758
+ resolved
4759
+ );
4760
+ const redactor = new Redactor({ extraKeys: resolved.extraKeys });
4761
+ const clone = deepClone(tree);
4762
+ function walk(nodes) {
4763
+ for (const node of nodes) {
4764
+ if (node.event.attributes !== void 0) {
4765
+ node.event.attributes = redactEventAttributes(
4766
+ node.event.attributes,
4767
+ redactor,
4768
+ maxMetadataValueLength,
4769
+ maxPreviewLength
4770
+ );
4771
+ }
4772
+ if (node.children.length > 0) {
4773
+ walk(node.children);
4774
+ }
4775
+ }
4776
+ }
4777
+ walk(clone.children);
4778
+ return clone;
4779
+ }
4780
+
4781
+ // packages/core/src/exporters/html-exporter.ts
4782
+ function renderTreeHtml(nodes, ulClass = "tree") {
4783
+ if (nodes.length === 0) return "";
4784
+ const parts = [`<ul class="${ulClass}">`];
4785
+ for (const n of nodes) {
4786
+ const ev = n.event;
4787
+ const status = ev.status ?? "?";
4788
+ const dur = ev.durationMs !== void 0 && Number.isFinite(ev.durationMs) ? `${ev.durationMs}ms` : "-";
4789
+ parts.push("<li>");
4790
+ parts.push(
4791
+ `<span class="nm">${escapeHtml(ev.name)}</span> <span class="meta">[${escapeHtml(ev.kind)}] ${escapeHtml(status)} (${escapeHtml(dur)})</span>`
4792
+ );
4793
+ if (n.children.length > 0) {
4794
+ parts.push(renderTreeHtml(n.children, "tree nested"));
4795
+ }
4796
+ parts.push("</li>");
4797
+ }
4798
+ parts.push("</ul>");
4799
+ return parts.join("");
4800
+ }
4801
+ function exportHtml(tree, options) {
4802
+ const warnings = [];
4803
+ const includeMetadata = options?.includeMetadata ?? true;
4804
+ const includeAttributes = options?.includeAttributes ?? false;
4805
+ const includeErrors = options?.includeErrors ?? true;
4806
+ const maxLen = options?.maxAttributeLength ?? 500;
4807
+ const redacted = options?.redacted;
4808
+ const titleName = escapeHtml(tree.name ?? tree.runId);
4809
+ const summaryRows = [];
4810
+ summaryRows.push(
4811
+ `<tr><th scope="row">runId</th><td><code>${escapeHtml(tree.runId)}</code></td></tr>`
4812
+ );
4813
+ if (tree.name !== void 0) {
4814
+ summaryRows.push(`<tr><th scope="row">name</th><td>${escapeHtml(tree.name)}</td></tr>`);
4815
+ }
4816
+ summaryRows.push(
4817
+ `<tr><th scope="row">status</th><td>${escapeHtml(String(tree.status ?? "unknown"))}</td></tr>`
4818
+ );
4819
+ summaryRows.push(
4820
+ `<tr><th scope="row">durationMs</th><td>${tree.durationMs !== void 0 ? escapeHtml(String(tree.durationMs)) : "\u2014"}</td></tr>`
4821
+ );
4822
+ summaryRows.push(
4823
+ `<tr><th scope="row">startedAt</th><td>${tree.startedAt !== void 0 ? escapeHtml(String(tree.startedAt)) : "\u2014"}</td></tr>`
4824
+ );
4825
+ summaryRows.push(
4826
+ `<tr><th scope="row">endedAt</th><td>${tree.endedAt !== void 0 ? escapeHtml(String(tree.endedAt)) : "\u2014"}</td></tr>`
4827
+ );
4828
+ summaryRows.push(
4829
+ `<tr><th scope="row">totalEvents</th><td>${escapeHtml(String(tree.metadata.totalEvents))}</td></tr>`
4830
+ );
4831
+ let confidenceHtml = "";
4832
+ if (includeMetadata) {
4833
+ const cb = tree.metadata.confidenceBreakdown;
4834
+ confidenceHtml += "<h3>Confidence breakdown</h3><table><thead><tr><th>bucket</th><th>count</th></tr></thead><tbody>";
4835
+ for (const k of Object.keys(cb).sort()) {
4836
+ const key = k;
4837
+ confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${cb[key]}</td></tr>`;
4838
+ }
4839
+ confidenceHtml += "</tbody></table>";
4840
+ confidenceHtml += "<h3>Kind breakdown</h3><table><thead><tr><th>kind</th><th>count</th></tr></thead><tbody>";
4841
+ for (const k of Object.keys(tree.metadata.kinds).sort()) {
4842
+ const key = k;
4843
+ const c = tree.metadata.kinds[key];
4844
+ if (c > 0) confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${c}</td></tr>`;
4845
+ }
4846
+ confidenceHtml += "</tbody></table>";
4847
+ }
4848
+ const flat = flattenTree(tree);
4849
+ const errors = flat.filter((n) => n.event.status === "error");
4850
+ let errorsHtml = "";
4851
+ if (includeErrors && errors.length > 0) {
4852
+ errorsHtml += "<h2>Errors</h2><ul>";
4853
+ for (const n of errors) {
4854
+ const msg = n.event.attributes && typeof n.event.attributes.error === "object" ? safeString(
4855
+ n.event.attributes.error.message,
4856
+ maxLen
4857
+ ) : "";
4858
+ errorsHtml += `<li><strong>${escapeHtml(n.event.name)}</strong> (${escapeHtml(n.event.eventId)}): ${escapeHtml(msg || "error")}</li>`;
4859
+ }
4860
+ errorsHtml += "</ul>";
4861
+ }
4862
+ let attrsHtml = "";
4863
+ if (includeAttributes) {
4864
+ attrsHtml += "<h2>Attributes (bounded)</h2>";
4865
+ for (const n of flat) {
4866
+ if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
4867
+ const compact = compactAttributes3(n.event.attributes, {
4868
+ maxLength: maxLen,
4869
+ redacted
4870
+ });
4871
+ attrsHtml += `<h3>${escapeHtml(n.event.name)}</h3><pre class="json">${escapeHtml(stableJson(compact, true))}</pre>`;
4872
+ }
4873
+ warnings.push(
4874
+ "Attributes may still contain sensitive data; review exports before sharing."
4875
+ );
4876
+ }
4877
+ const css = `
4878
+ body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}
4879
+ h1{font-size:1.35rem}
4880
+ h2{font-size:1.1rem;margin-top:1.5rem}
4881
+ table{border-collapse:collapse;margin:0.75rem 0}
4882
+ th,td{border:1px solid #ccc;padding:0.35rem 0.6rem;text-align:left}
4883
+ th{background:#f5f5f5}
4884
+ pre.json{background:#f8f8f8;padding:0.75rem;overflow:auto;font-size:0.85rem}
4885
+ ul.tree{list-style:none;padding-left:1rem}
4886
+ ul.tree.nested{padding-left:1.25rem;border-left:1px solid #ddd;margin:0.25rem 0}
4887
+ .nm{font-weight:600}
4888
+ .meta{color:#555;font-size:0.9rem}
4889
+ footer{margin-top:2rem;font-size:0.85rem;color:#555}
4890
+ `.trim();
4891
+ const html = `<!doctype html>
4892
+ <html lang="en">
4893
+ <head>
4894
+ <meta charset="utf-8"/>
4895
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
4896
+ <title>${titleName}</title>
4897
+ <style>${css}</style>
4898
+ </head>
4899
+ <body>
4900
+ <header><h1>AgentInspect Run: ${titleName}</h1></header>
4901
+ <p class="note">Generated locally by AgentInspect.</p>
4902
+ ${includeMetadata ? `<section class="summary"><h2>Summary</h2><table>${summaryRows.join("")}</table>${confidenceHtml}</section>` : ""}
4903
+ <section class="tree"><h2>Execution tree</h2>${tree.children.length > 0 ? renderTreeHtml(tree.children) : "<p>No steps recorded.</p>"}</section>
4904
+ ${errorsHtml}
4905
+ ${attrsHtml}
4906
+ <footer>Generated locally by AgentInspect. Review for sensitive data before sharing.</footer>
4907
+ </body>
4908
+ </html>`;
4909
+ return {
4910
+ format: "html",
4911
+ content: html,
4912
+ contentType: "text/html",
4913
+ fileExtension: ".html",
4914
+ warnings
4915
+ };
4916
+ }
4917
+
4153
4918
  // packages/core/src/exporters/markdown-exporter.ts
4154
4919
  function renderTreeAscii(nodes, indent = "") {
4155
4920
  const lines = [];
@@ -4175,6 +4940,7 @@ function exportMarkdown(tree, options) {
4175
4940
  const includeAttributes = options?.includeAttributes ?? false;
4176
4941
  const includeErrors = options?.includeErrors ?? true;
4177
4942
  const maxLen = options?.maxAttributeLength ?? 500;
4943
+ const redacted = options?.redacted ?? true;
4178
4944
  const titleName = tree.name ?? tree.runId;
4179
4945
  const lines = [];
4180
4946
  lines.push(`# AgentInspect Run: ${escapeMarkdown(titleName)}`);
@@ -4250,7 +5016,9 @@ function exportMarkdown(tree, options) {
4250
5016
  for (const n of flat) {
4251
5017
  if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
4252
5018
  const compact = compactAttributes3(n.event.attributes, {
4253
- maxLength: maxLen});
5019
+ maxLength: maxLen,
5020
+ redacted
5021
+ });
4254
5022
  lines.push(`### ${escapeMarkdown(n.event.name)}`);
4255
5023
  lines.push("");
4256
5024
  lines.push("```json");
@@ -4270,6 +5038,295 @@ function exportMarkdown(tree, options) {
4270
5038
  warnings
4271
5039
  };
4272
5040
  }
5041
+ function hexFrom(seed, byteLen) {
5042
+ return crypto.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
5043
+ }
5044
+ function mapInspectKindToOI(kind, warnings) {
5045
+ switch (kind) {
5046
+ case "LLM":
5047
+ return { openInferenceKind: "LLM" };
5048
+ case "TOOL":
5049
+ return { openInferenceKind: "TOOL" };
5050
+ case "CHAIN":
5051
+ return { openInferenceKind: "CHAIN" };
5052
+ case "RETRIEVER":
5053
+ return { openInferenceKind: "RETRIEVER" };
5054
+ case "AGENT":
5055
+ return { openInferenceKind: "AGENT" };
5056
+ case "DECISION":
5057
+ warnings.push(
5058
+ `Ambiguous kind DECISION mapped to CHAIN for span compatibility (${EXPORT_PAYLOAD_VERSION}).`
5059
+ );
5060
+ return { openInferenceKind: "CHAIN" };
5061
+ case "RESULT":
5062
+ warnings.push(
5063
+ `Ambiguous kind RESULT mapped to UNKNOWN for span compatibility (${EXPORT_PAYLOAD_VERSION}).`
5064
+ );
5065
+ return { openInferenceKind: "UNKNOWN" };
5066
+ case "ERROR":
5067
+ warnings.push(`ERROR kind mapped to CHAIN for span compatibility.`);
5068
+ return { openInferenceKind: "CHAIN" };
5069
+ case "LOG":
5070
+ case "LOGIC":
5071
+ case "RUN":
5072
+ warnings.push(`${kind} mapped to CHAIN for span compatibility.`);
5073
+ return { openInferenceKind: "CHAIN" };
5074
+ default:
5075
+ warnings.push(`Unhandled InspectKind ${kind} mapped to UNKNOWN.`);
5076
+ return { openInferenceKind: "UNKNOWN" };
5077
+ }
5078
+ }
5079
+ function exportOpenInference(tree, options) {
5080
+ const warnings = [
5081
+ "OpenInference-compatible JSON export is experimental until verified against specific backends.",
5082
+ "This file was generated locally and not sent anywhere."
5083
+ ];
5084
+ const traceId = hexFrom(`trace:${tree.runId}`, 16);
5085
+ const includeAttributes = options?.includeAttributes ?? false;
5086
+ const maxLen = options?.maxAttributeLength ?? 500;
5087
+ const pretty = options?.pretty ?? true;
5088
+ const spans = [];
5089
+ for (const n of flattenTree(tree)) {
5090
+ const ev = n.event;
5091
+ const spanId = hexFrom(`${tree.runId}:${ev.eventId}`, 8);
5092
+ const parentSpanHex = ev.parentId ? hexFrom(`${tree.runId}:${ev.parentId}`, 8) : void 0;
5093
+ const startNs = Math.round(ev.timestamp * 1e6);
5094
+ let endNs;
5095
+ if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
5096
+ endNs = startNs + Math.round(ev.durationMs * 1e6);
5097
+ }
5098
+ const { openInferenceKind } = mapInspectKindToOI(ev.kind, warnings);
5099
+ const attrs = {
5100
+ "openinference.span.kind": openInferenceKind,
5101
+ "agent_inspect.kind": ev.kind,
5102
+ "agent_inspect.confidence": ev.confidence,
5103
+ "agent_inspect.source.type": ev.source.type,
5104
+ "agent_inspect.run_id": tree.runId,
5105
+ "agent_inspect.event_id": ev.eventId,
5106
+ "agent_inspect.status": ev.status ?? "unset"
5107
+ };
5108
+ if (ev.durationMs !== void 0) {
5109
+ attrs["agent_inspect.duration_ms"] = ev.durationMs;
5110
+ }
5111
+ const meta = ev.attributes;
5112
+ if (meta?.model !== void 0 && typeof meta.model === "string") {
5113
+ attrs["llm.model_name"] = meta.model;
5114
+ }
5115
+ const tokens = meta?.tokens;
5116
+ if (tokens && typeof tokens === "object" && tokens !== null) {
5117
+ const inp = tokens.input;
5118
+ const outp = tokens.output;
5119
+ if (typeof inp === "number") attrs["llm.token_count.prompt"] = inp;
5120
+ if (typeof outp === "number") attrs["llm.token_count.completion"] = outp;
5121
+ }
5122
+ if (includeAttributes && meta && typeof meta === "object") {
5123
+ for (const [k, v] of Object.entries(meta)) {
5124
+ if (k === "tokens" || k === "model") continue;
5125
+ if (v !== void 0 && v !== null && typeof v !== "object") {
5126
+ attrs[`agent_inspect.preview.${k}`] = typeof v === "string" ? v.slice(0, maxLen) : v;
5127
+ }
5128
+ }
5129
+ }
5130
+ let status;
5131
+ if (ev.status === "error") {
5132
+ const msg = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error") : "error";
5133
+ status = { code: "ERROR", message: msg.slice(0, maxLen) };
5134
+ } else if (ev.status === "ok") {
5135
+ status = { code: "OK" };
5136
+ } else {
5137
+ status = { code: "UNSET" };
5138
+ }
5139
+ spans.push({
5140
+ trace_id: traceId,
5141
+ span_id: spanId,
5142
+ parent_span_id: parentSpanHex,
5143
+ name: ev.name,
5144
+ start_time_unix_nano: startNs,
5145
+ end_time_unix_nano: endNs,
5146
+ attributes: attrs,
5147
+ status
5148
+ });
5149
+ }
5150
+ const payload = {
5151
+ exporter: "agent-inspect",
5152
+ format: "openinference",
5153
+ compatibility: "openinference-compatible",
5154
+ version: EXPORT_PAYLOAD_VERSION,
5155
+ trace_id: traceId,
5156
+ spans,
5157
+ warnings
5158
+ };
5159
+ return {
5160
+ format: "openinference",
5161
+ content: JSON.stringify(payload, null, pretty ? 2 : void 0),
5162
+ contentType: "application/json",
5163
+ fileExtension: ".openinference.json",
5164
+ warnings
5165
+ };
5166
+ }
5167
+ function hexFrom2(seed, byteLen) {
5168
+ return crypto.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
5169
+ }
5170
+ function stringAttr(key, value) {
5171
+ return { key, value: { stringValue: value } };
5172
+ }
5173
+ function intAttr(key, value) {
5174
+ return { key, value: { intValue: String(value) } };
5175
+ }
5176
+ function genAiOperationName(kind) {
5177
+ switch (kind) {
5178
+ case "LLM":
5179
+ return "generate_content";
5180
+ case "TOOL":
5181
+ return "execute_tool";
5182
+ case "AGENT":
5183
+ return "invoke_agent";
5184
+ default:
5185
+ return void 0;
5186
+ }
5187
+ }
5188
+ function exportOtlpJson(tree, options) {
5189
+ const warnings = [
5190
+ "OTLP JSON export uses OTel GenAI-aligned attributes where applicable; experimental until verified against specific collectors.",
5191
+ "Not OTLP gRPC/protobuf \u2014 JSON mapping only. Generated locally; no network upload."
5192
+ ];
5193
+ const traceId = hexFrom2(`trace:${tree.runId}`, 16);
5194
+ const includeAttributes = options?.includeAttributes ?? false;
5195
+ const maxLen = options?.maxAttributeLength ?? 500;
5196
+ const pretty = options?.pretty ?? true;
5197
+ const flat = flattenTree(tree);
5198
+ const spans = [];
5199
+ for (const n of flat) {
5200
+ const ev = n.event;
5201
+ const spanId = hexFrom2(`${tree.runId}:${ev.eventId}`, 8);
5202
+ const parentSpanId = ev.parentId ? hexFrom2(`${tree.runId}:${ev.parentId}`, 8) : void 0;
5203
+ const startNs = String(Math.round(ev.timestamp * 1e6));
5204
+ let endNs;
5205
+ if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
5206
+ endNs = String(Math.round(ev.timestamp * 1e6 + ev.durationMs * 1e6));
5207
+ }
5208
+ const attrs = [
5209
+ stringAttr("agent_inspect.kind", ev.kind),
5210
+ stringAttr("agent_inspect.confidence", ev.confidence),
5211
+ stringAttr("agent_inspect.source.type", ev.source.type),
5212
+ stringAttr("agent_inspect.run_id", tree.runId),
5213
+ stringAttr("agent_inspect.event_id", ev.eventId),
5214
+ stringAttr("agent_inspect.status", ev.status ?? "unset")
5215
+ ];
5216
+ if (ev.durationMs !== void 0) {
5217
+ attrs.push(intAttr("agent_inspect.duration_ms", ev.durationMs));
5218
+ }
5219
+ const op = genAiOperationName(ev.kind);
5220
+ if (op !== void 0) {
5221
+ attrs.push(stringAttr("gen_ai.operation.name", op));
5222
+ }
5223
+ const meta = ev.attributes;
5224
+ if (meta?.model !== void 0 && typeof meta.model === "string") {
5225
+ attrs.push(stringAttr("gen_ai.request.model", meta.model.slice(0, maxLen)));
5226
+ }
5227
+ const tokens = meta?.tokens;
5228
+ if (tokens && typeof tokens === "object" && tokens !== null) {
5229
+ const inp = tokens.input;
5230
+ const outp = tokens.output;
5231
+ if (typeof inp === "number") attrs.push(intAttr("gen_ai.usage.input_tokens", inp));
5232
+ if (typeof outp === "number") attrs.push(intAttr("gen_ai.usage.output_tokens", outp));
5233
+ }
5234
+ if (includeAttributes && meta && typeof meta === "object") {
5235
+ for (const [k, v] of Object.entries(meta)) {
5236
+ if (k === "tokens" || k === "model") continue;
5237
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
5238
+ attrs.push(
5239
+ stringAttr(
5240
+ `agent_inspect.preview.${k}`,
5241
+ typeof v === "string" ? v.slice(0, maxLen) : String(v)
5242
+ )
5243
+ );
5244
+ }
5245
+ }
5246
+ }
5247
+ let statusCode = "STATUS_CODE_UNSET";
5248
+ let statusMessage;
5249
+ if (ev.status === "error") {
5250
+ statusCode = "STATUS_CODE_ERROR";
5251
+ statusMessage = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error").slice(0, maxLen) : "error";
5252
+ } else if (ev.status === "ok") {
5253
+ statusCode = "STATUS_CODE_OK";
5254
+ }
5255
+ const spanJson = {
5256
+ traceId,
5257
+ spanId,
5258
+ name: ev.name,
5259
+ kind: "SPAN_KIND_INTERNAL",
5260
+ startTimeUnixNano: startNs,
5261
+ attributes: attrs,
5262
+ status: {
5263
+ code: statusCode,
5264
+ ...statusMessage !== void 0 ? { message: statusMessage } : {}
5265
+ }
5266
+ };
5267
+ if (parentSpanId !== void 0) {
5268
+ spanJson.parentSpanId = parentSpanId;
5269
+ }
5270
+ if (endNs !== void 0) {
5271
+ spanJson.endTimeUnixNano = endNs;
5272
+ }
5273
+ spans.push(spanJson);
5274
+ }
5275
+ const payload = {
5276
+ resourceSpans: [
5277
+ {
5278
+ resource: {
5279
+ attributes: [stringAttr("service.name", "agent-inspect")]
5280
+ },
5281
+ scopeSpans: [
5282
+ {
5283
+ scope: { name: "agent-inspect" },
5284
+ spans
5285
+ }
5286
+ ]
5287
+ }
5288
+ ]
5289
+ };
5290
+ return {
5291
+ format: "otlp-json",
5292
+ content: JSON.stringify(payload, null, pretty ? 2 : void 0),
5293
+ contentType: "application/json",
5294
+ fileExtension: ".otlp.json",
5295
+ warnings
5296
+ };
5297
+ }
5298
+
5299
+ // packages/core/src/exporters/index.ts
5300
+ function mergeExportDefaults(options) {
5301
+ return {
5302
+ format: options.format,
5303
+ includeMetadata: options.includeMetadata ?? true,
5304
+ includeAttributes: options.includeAttributes ?? false,
5305
+ includeErrors: options.includeErrors ?? true,
5306
+ pretty: options.pretty ?? true,
5307
+ redacted: options.redacted,
5308
+ maxAttributeLength: options.maxAttributeLength ?? 500,
5309
+ redactionProfile: options.redactionProfile ?? "local"
5310
+ };
5311
+ }
5312
+ function exportRunTree(tree, options) {
5313
+ const opts = mergeExportDefaults(options);
5314
+ const exportTree = opts.redactionProfile === "local" ? tree : redactRunTreeForExport(tree, { redactionProfile: opts.redactionProfile });
5315
+ switch (opts.format) {
5316
+ case "markdown":
5317
+ return exportMarkdown(exportTree, opts);
5318
+ case "html":
5319
+ return exportHtml(exportTree, opts);
5320
+ case "openinference":
5321
+ return exportOpenInference(exportTree, opts);
5322
+ case "otlp-json":
5323
+ return exportOtlpJson(exportTree, opts);
5324
+ default: {
5325
+ const _x = opts.format;
5326
+ throw new Error(`Unsupported export format: ${String(_x)}`);
5327
+ }
5328
+ }
5329
+ }
4273
5330
 
4274
5331
  // packages/mcp-server/src/tools.ts
4275
5332
  var READ_ONLY_TOOLS = [
@@ -4343,6 +5400,42 @@ var READ_ONLY_TOOLS = [
4343
5400
  properties: { runId: { type: "string" } },
4344
5401
  required: ["runId"]
4345
5402
  }
5403
+ },
5404
+ {
5405
+ name: "summarize_failed_run",
5406
+ description: "Summarize a failed run with step errors and correlation metadata.",
5407
+ inputSchema: {
5408
+ type: "object",
5409
+ properties: { runId: { type: "string" } },
5410
+ required: ["runId"]
5411
+ }
5412
+ },
5413
+ {
5414
+ name: "retrieve_decision_notes",
5415
+ description: "List decision steps and decision metadata for one run.",
5416
+ inputSchema: {
5417
+ type: "object",
5418
+ properties: { runId: { type: "string" } },
5419
+ required: ["runId"]
5420
+ }
5421
+ },
5422
+ {
5423
+ name: "find_failed_observation",
5424
+ description: "Find failed observed outcomes in one run.",
5425
+ inputSchema: {
5426
+ type: "object",
5427
+ properties: { runId: { type: "string" } },
5428
+ required: ["runId"]
5429
+ }
5430
+ },
5431
+ {
5432
+ name: "create_share_safe_bundle",
5433
+ description: "Create an in-memory share-safe bundle manifest and redacted exports.",
5434
+ inputSchema: {
5435
+ type: "object",
5436
+ properties: { runId: { type: "string" } },
5437
+ required: ["runId"]
5438
+ }
4346
5439
  }
4347
5440
  ];
4348
5441
  function textResult(payload) {
@@ -4377,6 +5470,19 @@ async function openRunTrace(context, runId) {
4377
5470
  function legacyTraceEvents(events) {
4378
5471
  return persistedInspectEventsToTraceEvents(events);
4379
5472
  }
5473
+ function redactionProfileForExport(context) {
5474
+ return context.redactionProfile === "local" ? "share" : context.redactionProfile;
5475
+ }
5476
+ function decisionNotes(events) {
5477
+ return events.filter(
5478
+ (event) => event.kind === "DECISION" || typeof event.attributes?.decisionId === "string" && event.attributes.decisionId !== ""
5479
+ ).slice(0, 50).map((event) => ({
5480
+ name: event.name,
5481
+ kind: event.kind,
5482
+ status: event.status,
5483
+ decisionId: typeof event.attributes?.decisionId === "string" ? event.attributes.decisionId : void 0
5484
+ }));
5485
+ }
4380
5486
  async function callReadOnlyTool(context, name, args = {}) {
4381
5487
  switch (name) {
4382
5488
  case "list_traces": {
@@ -4477,10 +5583,73 @@ async function callReadOnlyTool(context, name, args = {}) {
4477
5583
  const { read } = await openRunTrace(context, runId);
4478
5584
  const run = read.runs.find((item) => item.runId === runId) ?? read.runs[0];
4479
5585
  if (!run) return errorResult2(`Run tree not found: ${runId}`);
5586
+ const profile = redactionProfileForExport(context);
4480
5587
  const markdown = exportMarkdown(run, {
4481
- redactionProfile: context.redactionProfile === "local" ? "share" : context.redactionProfile
5588
+ redacted: true});
5589
+ return textResult({ runId, profile, markdown: markdown.content });
5590
+ }
5591
+ case "summarize_failed_run": {
5592
+ const runId = String(args.runId ?? "");
5593
+ const { read } = await openRunTrace(context, runId);
5594
+ const traceEvents = legacyTraceEvents(read.events);
5595
+ const summary = buildRunWhatSummary(traceEvents);
5596
+ return textResult({
5597
+ runId,
5598
+ status: summary.status,
5599
+ summary: renderRunWhat(summary),
5600
+ failedStepNames: summary.failedStepNames,
5601
+ correlation: summary.correlation ?? null
5602
+ });
5603
+ }
5604
+ case "retrieve_decision_notes": {
5605
+ const runId = String(args.runId ?? "");
5606
+ const { read } = await openRunTrace(context, runId);
5607
+ const notes = decisionNotes(read.events);
5608
+ return textResult({ runId, decisions: notes, count: notes.length });
5609
+ }
5610
+ case "find_failed_observation": {
5611
+ const runId = String(args.runId ?? "");
5612
+ const { read } = await openRunTrace(context, runId);
5613
+ const outcomes = extractOutcomesFromTraceEvents(legacyTraceEvents(read.events));
5614
+ const failed = outcomes.filter((outcome) => outcome.status === "failed");
5615
+ return textResult({
5616
+ runId,
5617
+ failed,
5618
+ count: failed.length
5619
+ });
5620
+ }
5621
+ case "create_share_safe_bundle": {
5622
+ const runId = String(args.runId ?? "");
5623
+ const { read } = await openRunTrace(context, runId);
5624
+ const run = read.runs.find((item) => item.runId === runId) ?? read.runs[0];
5625
+ if (!run) return errorResult2(`Run tree not found: ${runId}`);
5626
+ const profile = redactionProfileForExport(context);
5627
+ const markdown = exportMarkdown(run, {
5628
+ redacted: true});
5629
+ const tree = exportRunTree(run, {
5630
+ format: "openinference",
5631
+ redacted: true,
5632
+ redactionProfile: profile
5633
+ });
5634
+ const metadata = buildBundleMetadata({
5635
+ agentInspectVersion: "mcp-server",
5636
+ profile,
5637
+ resolve: { runIds: [runId] },
5638
+ checks: {
5639
+ aggregateStatus: "SAFE",
5640
+ runs: [{ runId, status: "SAFE", errors: 0, warnings: 0, findings: 0 }]
5641
+ },
5642
+ files: ["report.md", "tree.json"]
5643
+ });
5644
+ return textResult({
5645
+ runId,
5646
+ profile,
5647
+ metadata,
5648
+ files: {
5649
+ "report.md": markdown.content,
5650
+ "tree.json": tree.content
5651
+ }
4482
5652
  });
4483
- return textResult({ runId, profile: context.redactionProfile, markdown: markdown.content });
4484
5653
  }
4485
5654
  default:
4486
5655
  return errorResult2(`Unknown tool: ${name}`);