@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.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  var readline = require('readline');
4
4
  var path = require('path');
5
5
  var async_hooks = require('async_hooks');
6
- require('crypto');
6
+ var crypto = require('crypto');
7
7
  var promises = require('fs/promises');
8
8
  var os = require('os');
9
9
  require('nanoid');
@@ -15,6 +15,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
15
 
16
16
  var readline__default = /*#__PURE__*/_interopDefault(readline);
17
17
  var path__default = /*#__PURE__*/_interopDefault(path);
18
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
18
19
  var os__default = /*#__PURE__*/_interopDefault(os);
19
20
 
20
21
  // packages/mcp-server/src/index.ts
@@ -44,6 +45,183 @@ function extractCorrelationMetadata(record) {
44
45
  }
45
46
  return found ? out : void 0;
46
47
  }
48
+ var DEFAULT_REDACT_KEYS = [
49
+ "authorization",
50
+ "cookie",
51
+ "token",
52
+ "apiKey",
53
+ "password",
54
+ "secret",
55
+ "email"
56
+ ];
57
+ function isRecord(v) {
58
+ return typeof v === "object" && v !== null && !Array.isArray(v);
59
+ }
60
+ function toKey(s) {
61
+ return s.toLowerCase();
62
+ }
63
+ function stableHash(value) {
64
+ const h = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
65
+ return h.slice(0, 8);
66
+ }
67
+ function compileRules(rules, extraKeys) {
68
+ const out = /* @__PURE__ */ new Map();
69
+ const set = (r) => {
70
+ const k = toKey(r.key);
71
+ out.set(k, { ...r, key: k });
72
+ };
73
+ for (const k of DEFAULT_REDACT_KEYS) {
74
+ set({ key: k, strategy: "full" });
75
+ }
76
+ for (const k of extraKeys ?? []) {
77
+ if (typeof k === "string" && k.length > 0) {
78
+ set({ key: k, strategy: "full" });
79
+ }
80
+ }
81
+ for (const r of rules ?? []) {
82
+ if (typeof r === "string") {
83
+ set({ key: r, strategy: "full" });
84
+ continue;
85
+ }
86
+ const key = r.key;
87
+ if (r.strategy === "full") set({ key, strategy: "full" });
88
+ if (r.strategy === "hash") set({ key, strategy: "hash" });
89
+ if (r.strategy === "prefix") {
90
+ set({ key, strategy: "prefix", keep: typeof r.keep === "number" ? r.keep : 8 });
91
+ }
92
+ }
93
+ return [...out.values()];
94
+ }
95
+ var Redactor = class {
96
+ #rules;
97
+ constructor(options) {
98
+ this.#rules = compileRules(options?.rules, options?.extraKeys);
99
+ }
100
+ redactValue(key, value) {
101
+ const k = toKey(key);
102
+ const rule = this.#rules.find((r) => r.key === k);
103
+ if (!rule) {
104
+ return this.#redactNested(value);
105
+ }
106
+ if (rule.strategy === "full") return "[REDACTED]";
107
+ const asString = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : void 0;
108
+ if (rule.strategy === "prefix") {
109
+ if (asString === void 0) return "[REDACTED]";
110
+ const keep = Math.max(0, Math.floor(rule.keep));
111
+ return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
112
+ }
113
+ if (rule.strategy === "hash") {
114
+ if (asString === void 0) return "[HASH:unknown]";
115
+ return `[HASH:${stableHash(asString)}]`;
116
+ }
117
+ return this.#redactNested(value);
118
+ }
119
+ redactRecord(record) {
120
+ const out = {};
121
+ for (const [k, v] of Object.entries(record)) {
122
+ out[k] = this.redactValue(k, v);
123
+ }
124
+ return out;
125
+ }
126
+ #redactNested(value) {
127
+ if (Array.isArray(value)) {
128
+ return value.map((v) => this.#redactNested(v));
129
+ }
130
+ if (isRecord(value)) {
131
+ const out = {};
132
+ for (const [k, v] of Object.entries(value)) {
133
+ out[k] = this.redactValue(k, v);
134
+ }
135
+ return out;
136
+ }
137
+ return value;
138
+ }
139
+ };
140
+
141
+ // packages/core/src/redaction-profiles.ts
142
+ var SHARE_PROFILE_EXTRA_KEYS = [
143
+ "userEmail",
144
+ "customerEmail",
145
+ "phone",
146
+ "phoneNumber",
147
+ "address",
148
+ "ip",
149
+ "ipAddress",
150
+ "sessionId",
151
+ "requestId",
152
+ "correlationId",
153
+ "decisionId",
154
+ "groupId",
155
+ "customerId",
156
+ "userId",
157
+ "accountId",
158
+ "tenantId",
159
+ "orgId",
160
+ "organizationId",
161
+ "traceId",
162
+ "spanId",
163
+ "parentSpanId"
164
+ ];
165
+ var STRICT_PROFILE_EXTRA_KEYS = [
166
+ "prompt",
167
+ "completion",
168
+ "input",
169
+ "output",
170
+ "inputPreview",
171
+ "outputPreview",
172
+ "message",
173
+ "messages",
174
+ "transcript",
175
+ "context",
176
+ "document",
177
+ "documents",
178
+ "chunk",
179
+ "chunks",
180
+ "retrieval",
181
+ "query"
182
+ ];
183
+ function resolveRedactionProfile(profile = "local") {
184
+ switch (profile) {
185
+ case "local":
186
+ return { profile: "local", extraKeys: [] };
187
+ case "share":
188
+ return {
189
+ profile: "share",
190
+ extraKeys: SHARE_PROFILE_EXTRA_KEYS,
191
+ maxMetadataValueLengthCap: 500,
192
+ maxPreviewLengthCap: 200
193
+ };
194
+ case "strict":
195
+ return {
196
+ profile: "strict",
197
+ extraKeys: [...SHARE_PROFILE_EXTRA_KEYS, ...STRICT_PROFILE_EXTRA_KEYS],
198
+ maxMetadataValueLengthCap: 200,
199
+ maxPreviewLengthCap: 80
200
+ };
201
+ default:
202
+ return { profile: "local", extraKeys: [] };
203
+ }
204
+ }
205
+ function isPreviewKey(key) {
206
+ return key.toLowerCase().includes("preview");
207
+ }
208
+ function applyProfileMetadataCaps(maxMetadataValueLength, maxPreviewLength, resolved) {
209
+ let meta = maxMetadataValueLength;
210
+ let preview = maxPreviewLength;
211
+ if (resolved.maxMetadataValueLengthCap !== void 0) {
212
+ meta = Math.min(meta, resolved.maxMetadataValueLengthCap);
213
+ }
214
+ if (resolved.maxPreviewLengthCap !== void 0) {
215
+ preview = Math.min(preview, resolved.maxPreviewLengthCap);
216
+ }
217
+ return { maxMetadataValueLength: meta, maxPreviewLength: preview };
218
+ }
219
+ function truncateStringForProfile(value, key, maxMetadataValueLength, maxPreviewLength) {
220
+ const max = isPreviewKey(key) ? maxPreviewLength : maxMetadataValueLength;
221
+ if (max <= 0) return "\u2026";
222
+ if (value.length <= max) return value;
223
+ return `${value.slice(0, max)}\u2026`;
224
+ }
47
225
 
48
226
  // packages/core/src/types.ts
49
227
  var STEP_TYPES = [
@@ -55,14 +233,14 @@ var STEP_TYPES = [
55
233
  "state",
56
234
  "custom"
57
235
  ];
58
- function isRecord(value) {
236
+ function isRecord2(value) {
59
237
  return typeof value === "object" && value !== null && !Array.isArray(value);
60
238
  }
61
239
  function isStepType(value) {
62
240
  return typeof value === "string" && STEP_TYPES.includes(value);
63
241
  }
64
242
  function isTraceEvent(value) {
65
- if (!isRecord(value)) return false;
243
+ if (!isRecord2(value)) return false;
66
244
  if (value.schemaVersion !== "0.1") return false;
67
245
  if (typeof value.timestamp !== "number") return false;
68
246
  if (typeof value.event !== "string") return false;
@@ -122,7 +300,7 @@ var PERSISTED_EVENT_STATUSES = [
122
300
  "error",
123
301
  "unknown"
124
302
  ];
125
- function isRecord2(value) {
303
+ function isRecord3(value) {
126
304
  return typeof value === "object" && value !== null && !Array.isArray(value);
127
305
  }
128
306
  function isString(value) {
@@ -153,21 +331,21 @@ function isPersistedEventStatus(value) {
153
331
  return typeof value === "string" && PERSISTED_EVENT_STATUSES.includes(value);
154
332
  }
155
333
  function isPersistedEventSource(value) {
156
- if (!isRecord2(value)) return false;
334
+ if (!isRecord3(value)) return false;
157
335
  if (!isPersistedEventSourceType(value.type)) return false;
158
336
  if (!isOptionalString(value.name)) return false;
159
337
  if (!isOptionalString(value.version)) return false;
160
338
  return true;
161
339
  }
162
340
  function isPersistedInspectError(value) {
163
- if (!isRecord2(value)) return false;
341
+ if (!isRecord3(value)) return false;
164
342
  if (!isNonEmptyString2(value.message)) return false;
165
343
  if (!isOptionalString(value.name)) return false;
166
344
  if (!isOptionalString(value.code)) return false;
167
345
  return true;
168
346
  }
169
347
  function isPersistedTokenUsage(value) {
170
- if (!isRecord2(value)) return false;
348
+ if (!isRecord3(value)) return false;
171
349
  if (!isOptionalNonNegativeNumber(value.input)) return false;
172
350
  if (!isOptionalNonNegativeNumber(value.output)) return false;
173
351
  if (!isOptionalNonNegativeNumber(value.total)) return false;
@@ -175,14 +353,14 @@ function isPersistedTokenUsage(value) {
175
353
  return true;
176
354
  }
177
355
  function isPersistedTraceContext(value) {
178
- if (!isRecord2(value)) return false;
356
+ if (!isRecord3(value)) return false;
179
357
  if (!isOptionalString(value.traceId)) return false;
180
358
  if (!isOptionalString(value.spanId)) return false;
181
359
  if (!isOptionalString(value.parentSpanId)) return false;
182
360
  return true;
183
361
  }
184
362
  function isPersistedInspectEvent(value) {
185
- if (!isRecord2(value)) return false;
363
+ if (!isRecord3(value)) return false;
186
364
  if (value.schemaVersion !== "0.2" && value.schemaVersion !== "1.0") {
187
365
  return false;
188
366
  }
@@ -204,7 +382,7 @@ function isPersistedInspectEvent(value) {
204
382
  if (value.durationMs !== void 0 && !isNonNegativeNumber(value.durationMs)) {
205
383
  return false;
206
384
  }
207
- if (value.attributes !== void 0 && !isRecord2(value.attributes)) {
385
+ if (value.attributes !== void 0 && !isRecord3(value.attributes)) {
208
386
  return false;
209
387
  }
210
388
  if (value.error !== void 0 && !isPersistedInspectError(value.error)) {
@@ -556,6 +734,24 @@ function parseDuration(duration) {
556
734
  }
557
735
  }
558
736
  }
737
+ function formatDuration(ms) {
738
+ if (!Number.isFinite(ms)) {
739
+ return "0ms";
740
+ }
741
+ if (ms < 0) {
742
+ throw new Error(`formatDuration: ms must be non-negative (got ${ms})`);
743
+ }
744
+ if (ms < 1e3) {
745
+ return `${Math.floor(ms)}ms`;
746
+ }
747
+ if (ms < 6e4) {
748
+ return `${(ms / 1e3).toFixed(2)}s`;
749
+ }
750
+ if (ms < 36e5) {
751
+ return `${(ms / 6e4).toFixed(1)}m`;
752
+ }
753
+ return `${(ms / 36e5).toFixed(1)}h`;
754
+ }
559
755
 
560
756
  // packages/core/src/utils.ts
561
757
  var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
@@ -565,6 +761,9 @@ var FALLBACK_TRACE_DIR = path__default.default.join(
565
761
  "agent-inspect",
566
762
  RUNS_DIR_NAME
567
763
  );
764
+ function formatDuration2(ms) {
765
+ return formatDuration(ms);
766
+ }
568
767
  function getDefaultTraceDir() {
569
768
  const envDir = process.env.AGENT_INSPECT_TRACE_DIR;
570
769
  if (typeof envDir === "string" && envDir.trim() !== "") {
@@ -619,11 +818,11 @@ function warn(message, error) {
619
818
  }
620
819
 
621
820
  // packages/core/src/read-trace.ts
622
- function isRecord3(value) {
821
+ function isRecord4(value) {
623
822
  return typeof value === "object" && value !== null && !Array.isArray(value);
624
823
  }
625
824
  function detectLineFormat(parsed) {
626
- if (!isRecord3(parsed)) return "unknown";
825
+ if (!isRecord4(parsed)) return "unknown";
627
826
  if (parsed.schemaVersion === "0.1") return "0.1";
628
827
  if (parsed.schemaVersion === "0.2") return "0.2";
629
828
  if (parsed.schemaVersion === "1.0") return "1.0";
@@ -695,7 +894,7 @@ function parseTraceJsonl(raw, options = {}) {
695
894
  }
696
895
 
697
896
  // packages/core/src/storage.ts
698
- function isRecord4(value) {
897
+ function isRecord5(value) {
699
898
  return typeof value === "object" && value !== null && !Array.isArray(value);
700
899
  }
701
900
  function nonEmptyString(value) {
@@ -706,7 +905,7 @@ function finiteNumber(value) {
706
905
  }
707
906
  function optionalErrorInfo(value) {
708
907
  if (value === void 0) return true;
709
- if (!isRecord4(value)) return false;
908
+ if (!isRecord5(value)) return false;
710
909
  if (typeof value.message !== "string") return false;
711
910
  if ("stack" in value && value.stack !== void 0) {
712
911
  if (typeof value.stack !== "string") return false;
@@ -714,7 +913,7 @@ function optionalErrorInfo(value) {
714
913
  return true;
715
914
  }
716
915
  function validateEvent(event) {
717
- if (!isRecord4(event)) return false;
916
+ if (!isRecord5(event)) return false;
718
917
  if (event.schemaVersion !== "0.1") return false;
719
918
  if (!finiteNumber(event.timestamp)) return false;
720
919
  if (typeof event.event !== "string") return false;
@@ -723,7 +922,7 @@ function validateEvent(event) {
723
922
  if (!nonEmptyString(event.runId) || !nonEmptyString(event.name) || !finiteNumber(event.startTime)) {
724
923
  return false;
725
924
  }
726
- if (event.metadata !== void 0 && !isRecord4(event.metadata)) {
925
+ if (event.metadata !== void 0 && !isRecord5(event.metadata)) {
727
926
  return false;
728
927
  }
729
928
  return true;
@@ -738,7 +937,7 @@ function validateEvent(event) {
738
937
  if (event.parentId !== void 0 && typeof event.parentId !== "string") {
739
938
  return false;
740
939
  }
741
- if (event.metadata !== void 0 && !isRecord4(event.metadata)) {
940
+ if (event.metadata !== void 0 && !isRecord5(event.metadata)) {
742
941
  return false;
743
942
  }
744
943
  return true;
@@ -960,6 +1159,129 @@ async function extractMetadata(filePath, _quickScan) {
960
1159
  createdAt: stats.birthtime
961
1160
  };
962
1161
  }
1162
+ function isNonNegativeFiniteNumber(value) {
1163
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1164
+ }
1165
+ function buildRunSummary(events) {
1166
+ const started = events.find(
1167
+ (e) => e.event === "run_started"
1168
+ );
1169
+ const completed = events.filter(
1170
+ (e) => e.event === "run_completed"
1171
+ );
1172
+ const lastCompleted = completed[completed.length - 1];
1173
+ const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
1174
+ const name = typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0;
1175
+ const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
1176
+ const durationMs = lastCompleted && isFiniteNumber(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
1177
+ started && isFiniteNumber(started.startTime) ? started.startTime : void 0;
1178
+ const steps = /* @__PURE__ */ new Map();
1179
+ for (const e of events) {
1180
+ if (e.event === "step_started") {
1181
+ const s = e;
1182
+ steps.set(s.stepId, {
1183
+ type: s.type,
1184
+ name: s.name,
1185
+ status: "running",
1186
+ parentId: s.parentId,
1187
+ tokensInput: isNonNegativeFiniteNumber(s.metadata?.tokens?.input) ? s.metadata.tokens.input : void 0,
1188
+ tokensOutput: isNonNegativeFiniteNumber(s.metadata?.tokens?.output) ? s.metadata.tokens.output : void 0,
1189
+ tokensTotal: isNonNegativeFiniteNumber(s.metadata?.tokens?.total) ? s.metadata.tokens.total : void 0,
1190
+ tokensCached: isNonNegativeFiniteNumber(s.metadata?.tokens?.cached) ? s.metadata.tokens.cached : void 0
1191
+ });
1192
+ }
1193
+ }
1194
+ for (const e of events) {
1195
+ if (e.event === "step_completed") {
1196
+ const c = e;
1197
+ const existing = steps.get(c.stepId);
1198
+ if (!existing) continue;
1199
+ existing.status = c.status;
1200
+ existing.durationMs = c.durationMs;
1201
+ }
1202
+ }
1203
+ let totalSteps = 0;
1204
+ let llmSteps = 0;
1205
+ let toolSteps = 0;
1206
+ let logicSteps = 0;
1207
+ let errorSteps = 0;
1208
+ let maxDepth = 0;
1209
+ let longestStep;
1210
+ let totalTokensInput = 0;
1211
+ let totalTokensOutput = 0;
1212
+ let totalTokensTotal = 0;
1213
+ let totalTokensCached = 0;
1214
+ let tokenBearingSteps = 0;
1215
+ let stepsWithKnownTotal = 0;
1216
+ let hasCachedTokens = false;
1217
+ const depthCache = /* @__PURE__ */ new Map();
1218
+ const computeDepth = (stepId) => {
1219
+ const cached = depthCache.get(stepId);
1220
+ if (cached !== void 0) return cached;
1221
+ const node = steps.get(stepId);
1222
+ if (!node) return 0;
1223
+ const parent = node.parentId;
1224
+ if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
1225
+ depthCache.set(stepId, 0);
1226
+ return 0;
1227
+ }
1228
+ const d = Math.min(1e3, computeDepth(parent) + 1);
1229
+ depthCache.set(stepId, d);
1230
+ return d;
1231
+ };
1232
+ for (const [id, s] of steps.entries()) {
1233
+ totalSteps += 1;
1234
+ if (s.type === "llm") llmSteps += 1;
1235
+ else if (s.type === "tool") toolSteps += 1;
1236
+ else logicSteps += 1;
1237
+ if (s.status === "error") errorSteps += 1;
1238
+ const depth = computeDepth(id);
1239
+ if (depth > maxDepth) maxDepth = depth;
1240
+ if (typeof s.durationMs === "number" && Number.isFinite(s.durationMs)) {
1241
+ if (!longestStep || s.durationMs > longestStep.durationMs) {
1242
+ longestStep = { name: s.name, durationMs: s.durationMs, type: s.type };
1243
+ }
1244
+ }
1245
+ if (s.tokensInput !== void 0 || s.tokensOutput !== void 0 || s.tokensTotal !== void 0 || s.tokensCached !== void 0) {
1246
+ tokenBearingSteps += 1;
1247
+ if (s.tokensInput !== void 0) totalTokensInput += s.tokensInput;
1248
+ if (s.tokensOutput !== void 0) totalTokensOutput += s.tokensOutput;
1249
+ if (s.tokensTotal !== void 0) {
1250
+ totalTokensTotal += s.tokensTotal;
1251
+ stepsWithKnownTotal += 1;
1252
+ } else if (s.tokensInput !== void 0 && s.tokensOutput !== void 0) {
1253
+ totalTokensTotal += s.tokensInput + s.tokensOutput;
1254
+ stepsWithKnownTotal += 1;
1255
+ }
1256
+ if (s.tokensCached !== void 0) {
1257
+ totalTokensCached += s.tokensCached;
1258
+ hasCachedTokens = true;
1259
+ }
1260
+ }
1261
+ }
1262
+ const summary = {
1263
+ runId,
1264
+ name,
1265
+ status,
1266
+ durationMs,
1267
+ totalSteps,
1268
+ llmSteps,
1269
+ toolSteps,
1270
+ logicSteps,
1271
+ errorSteps,
1272
+ maxDepth,
1273
+ ...longestStep ? { longestStep } : {},
1274
+ ...tokenBearingSteps > 0 ? {
1275
+ totalTokens: {
1276
+ input: totalTokensInput,
1277
+ output: totalTokensOutput,
1278
+ ...stepsWithKnownTotal === tokenBearingSteps ? { total: totalTokensTotal } : {},
1279
+ ...hasCachedTokens ? { cached: totalTokensCached } : {}
1280
+ }
1281
+ } : {}
1282
+ };
1283
+ return summary;
1284
+ }
963
1285
 
964
1286
  // packages/core/src/trace-filter.ts
965
1287
  function toLower(s) {
@@ -1118,6 +1440,145 @@ function buildRunTimeline(events, options = {}) {
1118
1440
  };
1119
1441
  }
1120
1442
 
1443
+ // packages/core/src/what.ts
1444
+ function pickCorrelation2(metadata) {
1445
+ if (!metadata) return void 0;
1446
+ const out = {};
1447
+ for (const key of [
1448
+ "correlationId",
1449
+ "requestId",
1450
+ "decisionId",
1451
+ "groupId"
1452
+ ]) {
1453
+ const value = metadata[key];
1454
+ if (typeof value === "string" && value.trim() !== "") {
1455
+ out[key] = value;
1456
+ }
1457
+ }
1458
+ return Object.keys(out).length > 0 ? out : void 0;
1459
+ }
1460
+ function stepMixLine(summary) {
1461
+ const parts = [];
1462
+ if (summary.llmSteps > 0) parts.push(`${summary.llmSteps} LLM`);
1463
+ if (summary.toolSteps > 0) parts.push(`${summary.toolSteps} tool`);
1464
+ if (summary.logicSteps > 0) parts.push(`${summary.logicSteps} logic`);
1465
+ return parts.length > 0 ? parts.join(", ") : "none";
1466
+ }
1467
+ function outcomeLine(summary) {
1468
+ if (summary.status === "success") {
1469
+ return summary.errorSteps > 0 ? "Completed with step errors recorded." : "Completed successfully.";
1470
+ }
1471
+ if (summary.status === "error") {
1472
+ if (summary.failedStepNames.length > 0) {
1473
+ const names = summary.failedStepNames.slice(0, 3).join(", ");
1474
+ const suffix = summary.failedStepNames.length > 3 ? ` (+${summary.failedStepNames.length - 3} more)` : "";
1475
+ return `Failed at step(s): ${names}${suffix}.`;
1476
+ }
1477
+ if (summary.runErrorMessage) {
1478
+ return `Run failed: ${summary.runErrorMessage}`;
1479
+ }
1480
+ return "Run failed.";
1481
+ }
1482
+ if (summary.status === "running") {
1483
+ return "Run is still in progress (no run_completed).";
1484
+ }
1485
+ return "Outcome unknown \u2014 inspect events may be incomplete.";
1486
+ }
1487
+ function buildRunWhatSummary(events) {
1488
+ const base = buildRunSummary(events);
1489
+ const started = events.find(
1490
+ (e) => e.event === "run_started"
1491
+ );
1492
+ const completed = events.filter(
1493
+ (e) => e.event === "run_completed"
1494
+ );
1495
+ const lastCompleted = completed[completed.length - 1];
1496
+ const failedStepNames = [];
1497
+ const stepNames = /* @__PURE__ */ new Map();
1498
+ for (const e of events) {
1499
+ if (e.event === "step_started") {
1500
+ const s = e;
1501
+ stepNames.set(s.stepId, s.name);
1502
+ }
1503
+ }
1504
+ for (const e of events) {
1505
+ if (e.event === "step_completed") {
1506
+ const sc = e;
1507
+ if (sc.status === "error") {
1508
+ failedStepNames.push(stepNames.get(sc.stepId) ?? sc.stepId);
1509
+ }
1510
+ }
1511
+ }
1512
+ return {
1513
+ runId: base.runId,
1514
+ name: base.name,
1515
+ status: base.status,
1516
+ durationMs: base.durationMs,
1517
+ totalSteps: base.totalSteps,
1518
+ llmSteps: base.llmSteps,
1519
+ toolSteps: base.toolSteps,
1520
+ logicSteps: base.logicSteps,
1521
+ errorSteps: base.errorSteps,
1522
+ maxDepth: base.maxDepth,
1523
+ longestStep: base.longestStep,
1524
+ totalTokens: base.totalTokens,
1525
+ correlation: pickCorrelation2(started?.metadata),
1526
+ failedStepNames,
1527
+ runErrorMessage: lastCompleted?.error?.message
1528
+ };
1529
+ }
1530
+ function renderRunWhat(summary, options = {}) {
1531
+ const showCorrelation = options.correlation !== false;
1532
+ const lines = [];
1533
+ const label = summary.name ?? summary.runId;
1534
+ lines.push(`What: ${label}`);
1535
+ const duration = summary.durationMs !== void 0 ? formatDuration2(summary.durationMs) : "\u2014";
1536
+ lines.push(
1537
+ `Status: ${summary.status} \xB7 Duration: ${duration} \xB7 Steps: ${summary.totalSteps} (${stepMixLine(summary)})`
1538
+ );
1539
+ if (summary.totalTokens) {
1540
+ const tokenParts = [
1541
+ `${summary.totalTokens.input} in`,
1542
+ `${summary.totalTokens.output} out`
1543
+ ];
1544
+ if (summary.totalTokens.total !== void 0) {
1545
+ tokenParts.push(`${summary.totalTokens.total} total`);
1546
+ }
1547
+ if (summary.totalTokens.cached !== void 0) {
1548
+ tokenParts.push(`${summary.totalTokens.cached} cached`);
1549
+ }
1550
+ lines.push(`Tokens: ${tokenParts.join(" / ")}`);
1551
+ }
1552
+ if (showCorrelation && summary.correlation) {
1553
+ const parts = [];
1554
+ if (summary.correlation.correlationId) {
1555
+ parts.push(`correlationId=${summary.correlation.correlationId}`);
1556
+ }
1557
+ if (summary.correlation.requestId) {
1558
+ parts.push(`requestId=${summary.correlation.requestId}`);
1559
+ }
1560
+ if (summary.correlation.decisionId) {
1561
+ parts.push(`decisionId=${summary.correlation.decisionId}`);
1562
+ }
1563
+ if (summary.correlation.groupId) {
1564
+ parts.push(`groupId=${summary.correlation.groupId}`);
1565
+ }
1566
+ if (parts.length > 0) {
1567
+ lines.push(`Correlation: ${parts.join(", ")}`);
1568
+ }
1569
+ }
1570
+ lines.push(`Outcome: ${outcomeLine(summary)}`);
1571
+ if (summary.longestStep && summary.totalSteps > 0) {
1572
+ lines.push(
1573
+ `Slowest: ${summary.longestStep.name} (${formatDuration2(summary.longestStep.durationMs)}, ${summary.longestStep.type})`
1574
+ );
1575
+ }
1576
+ if (summary.maxDepth > 0) {
1577
+ lines.push(`Max depth: ${summary.maxDepth}`);
1578
+ }
1579
+ return lines.join("\n");
1580
+ }
1581
+
1121
1582
  // packages/core/src/search.ts
1122
1583
  function parseDurationFilter(expr) {
1123
1584
  const raw = expr.trim();
@@ -1337,6 +1798,39 @@ async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
1337
1798
  return metas;
1338
1799
  }
1339
1800
 
1801
+ // packages/core/src/bundle/safety-status.ts
1802
+ function aggregateBundleSafeStatus(statuses) {
1803
+ if (statuses.length === 0) return "UNKNOWN";
1804
+ if (statuses.some((status) => status === "UNSAFE")) return "UNSAFE";
1805
+ if (statuses.some((status) => status === "UNKNOWN")) return "UNKNOWN";
1806
+ if (statuses.some((status) => status === "SAFE WITH WARNINGS")) return "SAFE WITH WARNINGS";
1807
+ return "SAFE";
1808
+ }
1809
+ function toMetadataSafeStatus(status) {
1810
+ if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
1811
+ return status;
1812
+ }
1813
+
1814
+ // packages/core/src/bundle/manifest.ts
1815
+ var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
1816
+ function buildBundleMetadata(parts) {
1817
+ const aggregate = aggregateBundleSafeStatus(
1818
+ parts.checks.runs.map((run) => run.status)
1819
+ );
1820
+ return {
1821
+ createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1822
+ agentInspectVersion: parts.agentInspectVersion,
1823
+ redactionProfile: parts.profile,
1824
+ sourceTraceCount: parts.resolve.runIds.length,
1825
+ runIds: [...parts.resolve.runIds],
1826
+ safeStatus: toMetadataSafeStatus(aggregate),
1827
+ files: [...parts.files].sort((a, b) => a.localeCompare(b)),
1828
+ note: BUNDLE_NOTE,
1829
+ ...parts.resolve.sessionId !== void 0 ? { sessionId: parts.resolve.sessionId } : {},
1830
+ ...parts.resolve.since !== void 0 ? { since: parts.resolve.since } : {}
1831
+ };
1832
+ }
1833
+
1340
1834
  // packages/core/src/checks/index.ts
1341
1835
  var SEVERITY_RANK = {
1342
1836
  error: 0,
@@ -1645,14 +2139,14 @@ function runTraceChecks(input, options = {}) {
1645
2139
  }
1646
2140
 
1647
2141
  // packages/core/src/persisted/token-usage.ts
1648
- function isRecord5(value) {
2142
+ function isRecord6(value) {
1649
2143
  return typeof value === "object" && value !== null && !Array.isArray(value);
1650
2144
  }
1651
2145
  function nonNegativeFinite(value) {
1652
2146
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1653
2147
  }
1654
2148
  function normalizeTokenUsage(value) {
1655
- if (!isRecord5(value)) return void 0;
2149
+ if (!isRecord6(value)) return void 0;
1656
2150
  const input = nonNegativeFinite(value.input);
1657
2151
  const output = nonNegativeFinite(value.output);
1658
2152
  const suppliedTotal = nonNegativeFinite(value.total);
@@ -2410,7 +2904,7 @@ function persistedEventsForParsedTrace(parsed) {
2410
2904
  sourceName: "agent-inspect-jsonl-reader"
2411
2905
  });
2412
2906
  }
2413
- function isRecord6(value) {
2907
+ function isRecord7(value) {
2414
2908
  return typeof value === "object" && value !== null && !Array.isArray(value);
2415
2909
  }
2416
2910
  function isNonEmptyString3(value) {
@@ -2425,13 +2919,13 @@ function readStringField(record, keys) {
2425
2919
  }
2426
2920
  function readRecordField(record, key) {
2427
2921
  const value = record[key];
2428
- return isRecord6(value) ? value : void 0;
2922
+ return isRecord7(value) ? value : void 0;
2429
2923
  }
2430
2924
  function parseJsonDocument(content) {
2431
2925
  return JSON.parse(content);
2432
2926
  }
2433
2927
  function looksLikeOpenInferenceSpan(value) {
2434
- if (!isRecord6(value)) return false;
2928
+ if (!isRecord7(value)) return false;
2435
2929
  const attributes = readRecordField(value, "attributes");
2436
2930
  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);
2437
2931
  }
@@ -2456,7 +2950,7 @@ function extractOpenInferenceDocument(root) {
2456
2950
  unsupportedFields
2457
2951
  };
2458
2952
  }
2459
- if (!isRecord6(root)) return void 0;
2953
+ if (!isRecord7(root)) return void 0;
2460
2954
  const rootFormat = root.format;
2461
2955
  const rootCompatibility = root.compatibility;
2462
2956
  const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
@@ -2596,7 +3090,7 @@ function summarizeAttributeValue(value) {
2596
3090
  if (Array.isArray(value)) {
2597
3091
  return { type: "array", length: value.length };
2598
3092
  }
2599
- if (isRecord6(value)) {
3093
+ if (isRecord7(value)) {
2600
3094
  return { type: "object", keyCount: Object.keys(value).length };
2601
3095
  }
2602
3096
  if (value === null) {
@@ -2683,7 +3177,7 @@ function mapOpenInferenceKind(span, attributes, pathPrefix) {
2683
3177
  }
2684
3178
  }
2685
3179
  function mapOpenInferenceStatus(status) {
2686
- if (!isRecord6(status)) return void 0;
3180
+ if (!isRecord7(status)) return void 0;
2687
3181
  const rawCode = status.code;
2688
3182
  if (typeof rawCode !== "string") return void 0;
2689
3183
  switch (rawCode.toUpperCase()) {
@@ -2783,7 +3277,7 @@ function mapOpenInferenceSpan(span, index, version) {
2783
3277
  warnings.push(...kindWarnings);
2784
3278
  const status = mapOpenInferenceStatus(span.status);
2785
3279
  const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
2786
- const errorMessage = isRecord6(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3280
+ const errorMessage = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
2787
3281
  const event = {
2788
3282
  schemaVersion: "0.2",
2789
3283
  eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
@@ -2929,7 +3423,7 @@ var openInferenceJsonReader = {
2929
3423
  }
2930
3424
  };
2931
3425
  function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
2932
- if (!isRecord6(value)) {
3426
+ if (!isRecord7(value)) {
2933
3427
  unsupportedFields.push(field);
2934
3428
  warnings.push({
2935
3429
  code: "otlp_attribute_value_invalid",
@@ -2951,15 +3445,15 @@ function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
2951
3445
  if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
2952
3446
  return value.doubleValue;
2953
3447
  }
2954
- if (isRecord6(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
3448
+ if (isRecord7(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
2955
3449
  return value.arrayValue.values.map(
2956
3450
  (item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
2957
3451
  );
2958
3452
  }
2959
- if (isRecord6(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
3453
+ if (isRecord7(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
2960
3454
  const out = {};
2961
3455
  for (const [index, item] of value.kvlistValue.values.entries()) {
2962
- if (!isRecord6(item) || typeof item.key !== "string") {
3456
+ if (!isRecord7(item) || typeof item.key !== "string") {
2963
3457
  unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
2964
3458
  continue;
2965
3459
  }
@@ -3010,7 +3504,7 @@ function parseOtlpAttributes(value, pathPrefix) {
3010
3504
  }
3011
3505
  for (const [index, item] of value.entries()) {
3012
3506
  const field = `${pathPrefix}[${index}]`;
3013
- if (!isRecord6(item) || typeof item.key !== "string") {
3507
+ if (!isRecord7(item) || typeof item.key !== "string") {
3014
3508
  unsupportedFields.push(field);
3015
3509
  warnings.push({
3016
3510
  code: "otlp_attribute_invalid",
@@ -3033,16 +3527,16 @@ function parseOtlpAttributes(value, pathPrefix) {
3033
3527
  return { attributes, warnings, unsupportedFields };
3034
3528
  }
3035
3529
  function looksLikeOtlpSpan(value) {
3036
- return isRecord6(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
3530
+ return isRecord7(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
3037
3531
  }
3038
3532
  function extractOtlpDocument(root) {
3039
- if (!isRecord6(root) || !Array.isArray(root.resourceSpans)) return void 0;
3533
+ if (!isRecord7(root) || !Array.isArray(root.resourceSpans)) return void 0;
3040
3534
  const spans = [];
3041
3535
  const warnings = [];
3042
3536
  const unsupportedFields = [];
3043
3537
  for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
3044
3538
  const resourcePath = `resourceSpans[${resourceIndex}]`;
3045
- if (!isRecord6(resourceSpan)) {
3539
+ if (!isRecord7(resourceSpan)) {
3046
3540
  unsupportedFields.push(resourcePath);
3047
3541
  continue;
3048
3542
  }
@@ -3065,7 +3559,7 @@ function extractOtlpDocument(root) {
3065
3559
  }
3066
3560
  for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
3067
3561
  const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
3068
- if (!isRecord6(scopeSpan)) {
3562
+ if (!isRecord7(scopeSpan)) {
3069
3563
  unsupportedFields.push(scopePath);
3070
3564
  continue;
3071
3565
  }
@@ -3132,7 +3626,7 @@ function extractOtlpDocument(root) {
3132
3626
  };
3133
3627
  }
3134
3628
  function mapOtlpStatus(status) {
3135
- if (!isRecord6(status)) return void 0;
3629
+ if (!isRecord7(status)) return void 0;
3136
3630
  const rawCode = status.code;
3137
3631
  if (typeof rawCode !== "string") return void 0;
3138
3632
  switch (rawCode.toUpperCase()) {
@@ -3232,7 +3726,7 @@ function mapOtlpEvents(value, pathPrefix) {
3232
3726
  const events = [];
3233
3727
  for (const [index, event] of value.entries()) {
3234
3728
  const eventPath = `${pathPrefix}[${index}]`;
3235
- if (!isRecord6(event)) {
3729
+ if (!isRecord7(event)) {
3236
3730
  unsupportedFields.push(eventPath);
3237
3731
  continue;
3238
3732
  }
@@ -3370,7 +3864,7 @@ function mapOtlpSpan(context) {
3370
3864
  warnings.push(...kindWarnings);
3371
3865
  const status = mapOtlpStatus(span.status);
3372
3866
  const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
3373
- const errorMessage = isRecord6(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3867
+ const errorMessage = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3374
3868
  const event = {
3375
3869
  schemaVersion: "0.2",
3376
3870
  eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
@@ -3743,6 +4237,9 @@ function safeString(value, maxLength) {
3743
4237
  function escapeMarkdown(value) {
3744
4238
  return value.replace(/\|/g, "\\|").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, " ");
3745
4239
  }
4240
+ function escapeHtml(value) {
4241
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4242
+ }
3746
4243
  function sortKeysDeep(input) {
3747
4244
  if (input === null || typeof input !== "object") return input;
3748
4245
  if (Array.isArray(input)) return input.map(sortKeysDeep);
@@ -3760,14 +4257,15 @@ function stableJson(value, pretty) {
3760
4257
  function compactAttributes3(attrs, options) {
3761
4258
  if (attrs === void 0) return {};
3762
4259
  const maxLen = options?.maxLength ?? 500;
4260
+ const redacted = options?.redacted ?? true;
3763
4261
  const out = {};
3764
4262
  for (const key of Object.keys(attrs).sort()) {
3765
- if (shouldRedactKey(key)) {
4263
+ if (redacted && shouldRedactKey(key)) {
3766
4264
  out[key] = "[REDACTED]";
3767
4265
  continue;
3768
4266
  }
3769
4267
  const v = attrs[key];
3770
- out[key] = compactValue(v, maxLen);
4268
+ out[key] = compactValue(v, maxLen, redacted);
3771
4269
  }
3772
4270
  return out;
3773
4271
  }
@@ -3776,15 +4274,15 @@ function compactValue(value, maxLen, redacted) {
3776
4274
  return typeof value === "string" ? safeString(value, maxLen) : value;
3777
4275
  }
3778
4276
  if (Array.isArray(value)) {
3779
- const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen));
4277
+ const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen, redacted));
3780
4278
  if (value.length > 20) arr.push(`\u2026(+${value.length - 20} more)`);
3781
4279
  return arr;
3782
4280
  }
3783
4281
  const o = value;
3784
4282
  const inner = {};
3785
4283
  for (const k of Object.keys(o)) {
3786
- if (shouldRedactKey(k)) inner[k] = "[REDACTED]";
3787
- else inner[k] = compactValue(o[k], maxLen);
4284
+ if (redacted && shouldRedactKey(k)) inner[k] = "[REDACTED]";
4285
+ else inner[k] = compactValue(o[k], maxLen, redacted);
3788
4286
  }
3789
4287
  return inner;
3790
4288
  }
@@ -4158,6 +4656,274 @@ function diffRuns(left, right, options) {
4158
4656
  return { summary, differences };
4159
4657
  }
4160
4658
 
4659
+ // packages/core/src/exporters/types.ts
4660
+ var EXPORT_PAYLOAD_VERSION = "0.1.2";
4661
+
4662
+ // packages/core/src/exporters/redact-export.ts
4663
+ function isRecord8(value) {
4664
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4665
+ }
4666
+ function deepClone(value) {
4667
+ if (value === null || typeof value !== "object") {
4668
+ return value;
4669
+ }
4670
+ if (Array.isArray(value)) {
4671
+ return value.map((item) => deepClone(item));
4672
+ }
4673
+ const out = {};
4674
+ for (const [k, v] of Object.entries(value)) {
4675
+ out[k] = deepClone(v);
4676
+ }
4677
+ return out;
4678
+ }
4679
+ function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
4680
+ if (depth > 32) {
4681
+ return { truncated: true, reason: "maxDepth" };
4682
+ }
4683
+ const out = {};
4684
+ for (const [key, value] of Object.entries(record)) {
4685
+ out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
4686
+ }
4687
+ return out;
4688
+ }
4689
+ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
4690
+ if (value === null || typeof value !== "object") {
4691
+ if (typeof value === "string") {
4692
+ return truncateStringForProfile(
4693
+ value,
4694
+ key,
4695
+ maxMetadataValueLength,
4696
+ maxPreviewLength
4697
+ );
4698
+ }
4699
+ return value;
4700
+ }
4701
+ if (seen.has(value)) return "[Circular]";
4702
+ seen.add(value);
4703
+ if (Array.isArray(value)) {
4704
+ return value.slice(0, 50).map(
4705
+ (item, index) => boundValue(
4706
+ item,
4707
+ String(index),
4708
+ maxMetadataValueLength,
4709
+ maxPreviewLength,
4710
+ seen,
4711
+ depth + 1
4712
+ )
4713
+ );
4714
+ }
4715
+ return boundAttributeValues(
4716
+ value,
4717
+ maxMetadataValueLength,
4718
+ maxPreviewLength,
4719
+ seen,
4720
+ depth + 1
4721
+ );
4722
+ }
4723
+ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPreviewLength) {
4724
+ if (!attrs || Object.keys(attrs).length === 0) {
4725
+ return attrs;
4726
+ }
4727
+ const redacted = redactor.redactRecord(attrs);
4728
+ const seen = /* @__PURE__ */ new WeakSet();
4729
+ const bounded = boundAttributeValues(
4730
+ redacted,
4731
+ maxMetadataValueLength,
4732
+ maxPreviewLength,
4733
+ seen,
4734
+ 0
4735
+ );
4736
+ const err = bounded.error;
4737
+ if (isRecord8(err) && typeof err.message === "string") {
4738
+ bounded.error = {
4739
+ ...err,
4740
+ message: truncateStringForProfile(
4741
+ err.message,
4742
+ "message",
4743
+ maxMetadataValueLength,
4744
+ maxPreviewLength
4745
+ ),
4746
+ ...typeof err.stack === "string" ? {
4747
+ stack: truncateStringForProfile(
4748
+ err.stack,
4749
+ "stack",
4750
+ maxMetadataValueLength,
4751
+ maxPreviewLength
4752
+ )
4753
+ } : {}
4754
+ };
4755
+ }
4756
+ return bounded;
4757
+ }
4758
+ function redactRunTreeForExport(tree, options) {
4759
+ const profile = options?.redactionProfile ?? "local";
4760
+ if (profile === "local") {
4761
+ return deepClone(tree);
4762
+ }
4763
+ const resolved = resolveRedactionProfile(profile);
4764
+ const { maxMetadataValueLength, maxPreviewLength } = applyProfileMetadataCaps(
4765
+ 2e3,
4766
+ 500,
4767
+ resolved
4768
+ );
4769
+ const redactor = new Redactor({ extraKeys: resolved.extraKeys });
4770
+ const clone = deepClone(tree);
4771
+ function walk(nodes) {
4772
+ for (const node of nodes) {
4773
+ if (node.event.attributes !== void 0) {
4774
+ node.event.attributes = redactEventAttributes(
4775
+ node.event.attributes,
4776
+ redactor,
4777
+ maxMetadataValueLength,
4778
+ maxPreviewLength
4779
+ );
4780
+ }
4781
+ if (node.children.length > 0) {
4782
+ walk(node.children);
4783
+ }
4784
+ }
4785
+ }
4786
+ walk(clone.children);
4787
+ return clone;
4788
+ }
4789
+
4790
+ // packages/core/src/exporters/html-exporter.ts
4791
+ function renderTreeHtml(nodes, ulClass = "tree") {
4792
+ if (nodes.length === 0) return "";
4793
+ const parts = [`<ul class="${ulClass}">`];
4794
+ for (const n of nodes) {
4795
+ const ev = n.event;
4796
+ const status = ev.status ?? "?";
4797
+ const dur = ev.durationMs !== void 0 && Number.isFinite(ev.durationMs) ? `${ev.durationMs}ms` : "-";
4798
+ parts.push("<li>");
4799
+ parts.push(
4800
+ `<span class="nm">${escapeHtml(ev.name)}</span> <span class="meta">[${escapeHtml(ev.kind)}] ${escapeHtml(status)} (${escapeHtml(dur)})</span>`
4801
+ );
4802
+ if (n.children.length > 0) {
4803
+ parts.push(renderTreeHtml(n.children, "tree nested"));
4804
+ }
4805
+ parts.push("</li>");
4806
+ }
4807
+ parts.push("</ul>");
4808
+ return parts.join("");
4809
+ }
4810
+ function exportHtml(tree, options) {
4811
+ const warnings = [];
4812
+ const includeMetadata = options?.includeMetadata ?? true;
4813
+ const includeAttributes = options?.includeAttributes ?? false;
4814
+ const includeErrors = options?.includeErrors ?? true;
4815
+ const maxLen = options?.maxAttributeLength ?? 500;
4816
+ const redacted = options?.redacted;
4817
+ const titleName = escapeHtml(tree.name ?? tree.runId);
4818
+ const summaryRows = [];
4819
+ summaryRows.push(
4820
+ `<tr><th scope="row">runId</th><td><code>${escapeHtml(tree.runId)}</code></td></tr>`
4821
+ );
4822
+ if (tree.name !== void 0) {
4823
+ summaryRows.push(`<tr><th scope="row">name</th><td>${escapeHtml(tree.name)}</td></tr>`);
4824
+ }
4825
+ summaryRows.push(
4826
+ `<tr><th scope="row">status</th><td>${escapeHtml(String(tree.status ?? "unknown"))}</td></tr>`
4827
+ );
4828
+ summaryRows.push(
4829
+ `<tr><th scope="row">durationMs</th><td>${tree.durationMs !== void 0 ? escapeHtml(String(tree.durationMs)) : "\u2014"}</td></tr>`
4830
+ );
4831
+ summaryRows.push(
4832
+ `<tr><th scope="row">startedAt</th><td>${tree.startedAt !== void 0 ? escapeHtml(String(tree.startedAt)) : "\u2014"}</td></tr>`
4833
+ );
4834
+ summaryRows.push(
4835
+ `<tr><th scope="row">endedAt</th><td>${tree.endedAt !== void 0 ? escapeHtml(String(tree.endedAt)) : "\u2014"}</td></tr>`
4836
+ );
4837
+ summaryRows.push(
4838
+ `<tr><th scope="row">totalEvents</th><td>${escapeHtml(String(tree.metadata.totalEvents))}</td></tr>`
4839
+ );
4840
+ let confidenceHtml = "";
4841
+ if (includeMetadata) {
4842
+ const cb = tree.metadata.confidenceBreakdown;
4843
+ confidenceHtml += "<h3>Confidence breakdown</h3><table><thead><tr><th>bucket</th><th>count</th></tr></thead><tbody>";
4844
+ for (const k of Object.keys(cb).sort()) {
4845
+ const key = k;
4846
+ confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${cb[key]}</td></tr>`;
4847
+ }
4848
+ confidenceHtml += "</tbody></table>";
4849
+ confidenceHtml += "<h3>Kind breakdown</h3><table><thead><tr><th>kind</th><th>count</th></tr></thead><tbody>";
4850
+ for (const k of Object.keys(tree.metadata.kinds).sort()) {
4851
+ const key = k;
4852
+ const c = tree.metadata.kinds[key];
4853
+ if (c > 0) confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${c}</td></tr>`;
4854
+ }
4855
+ confidenceHtml += "</tbody></table>";
4856
+ }
4857
+ const flat = flattenTree(tree);
4858
+ const errors = flat.filter((n) => n.event.status === "error");
4859
+ let errorsHtml = "";
4860
+ if (includeErrors && errors.length > 0) {
4861
+ errorsHtml += "<h2>Errors</h2><ul>";
4862
+ for (const n of errors) {
4863
+ const msg = n.event.attributes && typeof n.event.attributes.error === "object" ? safeString(
4864
+ n.event.attributes.error.message,
4865
+ maxLen
4866
+ ) : "";
4867
+ errorsHtml += `<li><strong>${escapeHtml(n.event.name)}</strong> (${escapeHtml(n.event.eventId)}): ${escapeHtml(msg || "error")}</li>`;
4868
+ }
4869
+ errorsHtml += "</ul>";
4870
+ }
4871
+ let attrsHtml = "";
4872
+ if (includeAttributes) {
4873
+ attrsHtml += "<h2>Attributes (bounded)</h2>";
4874
+ for (const n of flat) {
4875
+ if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
4876
+ const compact = compactAttributes3(n.event.attributes, {
4877
+ maxLength: maxLen,
4878
+ redacted
4879
+ });
4880
+ attrsHtml += `<h3>${escapeHtml(n.event.name)}</h3><pre class="json">${escapeHtml(stableJson(compact, true))}</pre>`;
4881
+ }
4882
+ warnings.push(
4883
+ "Attributes may still contain sensitive data; review exports before sharing."
4884
+ );
4885
+ }
4886
+ const css = `
4887
+ body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}
4888
+ h1{font-size:1.35rem}
4889
+ h2{font-size:1.1rem;margin-top:1.5rem}
4890
+ table{border-collapse:collapse;margin:0.75rem 0}
4891
+ th,td{border:1px solid #ccc;padding:0.35rem 0.6rem;text-align:left}
4892
+ th{background:#f5f5f5}
4893
+ pre.json{background:#f8f8f8;padding:0.75rem;overflow:auto;font-size:0.85rem}
4894
+ ul.tree{list-style:none;padding-left:1rem}
4895
+ ul.tree.nested{padding-left:1.25rem;border-left:1px solid #ddd;margin:0.25rem 0}
4896
+ .nm{font-weight:600}
4897
+ .meta{color:#555;font-size:0.9rem}
4898
+ footer{margin-top:2rem;font-size:0.85rem;color:#555}
4899
+ `.trim();
4900
+ const html = `<!doctype html>
4901
+ <html lang="en">
4902
+ <head>
4903
+ <meta charset="utf-8"/>
4904
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
4905
+ <title>${titleName}</title>
4906
+ <style>${css}</style>
4907
+ </head>
4908
+ <body>
4909
+ <header><h1>AgentInspect Run: ${titleName}</h1></header>
4910
+ <p class="note">Generated locally by AgentInspect.</p>
4911
+ ${includeMetadata ? `<section class="summary"><h2>Summary</h2><table>${summaryRows.join("")}</table>${confidenceHtml}</section>` : ""}
4912
+ <section class="tree"><h2>Execution tree</h2>${tree.children.length > 0 ? renderTreeHtml(tree.children) : "<p>No steps recorded.</p>"}</section>
4913
+ ${errorsHtml}
4914
+ ${attrsHtml}
4915
+ <footer>Generated locally by AgentInspect. Review for sensitive data before sharing.</footer>
4916
+ </body>
4917
+ </html>`;
4918
+ return {
4919
+ format: "html",
4920
+ content: html,
4921
+ contentType: "text/html",
4922
+ fileExtension: ".html",
4923
+ warnings
4924
+ };
4925
+ }
4926
+
4161
4927
  // packages/core/src/exporters/markdown-exporter.ts
4162
4928
  function renderTreeAscii(nodes, indent = "") {
4163
4929
  const lines = [];
@@ -4183,6 +4949,7 @@ function exportMarkdown(tree, options) {
4183
4949
  const includeAttributes = options?.includeAttributes ?? false;
4184
4950
  const includeErrors = options?.includeErrors ?? true;
4185
4951
  const maxLen = options?.maxAttributeLength ?? 500;
4952
+ const redacted = options?.redacted ?? true;
4186
4953
  const titleName = tree.name ?? tree.runId;
4187
4954
  const lines = [];
4188
4955
  lines.push(`# AgentInspect Run: ${escapeMarkdown(titleName)}`);
@@ -4258,7 +5025,9 @@ function exportMarkdown(tree, options) {
4258
5025
  for (const n of flat) {
4259
5026
  if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
4260
5027
  const compact = compactAttributes3(n.event.attributes, {
4261
- maxLength: maxLen});
5028
+ maxLength: maxLen,
5029
+ redacted
5030
+ });
4262
5031
  lines.push(`### ${escapeMarkdown(n.event.name)}`);
4263
5032
  lines.push("");
4264
5033
  lines.push("```json");
@@ -4278,6 +5047,295 @@ function exportMarkdown(tree, options) {
4278
5047
  warnings
4279
5048
  };
4280
5049
  }
5050
+ function hexFrom(seed, byteLen) {
5051
+ return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
5052
+ }
5053
+ function mapInspectKindToOI(kind, warnings) {
5054
+ switch (kind) {
5055
+ case "LLM":
5056
+ return { openInferenceKind: "LLM" };
5057
+ case "TOOL":
5058
+ return { openInferenceKind: "TOOL" };
5059
+ case "CHAIN":
5060
+ return { openInferenceKind: "CHAIN" };
5061
+ case "RETRIEVER":
5062
+ return { openInferenceKind: "RETRIEVER" };
5063
+ case "AGENT":
5064
+ return { openInferenceKind: "AGENT" };
5065
+ case "DECISION":
5066
+ warnings.push(
5067
+ `Ambiguous kind DECISION mapped to CHAIN for span compatibility (${EXPORT_PAYLOAD_VERSION}).`
5068
+ );
5069
+ return { openInferenceKind: "CHAIN" };
5070
+ case "RESULT":
5071
+ warnings.push(
5072
+ `Ambiguous kind RESULT mapped to UNKNOWN for span compatibility (${EXPORT_PAYLOAD_VERSION}).`
5073
+ );
5074
+ return { openInferenceKind: "UNKNOWN" };
5075
+ case "ERROR":
5076
+ warnings.push(`ERROR kind mapped to CHAIN for span compatibility.`);
5077
+ return { openInferenceKind: "CHAIN" };
5078
+ case "LOG":
5079
+ case "LOGIC":
5080
+ case "RUN":
5081
+ warnings.push(`${kind} mapped to CHAIN for span compatibility.`);
5082
+ return { openInferenceKind: "CHAIN" };
5083
+ default:
5084
+ warnings.push(`Unhandled InspectKind ${kind} mapped to UNKNOWN.`);
5085
+ return { openInferenceKind: "UNKNOWN" };
5086
+ }
5087
+ }
5088
+ function exportOpenInference(tree, options) {
5089
+ const warnings = [
5090
+ "OpenInference-compatible JSON export is experimental until verified against specific backends.",
5091
+ "This file was generated locally and not sent anywhere."
5092
+ ];
5093
+ const traceId = hexFrom(`trace:${tree.runId}`, 16);
5094
+ const includeAttributes = options?.includeAttributes ?? false;
5095
+ const maxLen = options?.maxAttributeLength ?? 500;
5096
+ const pretty = options?.pretty ?? true;
5097
+ const spans = [];
5098
+ for (const n of flattenTree(tree)) {
5099
+ const ev = n.event;
5100
+ const spanId = hexFrom(`${tree.runId}:${ev.eventId}`, 8);
5101
+ const parentSpanHex = ev.parentId ? hexFrom(`${tree.runId}:${ev.parentId}`, 8) : void 0;
5102
+ const startNs = Math.round(ev.timestamp * 1e6);
5103
+ let endNs;
5104
+ if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
5105
+ endNs = startNs + Math.round(ev.durationMs * 1e6);
5106
+ }
5107
+ const { openInferenceKind } = mapInspectKindToOI(ev.kind, warnings);
5108
+ const attrs = {
5109
+ "openinference.span.kind": openInferenceKind,
5110
+ "agent_inspect.kind": ev.kind,
5111
+ "agent_inspect.confidence": ev.confidence,
5112
+ "agent_inspect.source.type": ev.source.type,
5113
+ "agent_inspect.run_id": tree.runId,
5114
+ "agent_inspect.event_id": ev.eventId,
5115
+ "agent_inspect.status": ev.status ?? "unset"
5116
+ };
5117
+ if (ev.durationMs !== void 0) {
5118
+ attrs["agent_inspect.duration_ms"] = ev.durationMs;
5119
+ }
5120
+ const meta = ev.attributes;
5121
+ if (meta?.model !== void 0 && typeof meta.model === "string") {
5122
+ attrs["llm.model_name"] = meta.model;
5123
+ }
5124
+ const tokens = meta?.tokens;
5125
+ if (tokens && typeof tokens === "object" && tokens !== null) {
5126
+ const inp = tokens.input;
5127
+ const outp = tokens.output;
5128
+ if (typeof inp === "number") attrs["llm.token_count.prompt"] = inp;
5129
+ if (typeof outp === "number") attrs["llm.token_count.completion"] = outp;
5130
+ }
5131
+ if (includeAttributes && meta && typeof meta === "object") {
5132
+ for (const [k, v] of Object.entries(meta)) {
5133
+ if (k === "tokens" || k === "model") continue;
5134
+ if (v !== void 0 && v !== null && typeof v !== "object") {
5135
+ attrs[`agent_inspect.preview.${k}`] = typeof v === "string" ? v.slice(0, maxLen) : v;
5136
+ }
5137
+ }
5138
+ }
5139
+ let status;
5140
+ if (ev.status === "error") {
5141
+ const msg = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error") : "error";
5142
+ status = { code: "ERROR", message: msg.slice(0, maxLen) };
5143
+ } else if (ev.status === "ok") {
5144
+ status = { code: "OK" };
5145
+ } else {
5146
+ status = { code: "UNSET" };
5147
+ }
5148
+ spans.push({
5149
+ trace_id: traceId,
5150
+ span_id: spanId,
5151
+ parent_span_id: parentSpanHex,
5152
+ name: ev.name,
5153
+ start_time_unix_nano: startNs,
5154
+ end_time_unix_nano: endNs,
5155
+ attributes: attrs,
5156
+ status
5157
+ });
5158
+ }
5159
+ const payload = {
5160
+ exporter: "agent-inspect",
5161
+ format: "openinference",
5162
+ compatibility: "openinference-compatible",
5163
+ version: EXPORT_PAYLOAD_VERSION,
5164
+ trace_id: traceId,
5165
+ spans,
5166
+ warnings
5167
+ };
5168
+ return {
5169
+ format: "openinference",
5170
+ content: JSON.stringify(payload, null, pretty ? 2 : void 0),
5171
+ contentType: "application/json",
5172
+ fileExtension: ".openinference.json",
5173
+ warnings
5174
+ };
5175
+ }
5176
+ function hexFrom2(seed, byteLen) {
5177
+ return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
5178
+ }
5179
+ function stringAttr(key, value) {
5180
+ return { key, value: { stringValue: value } };
5181
+ }
5182
+ function intAttr(key, value) {
5183
+ return { key, value: { intValue: String(value) } };
5184
+ }
5185
+ function genAiOperationName(kind) {
5186
+ switch (kind) {
5187
+ case "LLM":
5188
+ return "generate_content";
5189
+ case "TOOL":
5190
+ return "execute_tool";
5191
+ case "AGENT":
5192
+ return "invoke_agent";
5193
+ default:
5194
+ return void 0;
5195
+ }
5196
+ }
5197
+ function exportOtlpJson(tree, options) {
5198
+ const warnings = [
5199
+ "OTLP JSON export uses OTel GenAI-aligned attributes where applicable; experimental until verified against specific collectors.",
5200
+ "Not OTLP gRPC/protobuf \u2014 JSON mapping only. Generated locally; no network upload."
5201
+ ];
5202
+ const traceId = hexFrom2(`trace:${tree.runId}`, 16);
5203
+ const includeAttributes = options?.includeAttributes ?? false;
5204
+ const maxLen = options?.maxAttributeLength ?? 500;
5205
+ const pretty = options?.pretty ?? true;
5206
+ const flat = flattenTree(tree);
5207
+ const spans = [];
5208
+ for (const n of flat) {
5209
+ const ev = n.event;
5210
+ const spanId = hexFrom2(`${tree.runId}:${ev.eventId}`, 8);
5211
+ const parentSpanId = ev.parentId ? hexFrom2(`${tree.runId}:${ev.parentId}`, 8) : void 0;
5212
+ const startNs = String(Math.round(ev.timestamp * 1e6));
5213
+ let endNs;
5214
+ if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
5215
+ endNs = String(Math.round(ev.timestamp * 1e6 + ev.durationMs * 1e6));
5216
+ }
5217
+ const attrs = [
5218
+ stringAttr("agent_inspect.kind", ev.kind),
5219
+ stringAttr("agent_inspect.confidence", ev.confidence),
5220
+ stringAttr("agent_inspect.source.type", ev.source.type),
5221
+ stringAttr("agent_inspect.run_id", tree.runId),
5222
+ stringAttr("agent_inspect.event_id", ev.eventId),
5223
+ stringAttr("agent_inspect.status", ev.status ?? "unset")
5224
+ ];
5225
+ if (ev.durationMs !== void 0) {
5226
+ attrs.push(intAttr("agent_inspect.duration_ms", ev.durationMs));
5227
+ }
5228
+ const op = genAiOperationName(ev.kind);
5229
+ if (op !== void 0) {
5230
+ attrs.push(stringAttr("gen_ai.operation.name", op));
5231
+ }
5232
+ const meta = ev.attributes;
5233
+ if (meta?.model !== void 0 && typeof meta.model === "string") {
5234
+ attrs.push(stringAttr("gen_ai.request.model", meta.model.slice(0, maxLen)));
5235
+ }
5236
+ const tokens = meta?.tokens;
5237
+ if (tokens && typeof tokens === "object" && tokens !== null) {
5238
+ const inp = tokens.input;
5239
+ const outp = tokens.output;
5240
+ if (typeof inp === "number") attrs.push(intAttr("gen_ai.usage.input_tokens", inp));
5241
+ if (typeof outp === "number") attrs.push(intAttr("gen_ai.usage.output_tokens", outp));
5242
+ }
5243
+ if (includeAttributes && meta && typeof meta === "object") {
5244
+ for (const [k, v] of Object.entries(meta)) {
5245
+ if (k === "tokens" || k === "model") continue;
5246
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
5247
+ attrs.push(
5248
+ stringAttr(
5249
+ `agent_inspect.preview.${k}`,
5250
+ typeof v === "string" ? v.slice(0, maxLen) : String(v)
5251
+ )
5252
+ );
5253
+ }
5254
+ }
5255
+ }
5256
+ let statusCode = "STATUS_CODE_UNSET";
5257
+ let statusMessage;
5258
+ if (ev.status === "error") {
5259
+ statusCode = "STATUS_CODE_ERROR";
5260
+ statusMessage = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error").slice(0, maxLen) : "error";
5261
+ } else if (ev.status === "ok") {
5262
+ statusCode = "STATUS_CODE_OK";
5263
+ }
5264
+ const spanJson = {
5265
+ traceId,
5266
+ spanId,
5267
+ name: ev.name,
5268
+ kind: "SPAN_KIND_INTERNAL",
5269
+ startTimeUnixNano: startNs,
5270
+ attributes: attrs,
5271
+ status: {
5272
+ code: statusCode,
5273
+ ...statusMessage !== void 0 ? { message: statusMessage } : {}
5274
+ }
5275
+ };
5276
+ if (parentSpanId !== void 0) {
5277
+ spanJson.parentSpanId = parentSpanId;
5278
+ }
5279
+ if (endNs !== void 0) {
5280
+ spanJson.endTimeUnixNano = endNs;
5281
+ }
5282
+ spans.push(spanJson);
5283
+ }
5284
+ const payload = {
5285
+ resourceSpans: [
5286
+ {
5287
+ resource: {
5288
+ attributes: [stringAttr("service.name", "agent-inspect")]
5289
+ },
5290
+ scopeSpans: [
5291
+ {
5292
+ scope: { name: "agent-inspect" },
5293
+ spans
5294
+ }
5295
+ ]
5296
+ }
5297
+ ]
5298
+ };
5299
+ return {
5300
+ format: "otlp-json",
5301
+ content: JSON.stringify(payload, null, pretty ? 2 : void 0),
5302
+ contentType: "application/json",
5303
+ fileExtension: ".otlp.json",
5304
+ warnings
5305
+ };
5306
+ }
5307
+
5308
+ // packages/core/src/exporters/index.ts
5309
+ function mergeExportDefaults(options) {
5310
+ return {
5311
+ format: options.format,
5312
+ includeMetadata: options.includeMetadata ?? true,
5313
+ includeAttributes: options.includeAttributes ?? false,
5314
+ includeErrors: options.includeErrors ?? true,
5315
+ pretty: options.pretty ?? true,
5316
+ redacted: options.redacted,
5317
+ maxAttributeLength: options.maxAttributeLength ?? 500,
5318
+ redactionProfile: options.redactionProfile ?? "local"
5319
+ };
5320
+ }
5321
+ function exportRunTree(tree, options) {
5322
+ const opts = mergeExportDefaults(options);
5323
+ const exportTree = opts.redactionProfile === "local" ? tree : redactRunTreeForExport(tree, { redactionProfile: opts.redactionProfile });
5324
+ switch (opts.format) {
5325
+ case "markdown":
5326
+ return exportMarkdown(exportTree, opts);
5327
+ case "html":
5328
+ return exportHtml(exportTree, opts);
5329
+ case "openinference":
5330
+ return exportOpenInference(exportTree, opts);
5331
+ case "otlp-json":
5332
+ return exportOtlpJson(exportTree, opts);
5333
+ default: {
5334
+ const _x = opts.format;
5335
+ throw new Error(`Unsupported export format: ${String(_x)}`);
5336
+ }
5337
+ }
5338
+ }
4281
5339
 
4282
5340
  // packages/mcp-server/src/tools.ts
4283
5341
  var READ_ONLY_TOOLS = [
@@ -4351,6 +5409,42 @@ var READ_ONLY_TOOLS = [
4351
5409
  properties: { runId: { type: "string" } },
4352
5410
  required: ["runId"]
4353
5411
  }
5412
+ },
5413
+ {
5414
+ name: "summarize_failed_run",
5415
+ description: "Summarize a failed run with step errors and correlation metadata.",
5416
+ inputSchema: {
5417
+ type: "object",
5418
+ properties: { runId: { type: "string" } },
5419
+ required: ["runId"]
5420
+ }
5421
+ },
5422
+ {
5423
+ name: "retrieve_decision_notes",
5424
+ description: "List decision steps and decision metadata for one run.",
5425
+ inputSchema: {
5426
+ type: "object",
5427
+ properties: { runId: { type: "string" } },
5428
+ required: ["runId"]
5429
+ }
5430
+ },
5431
+ {
5432
+ name: "find_failed_observation",
5433
+ description: "Find failed observed outcomes in one run.",
5434
+ inputSchema: {
5435
+ type: "object",
5436
+ properties: { runId: { type: "string" } },
5437
+ required: ["runId"]
5438
+ }
5439
+ },
5440
+ {
5441
+ name: "create_share_safe_bundle",
5442
+ description: "Create an in-memory share-safe bundle manifest and redacted exports.",
5443
+ inputSchema: {
5444
+ type: "object",
5445
+ properties: { runId: { type: "string" } },
5446
+ required: ["runId"]
5447
+ }
4354
5448
  }
4355
5449
  ];
4356
5450
  function textResult(payload) {
@@ -4385,6 +5479,19 @@ async function openRunTrace(context, runId) {
4385
5479
  function legacyTraceEvents(events) {
4386
5480
  return persistedInspectEventsToTraceEvents(events);
4387
5481
  }
5482
+ function redactionProfileForExport(context) {
5483
+ return context.redactionProfile === "local" ? "share" : context.redactionProfile;
5484
+ }
5485
+ function decisionNotes(events) {
5486
+ return events.filter(
5487
+ (event) => event.kind === "DECISION" || typeof event.attributes?.decisionId === "string" && event.attributes.decisionId !== ""
5488
+ ).slice(0, 50).map((event) => ({
5489
+ name: event.name,
5490
+ kind: event.kind,
5491
+ status: event.status,
5492
+ decisionId: typeof event.attributes?.decisionId === "string" ? event.attributes.decisionId : void 0
5493
+ }));
5494
+ }
4388
5495
  async function callReadOnlyTool(context, name, args = {}) {
4389
5496
  switch (name) {
4390
5497
  case "list_traces": {
@@ -4485,10 +5592,73 @@ async function callReadOnlyTool(context, name, args = {}) {
4485
5592
  const { read } = await openRunTrace(context, runId);
4486
5593
  const run = read.runs.find((item) => item.runId === runId) ?? read.runs[0];
4487
5594
  if (!run) return errorResult2(`Run tree not found: ${runId}`);
5595
+ const profile = redactionProfileForExport(context);
4488
5596
  const markdown = exportMarkdown(run, {
4489
- redactionProfile: context.redactionProfile === "local" ? "share" : context.redactionProfile
5597
+ redacted: true});
5598
+ return textResult({ runId, profile, markdown: markdown.content });
5599
+ }
5600
+ case "summarize_failed_run": {
5601
+ const runId = String(args.runId ?? "");
5602
+ const { read } = await openRunTrace(context, runId);
5603
+ const traceEvents = legacyTraceEvents(read.events);
5604
+ const summary = buildRunWhatSummary(traceEvents);
5605
+ return textResult({
5606
+ runId,
5607
+ status: summary.status,
5608
+ summary: renderRunWhat(summary),
5609
+ failedStepNames: summary.failedStepNames,
5610
+ correlation: summary.correlation ?? null
5611
+ });
5612
+ }
5613
+ case "retrieve_decision_notes": {
5614
+ const runId = String(args.runId ?? "");
5615
+ const { read } = await openRunTrace(context, runId);
5616
+ const notes = decisionNotes(read.events);
5617
+ return textResult({ runId, decisions: notes, count: notes.length });
5618
+ }
5619
+ case "find_failed_observation": {
5620
+ const runId = String(args.runId ?? "");
5621
+ const { read } = await openRunTrace(context, runId);
5622
+ const outcomes = extractOutcomesFromTraceEvents(legacyTraceEvents(read.events));
5623
+ const failed = outcomes.filter((outcome) => outcome.status === "failed");
5624
+ return textResult({
5625
+ runId,
5626
+ failed,
5627
+ count: failed.length
5628
+ });
5629
+ }
5630
+ case "create_share_safe_bundle": {
5631
+ const runId = String(args.runId ?? "");
5632
+ const { read } = await openRunTrace(context, runId);
5633
+ const run = read.runs.find((item) => item.runId === runId) ?? read.runs[0];
5634
+ if (!run) return errorResult2(`Run tree not found: ${runId}`);
5635
+ const profile = redactionProfileForExport(context);
5636
+ const markdown = exportMarkdown(run, {
5637
+ redacted: true});
5638
+ const tree = exportRunTree(run, {
5639
+ format: "openinference",
5640
+ redacted: true,
5641
+ redactionProfile: profile
5642
+ });
5643
+ const metadata = buildBundleMetadata({
5644
+ agentInspectVersion: "mcp-server",
5645
+ profile,
5646
+ resolve: { runIds: [runId] },
5647
+ checks: {
5648
+ aggregateStatus: "SAFE",
5649
+ runs: [{ runId, status: "SAFE", errors: 0, warnings: 0, findings: 0 }]
5650
+ },
5651
+ files: ["report.md", "tree.json"]
5652
+ });
5653
+ return textResult({
5654
+ runId,
5655
+ profile,
5656
+ metadata,
5657
+ files: {
5658
+ "report.md": markdown.content,
5659
+ "tree.json": tree.content
5660
+ }
4490
5661
  });
4491
- return textResult({ runId, profile: context.redactionProfile, markdown: markdown.content });
4492
5662
  }
4493
5663
  default:
4494
5664
  return errorResult2(`Unknown tool: ${name}`);