agent-inspect 1.2.0 → 1.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.
@@ -467,7 +467,7 @@ function stableHash(value) {
467
467
  const h = crypto.createHash("sha256").update(value, "utf8").digest("hex");
468
468
  return h.slice(0, 8);
469
469
  }
470
- function compileRules(rules) {
470
+ function compileRules(rules, extraKeys) {
471
471
  const out = /* @__PURE__ */ new Map();
472
472
  const set = (r) => {
473
473
  const k = toKey(r.key);
@@ -476,6 +476,11 @@ function compileRules(rules) {
476
476
  for (const k of DEFAULT_REDACT_KEYS) {
477
477
  set({ key: k, strategy: "full" });
478
478
  }
479
+ for (const k of extraKeys ?? []) {
480
+ if (typeof k === "string" && k.length > 0) {
481
+ set({ key: k, strategy: "full" });
482
+ }
483
+ }
479
484
  for (const r of rules ?? []) {
480
485
  if (typeof r === "string") {
481
486
  set({ key: r, strategy: "full" });
@@ -493,7 +498,7 @@ function compileRules(rules) {
493
498
  var Redactor = class {
494
499
  #rules;
495
500
  constructor(options) {
496
- this.#rules = compileRules(options?.rules);
501
+ this.#rules = compileRules(options?.rules, options?.extraKeys);
497
502
  }
498
503
  redactValue(key, value) {
499
504
  const k = toKey(key);
@@ -1195,6 +1200,91 @@ function warn(message, error) {
1195
1200
  }
1196
1201
  console.warn(`${base}: ${formatError(error).message}`);
1197
1202
  }
1203
+
1204
+ // packages/core/src/redaction-profiles.ts
1205
+ var SHARE_PROFILE_EXTRA_KEYS = [
1206
+ "userEmail",
1207
+ "customerEmail",
1208
+ "phone",
1209
+ "phoneNumber",
1210
+ "address",
1211
+ "ip",
1212
+ "ipAddress",
1213
+ "sessionId",
1214
+ "requestId",
1215
+ "correlationId",
1216
+ "decisionId",
1217
+ "groupId",
1218
+ "customerId",
1219
+ "userId",
1220
+ "accountId",
1221
+ "tenantId",
1222
+ "orgId",
1223
+ "organizationId",
1224
+ "traceId",
1225
+ "spanId",
1226
+ "parentSpanId"
1227
+ ];
1228
+ var STRICT_PROFILE_EXTRA_KEYS = [
1229
+ "prompt",
1230
+ "completion",
1231
+ "input",
1232
+ "output",
1233
+ "inputPreview",
1234
+ "outputPreview",
1235
+ "message",
1236
+ "messages",
1237
+ "transcript",
1238
+ "context",
1239
+ "document",
1240
+ "documents",
1241
+ "chunk",
1242
+ "chunks",
1243
+ "retrieval",
1244
+ "query"
1245
+ ];
1246
+ function resolveRedactionProfile(profile = "local") {
1247
+ switch (profile) {
1248
+ case "local":
1249
+ return { profile: "local", extraKeys: [] };
1250
+ case "share":
1251
+ return {
1252
+ profile: "share",
1253
+ extraKeys: SHARE_PROFILE_EXTRA_KEYS,
1254
+ maxMetadataValueLengthCap: 500,
1255
+ maxPreviewLengthCap: 200
1256
+ };
1257
+ case "strict":
1258
+ return {
1259
+ profile: "strict",
1260
+ extraKeys: [...SHARE_PROFILE_EXTRA_KEYS, ...STRICT_PROFILE_EXTRA_KEYS],
1261
+ maxMetadataValueLengthCap: 200,
1262
+ maxPreviewLengthCap: 80
1263
+ };
1264
+ default:
1265
+ return { profile: "local", extraKeys: [] };
1266
+ }
1267
+ }
1268
+ function isPreviewKey(key) {
1269
+ return key.toLowerCase().includes("preview");
1270
+ }
1271
+ function applyProfileMetadataCaps(maxMetadataValueLength, maxPreviewLength, resolved) {
1272
+ let meta = maxMetadataValueLength;
1273
+ let preview = maxPreviewLength;
1274
+ if (resolved.maxMetadataValueLengthCap !== void 0) {
1275
+ meta = Math.min(meta, resolved.maxMetadataValueLengthCap);
1276
+ }
1277
+ if (resolved.maxPreviewLengthCap !== void 0) {
1278
+ preview = Math.min(preview, resolved.maxPreviewLengthCap);
1279
+ }
1280
+ return { maxMetadataValueLength: meta, maxPreviewLength: preview };
1281
+ }
1282
+ function truncateStringForProfile(value, key, maxMetadataValueLength, maxPreviewLength) {
1283
+ const max = isPreviewKey(key) ? maxPreviewLength : maxMetadataValueLength;
1284
+ if (max <= 0) return "\u2026";
1285
+ if (value.length <= max) return value;
1286
+ return `${value.slice(0, max)}\u2026`;
1287
+ }
1198
1288
  function isRecord6(value) {
1199
1289
  return typeof value === "object" && value !== null && !Array.isArray(value);
1200
1290
  }
@@ -1345,7 +1435,7 @@ var DEFAULT_MAX_EVENT_BYTES = 65536;
1345
1435
  function isRecord7(value) {
1346
1436
  return typeof value === "object" && value !== null && !Array.isArray(value);
1347
1437
  }
1348
- function isPreviewKey(key) {
1438
+ function isPreviewKey2(key) {
1349
1439
  return key.toLowerCase().includes("preview");
1350
1440
  }
1351
1441
  function truncateString(value, maxLen) {
@@ -1362,11 +1452,28 @@ function resolveTraceSafetyOptions(options) {
1362
1452
  {
1363
1453
  redactEnabled = true;
1364
1454
  }
1455
+ const profile = options?.redactionProfile ?? "local";
1456
+ const resolvedProfile = resolveRedactionProfile(profile);
1457
+ const userMaxMetadata = "undefined" === "number" && Number.isFinite(options.maxMetadataValueLength) && options.maxMetadataValueLength >= 0 ? Math.floor(options.maxMetadataValueLength) : void 0;
1458
+ const userMaxPreview = "undefined" === "number" && Number.isFinite(options.maxPreviewLength) && options.maxPreviewLength >= 0 ? Math.floor(options.maxPreviewLength) : void 0;
1459
+ let maxMetadataValueLength = userMaxMetadata ?? DEFAULT_MAX_METADATA_VALUE_LENGTH;
1460
+ let maxPreviewLength = userMaxPreview ?? DEFAULT_MAX_PREVIEW_LENGTH;
1461
+ if (redactEnabled && profile !== "local") {
1462
+ const capped = applyProfileMetadataCaps(
1463
+ maxMetadataValueLength,
1464
+ maxPreviewLength,
1465
+ resolvedProfile
1466
+ );
1467
+ maxMetadataValueLength = capped.maxMetadataValueLength;
1468
+ maxPreviewLength = capped.maxPreviewLength;
1469
+ }
1365
1470
  return {
1366
1471
  redactEnabled,
1367
1472
  redactionRules,
1368
- maxMetadataValueLength: "undefined" === "number" && Number.isFinite(options.maxMetadataValueLength) && options.maxMetadataValueLength >= 0 ? Math.floor(options.maxMetadataValueLength) : DEFAULT_MAX_METADATA_VALUE_LENGTH,
1369
- maxPreviewLength: "undefined" === "number" && Number.isFinite(options.maxPreviewLength) && options.maxPreviewLength >= 0 ? Math.floor(options.maxPreviewLength) : DEFAULT_MAX_PREVIEW_LENGTH,
1473
+ redactionProfile: profile,
1474
+ profileExtraKeys: redactEnabled ? resolvedProfile.extraKeys : [],
1475
+ maxMetadataValueLength,
1476
+ maxPreviewLength,
1370
1477
  maxEventBytes: "undefined" === "number" && Number.isFinite(options.maxEventBytes) && options.maxEventBytes > 0 ? Math.floor(options.maxEventBytes) : DEFAULT_MAX_EVENT_BYTES
1371
1478
  };
1372
1479
  }
@@ -1374,7 +1481,7 @@ function boundMetadataValue(key, value, opts, seen, depth) {
1374
1481
  if (depth > 32) return "[MaxDepth]";
1375
1482
  if (value === null || typeof value !== "object") {
1376
1483
  if (typeof value === "string") {
1377
- const max = isPreviewKey(key) ? opts.maxPreviewLength : opts.maxMetadataValueLength;
1484
+ const max = isPreviewKey2(key) ? opts.maxPreviewLength : opts.maxMetadataValueLength;
1378
1485
  return truncateString(value, max);
1379
1486
  }
1380
1487
  return value;
@@ -1400,7 +1507,10 @@ function boundMetadataValue(key, value, opts, seen, depth) {
1400
1507
  }
1401
1508
  function redactMetadata(metadata, opts) {
1402
1509
  if (!opts.redactEnabled) return { ...metadata };
1403
- const redactor = new Redactor({ rules: opts.redactionRules });
1510
+ const redactor = new Redactor({
1511
+ rules: opts.redactionRules,
1512
+ extraKeys: opts.profileExtraKeys
1513
+ });
1404
1514
  return redactor.redactRecord(metadata);
1405
1515
  }
1406
1516
  function prepareMetadataForDisk(metadata, opts) {
@@ -1886,6 +1996,574 @@ function filterTraces(traces, options) {
1886
1996
  }
1887
1997
  return out;
1888
1998
  }
1999
+
2000
+ // packages/core/src/timeline.ts
2001
+ function finite(n) {
2002
+ return typeof n === "number" && Number.isFinite(n);
2003
+ }
2004
+ function pickStreamingMeta(metadata) {
2005
+ if (!metadata || typeof metadata !== "object") return void 0;
2006
+ const chunkCount = metadata.chunkCount;
2007
+ const streamDurationMs = metadata.streamDurationMs;
2008
+ const streamedCharCount = metadata.streamedCharCount;
2009
+ if (!finite(chunkCount) && !finite(streamDurationMs) && !finite(streamedCharCount)) {
2010
+ return void 0;
2011
+ }
2012
+ return {
2013
+ ...finite(chunkCount) ? { chunkCount } : {},
2014
+ ...finite(streamDurationMs) ? { streamDurationMs } : {},
2015
+ ...finite(streamedCharCount) ? { streamedCharCount } : {}
2016
+ };
2017
+ }
2018
+ function pickCorrelation(metadata) {
2019
+ if (!metadata || typeof metadata !== "object") return void 0;
2020
+ const out = {};
2021
+ for (const key of [
2022
+ "correlationId",
2023
+ "requestId",
2024
+ "decisionId",
2025
+ "groupId"
2026
+ ]) {
2027
+ const v = metadata[key];
2028
+ if (typeof v === "string" && v.trim() !== "") {
2029
+ out[key] = v;
2030
+ }
2031
+ }
2032
+ return Object.keys(out).length > 0 ? out : void 0;
2033
+ }
2034
+ function buildRunTimeline(events, options = {}) {
2035
+ const started = events.find(
2036
+ (e) => e.event === "run_started"
2037
+ );
2038
+ const completed = events.filter(
2039
+ (e) => e.event === "run_completed"
2040
+ );
2041
+ const lastCompleted = completed[completed.length - 1];
2042
+ const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
2043
+ const runStart = started && finite(started.startTime) ? started.startTime : started && finite(started.timestamp) ? started.timestamp : void 0;
2044
+ const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
2045
+ const steps = /* @__PURE__ */ new Map();
2046
+ for (const e of events) {
2047
+ if (e.event === "step_started") {
2048
+ const s = e;
2049
+ steps.set(s.stepId, {
2050
+ name: s.name,
2051
+ type: s.type,
2052
+ parentId: s.parentId,
2053
+ startedAt: finite(s.startTime) ? s.startTime : s.timestamp,
2054
+ status: "running",
2055
+ metadata: s.metadata
2056
+ });
2057
+ }
2058
+ }
2059
+ for (const e of events) {
2060
+ if (e.event !== "step_completed") continue;
2061
+ const c = e;
2062
+ const node = steps.get(c.stepId);
2063
+ if (!node) continue;
2064
+ node.status = c.status;
2065
+ if (finite(c.durationMs)) node.durationMs = c.durationMs;
2066
+ }
2067
+ const depthCache = /* @__PURE__ */ new Map();
2068
+ const computeDepth = (stepId) => {
2069
+ const cached = depthCache.get(stepId);
2070
+ if (cached !== void 0) return cached;
2071
+ const node = steps.get(stepId);
2072
+ if (!node) return 0;
2073
+ const parent = node.parentId;
2074
+ if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
2075
+ depthCache.set(stepId, 0);
2076
+ return 0;
2077
+ }
2078
+ const d = Math.min(1e3, computeDepth(parent) + 1);
2079
+ depthCache.set(stepId, d);
2080
+ return d;
2081
+ };
2082
+ const entries = [];
2083
+ for (const [stepId, s] of steps.entries()) {
2084
+ const offsetMs = runStart !== void 0 && finite(s.startedAt) ? Math.max(0, s.startedAt - runStart) : 0;
2085
+ entries.push({
2086
+ stepId,
2087
+ name: s.name,
2088
+ type: s.type,
2089
+ status: s.status,
2090
+ depth: computeDepth(stepId),
2091
+ startedAt: s.startedAt,
2092
+ offsetMs,
2093
+ durationMs: s.durationMs,
2094
+ isError: s.status === "error",
2095
+ streaming: pickStreamingMeta(s.metadata)
2096
+ });
2097
+ }
2098
+ entries.sort((a, b) => a.startedAt - b.startedAt);
2099
+ const slowTopN = options.slowTopN ?? 3;
2100
+ if (options.focus === "slow" && entries.length > 0) {
2101
+ const ranked = [...entries].filter((e) => finite(e.durationMs)).sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
2102
+ const slowIds = new Set(
2103
+ ranked.slice(0, slowTopN).map((e) => e.stepId)
2104
+ );
2105
+ for (const e of entries) {
2106
+ if (slowIds.has(e.stepId)) e.slow = true;
2107
+ }
2108
+ }
2109
+ return {
2110
+ runId,
2111
+ name: typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0,
2112
+ status,
2113
+ startedAt: runStart,
2114
+ endedAt: lastCompleted && finite(lastCompleted.endTime) ? lastCompleted.endTime : void 0,
2115
+ durationMs: lastCompleted && finite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0,
2116
+ correlation: pickCorrelation(
2117
+ started?.metadata
2118
+ ),
2119
+ entries
2120
+ };
2121
+ }
2122
+ function renderTimeline(timeline, options = {}) {
2123
+ const lines = [];
2124
+ lines.push(`Timeline: ${timeline.name ?? timeline.runId}`);
2125
+ lines.push(`Run ID: ${timeline.runId}`);
2126
+ lines.push(`Status: ${timeline.status}`);
2127
+ if (timeline.startedAt !== void 0) {
2128
+ lines.push(`Started: ${formatTimestamp(timeline.startedAt)}`);
2129
+ }
2130
+ if (timeline.durationMs !== void 0) {
2131
+ lines.push(`Duration: ${formatDuration2(timeline.durationMs)}`);
2132
+ }
2133
+ if (timeline.correlation) {
2134
+ const parts = Object.entries(timeline.correlation).filter(([, v]) => typeof v === "string").map(([k, v]) => `${k}=${v}`);
2135
+ if (parts.length > 0) {
2136
+ lines.push(`Correlation: ${parts.join(", ")}`);
2137
+ }
2138
+ }
2139
+ lines.push("");
2140
+ lines.push("Steps (chronological):");
2141
+ const show = timeline.entries.filter((e) => {
2142
+ if (options.focus === "slow") return e.slow === true;
2143
+ return true;
2144
+ });
2145
+ if (show.length === 0) {
2146
+ lines.push(
2147
+ options.focus === "slow" ? "(no steps with duration for slow focus)" : "(no steps)"
2148
+ );
2149
+ return lines.join("\n");
2150
+ }
2151
+ for (const e of show) {
2152
+ const prefix = e.slow ? "[slow] " : "";
2153
+ const typeTag = e.type === "llm" ? "llm" : e.type === "tool" ? "tool" : e.type;
2154
+ const dur = e.durationMs !== void 0 ? formatDuration2(e.durationMs) : "-";
2155
+ const err = e.isError ? " error" : "";
2156
+ const off = formatDuration2(e.offsetMs);
2157
+ let line = `${prefix}+${off} ${typeTag}:${e.name} (${dur})${err}`;
2158
+ if (e.streaming?.chunkCount !== void 0) {
2159
+ line += ` chunks=${e.streaming.chunkCount}`;
2160
+ }
2161
+ if (e.streaming?.streamDurationMs !== void 0) {
2162
+ line += ` stream=${formatDuration2(e.streaming.streamDurationMs)}`;
2163
+ }
2164
+ lines.push(line);
2165
+ }
2166
+ return lines.join("\n");
2167
+ }
2168
+ function percentile(sorted, p) {
2169
+ if (sorted.length === 0) return void 0;
2170
+ const idx = Math.min(
2171
+ sorted.length - 1,
2172
+ Math.max(0, Math.ceil(p / 100 * sorted.length) - 1)
2173
+ );
2174
+ return sorted[idx];
2175
+ }
2176
+ async function readRunStartedMetadata(filePath) {
2177
+ try {
2178
+ const raw = await readFile(filePath, "utf-8");
2179
+ for (const line of raw.split(/\r?\n/)) {
2180
+ const trimmed = line.trim();
2181
+ if (trimmed === "") continue;
2182
+ let parsed;
2183
+ try {
2184
+ parsed = JSON.parse(trimmed);
2185
+ } catch {
2186
+ continue;
2187
+ }
2188
+ if (!isTraceEvent(parsed)) continue;
2189
+ if (parsed.event !== "run_started") continue;
2190
+ const rs = parsed;
2191
+ if (rs.metadata && typeof rs.metadata === "object") {
2192
+ return rs.metadata;
2193
+ }
2194
+ return void 0;
2195
+ }
2196
+ } catch {
2197
+ }
2198
+ return void 0;
2199
+ }
2200
+ function metaMatchesCorrelation(metadata, correlationId, groupId) {
2201
+ if (correlationId) {
2202
+ const v = metadata?.correlationId;
2203
+ if (typeof v !== "string" || v !== correlationId) return false;
2204
+ }
2205
+ if (groupId) {
2206
+ const v = metadata?.groupId;
2207
+ if (typeof v !== "string" || v !== groupId) return false;
2208
+ }
2209
+ return true;
2210
+ }
2211
+ async function buildTraceStats(metas, options) {
2212
+ let filtered = filterTraces(metas, { since: options.since });
2213
+ if (options.correlationId || options.groupId) {
2214
+ const next = [];
2215
+ for (const m of filtered) {
2216
+ const md = await readRunStartedMetadata(m.filePath);
2217
+ if (metaMatchesCorrelation(md, options.correlationId, options.groupId)) {
2218
+ next.push(m);
2219
+ }
2220
+ }
2221
+ filtered = next;
2222
+ }
2223
+ let successCount = 0;
2224
+ let errorCount = 0;
2225
+ let runningCount = 0;
2226
+ let unknownCount = 0;
2227
+ const durations = [];
2228
+ let totalSteps = 0;
2229
+ let totalLlmSteps = 0;
2230
+ let totalToolSteps = 0;
2231
+ let totalErrorSteps = 0;
2232
+ const slowestRuns = [];
2233
+ const slowestSteps = [];
2234
+ for (const m of filtered) {
2235
+ if (m.status === "success") successCount += 1;
2236
+ else if (m.status === "error") errorCount += 1;
2237
+ else if (m.status === "running") runningCount += 1;
2238
+ else unknownCount += 1;
2239
+ if (typeof m.durationMs === "number" && Number.isFinite(m.durationMs) && m.durationMs >= 0) {
2240
+ durations.push(m.durationMs);
2241
+ slowestRuns.push({
2242
+ runId: m.runId,
2243
+ name: m.name,
2244
+ durationMs: m.durationMs,
2245
+ status: m.status
2246
+ });
2247
+ }
2248
+ try {
2249
+ const events = await readTraceEvents(m.runId, options.traceDir);
2250
+ if (events.length === 0) continue;
2251
+ const summary = buildRunSummary(events);
2252
+ totalSteps += summary.totalSteps;
2253
+ totalLlmSteps += summary.llmSteps;
2254
+ totalToolSteps += summary.toolSteps;
2255
+ totalErrorSteps += summary.errorSteps;
2256
+ const steps = collectCompletedSteps(events, m.runId);
2257
+ for (const s of steps) {
2258
+ slowestSteps.push(s);
2259
+ }
2260
+ } catch {
2261
+ }
2262
+ }
2263
+ slowestRuns.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
2264
+ slowestSteps.sort((a, b) => b.durationMs - a.durationMs);
2265
+ const runLimit = options.slowRunLimit ?? 5;
2266
+ const stepLimit = options.slowStepLimit ?? 5;
2267
+ const sortedDur = [...durations].sort((a, b) => a - b);
2268
+ const totalRuns = filtered.length;
2269
+ const errorRate = totalRuns > 0 ? errorCount / totalRuns : 0;
2270
+ const sumDur = durations.reduce((a, b) => a + b, 0);
2271
+ return {
2272
+ traceDir: options.traceDir,
2273
+ ...options.since ? { since: options.since } : {},
2274
+ ...options.correlationId ? { correlationId: options.correlationId } : {},
2275
+ ...options.groupId ? { groupId: options.groupId } : {},
2276
+ totalRuns,
2277
+ successCount,
2278
+ errorCount,
2279
+ runningCount,
2280
+ unknownCount,
2281
+ errorRate,
2282
+ duration: {
2283
+ ...sortedDur.length > 0 ? {
2284
+ minMs: sortedDur[0],
2285
+ maxMs: sortedDur[sortedDur.length - 1],
2286
+ avgMs: sumDur / sortedDur.length,
2287
+ p50Ms: percentile(sortedDur, 50),
2288
+ p95Ms: percentile(sortedDur, 95)
2289
+ } : {}
2290
+ },
2291
+ totalSteps,
2292
+ avgStepsPerRun: totalRuns > 0 ? totalSteps / totalRuns : 0,
2293
+ totalLlmSteps,
2294
+ totalToolSteps,
2295
+ totalErrorSteps,
2296
+ slowestRuns: slowestRuns.slice(0, runLimit),
2297
+ slowestSteps: slowestSteps.slice(0, stepLimit)
2298
+ };
2299
+ }
2300
+ function collectCompletedSteps(events, runId) {
2301
+ const started = /* @__PURE__ */ new Map();
2302
+ const out = [];
2303
+ for (const e of events) {
2304
+ if (e.event === "step_started") {
2305
+ const s = e;
2306
+ started.set(s.stepId, { name: s.name, type: s.type });
2307
+ }
2308
+ if (e.event === "step_completed") {
2309
+ const c = e;
2310
+ if (c.status !== "success" && c.status !== "error") continue;
2311
+ if (typeof c.durationMs !== "number" || !Number.isFinite(c.durationMs)) {
2312
+ continue;
2313
+ }
2314
+ const meta = started.get(c.stepId);
2315
+ out.push({
2316
+ runId,
2317
+ stepName: meta?.name ?? c.stepId,
2318
+ stepType: meta?.type ?? "logic",
2319
+ durationMs: c.durationMs
2320
+ });
2321
+ }
2322
+ }
2323
+ return out;
2324
+ }
2325
+ function renderTraceStats(stats) {
2326
+ const lines = [];
2327
+ lines.push("Trace stats (local)");
2328
+ lines.push(`Directory: ${stats.traceDir}`);
2329
+ if (stats.since) lines.push(`Since: ${stats.since}`);
2330
+ if (stats.correlationId) lines.push(`Correlation ID: ${stats.correlationId}`);
2331
+ if (stats.groupId) lines.push(`Group ID: ${stats.groupId}`);
2332
+ lines.push("");
2333
+ lines.push(`Runs: ${stats.totalRuns}`);
2334
+ lines.push(
2335
+ ` success: ${stats.successCount} error: ${stats.errorCount} running: ${stats.runningCount} unknown: ${stats.unknownCount}`
2336
+ );
2337
+ lines.push(`Error rate: ${(stats.errorRate * 100).toFixed(1)}%`);
2338
+ if (stats.duration.avgMs !== void 0) {
2339
+ lines.push(
2340
+ `Duration: min ${formatDuration2(stats.duration.minMs ?? 0)} | avg ${formatDuration2(stats.duration.avgMs)} | p50 ${formatDuration2(stats.duration.p50Ms ?? 0)} | p95 ${formatDuration2(stats.duration.p95Ms ?? 0)} | max ${formatDuration2(stats.duration.maxMs ?? 0)}`
2341
+ );
2342
+ }
2343
+ lines.push("");
2344
+ lines.push(`Steps: ${stats.totalSteps} (avg ${stats.avgStepsPerRun.toFixed(1)} per run)`);
2345
+ lines.push(
2346
+ ` LLM: ${stats.totalLlmSteps} tool: ${stats.totalToolSteps} errors: ${stats.totalErrorSteps}`
2347
+ );
2348
+ if (stats.slowestRuns.length > 0) {
2349
+ lines.push("");
2350
+ lines.push("Slowest runs:");
2351
+ for (const r of stats.slowestRuns) {
2352
+ lines.push(
2353
+ ` ${r.runId} | ${r.name ?? "-"} | ${formatDuration2(r.durationMs ?? 0)} | ${r.status}`
2354
+ );
2355
+ }
2356
+ }
2357
+ if (stats.slowestSteps.length > 0) {
2358
+ lines.push("");
2359
+ lines.push("Slowest steps:");
2360
+ for (const s of stats.slowestSteps) {
2361
+ lines.push(
2362
+ ` ${s.runId} | ${s.stepType}:${s.stepName} | ${formatDuration2(s.durationMs)}`
2363
+ );
2364
+ }
2365
+ }
2366
+ return lines.join("\n");
2367
+ }
2368
+
2369
+ // packages/core/src/search.ts
2370
+ function parseDurationFilter(expr) {
2371
+ const raw = expr.trim();
2372
+ const m = raw.match(/^(>=|<=|>|<)\s*(.+)$/);
2373
+ if (!m) {
2374
+ throw new Error(
2375
+ `Invalid --duration "${expr}". Use forms like >5s, >=500ms, <2m.`
2376
+ );
2377
+ }
2378
+ const op = m[1];
2379
+ const ms = parseDuration(m[2].trim());
2380
+ return { op, ms };
2381
+ }
2382
+ function durationMatches(valueMs, filter) {
2383
+ if (valueMs === void 0 || !Number.isFinite(valueMs)) return false;
2384
+ switch (filter.op) {
2385
+ case ">":
2386
+ return valueMs > filter.ms;
2387
+ case ">=":
2388
+ return valueMs >= filter.ms;
2389
+ case "<":
2390
+ return valueMs < filter.ms;
2391
+ case "<=":
2392
+ return valueMs <= filter.ms;
2393
+ default:
2394
+ return false;
2395
+ }
2396
+ }
2397
+ function normalizeStepTypeFilter(kind, type) {
2398
+ const v = (kind ?? type)?.trim().toLowerCase();
2399
+ return v && v !== "" ? v : void 0;
2400
+ }
2401
+ function nameMatches(hay, needle) {
2402
+ return hay.toLowerCase().includes(needle.toLowerCase());
2403
+ }
2404
+ async function searchTraces(metas, options) {
2405
+ let filtered = filterTraces(metas, { since: options.since });
2406
+ const stepTypeFilter = normalizeStepTypeFilter(options.kind, options.type);
2407
+ const nameQuery = options.name?.trim();
2408
+ const toolQuery = options.tool?.trim();
2409
+ let durationFilter;
2410
+ if (options.duration) {
2411
+ durationFilter = parseDurationFilter(options.duration);
2412
+ }
2413
+ const limit = options.limit ?? 50;
2414
+ const hasContentFilter = Boolean(
2415
+ options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter
2416
+ );
2417
+ const results = [];
2418
+ if (!hasContentFilter) {
2419
+ for (const m of filtered) {
2420
+ results.push({
2421
+ runId: m.runId,
2422
+ runName: m.name,
2423
+ runStatus: m.status,
2424
+ timestamp: m.startedAt,
2425
+ durationMs: m.durationMs,
2426
+ matchReason: "trace in directory",
2427
+ matchedFields: ["run"],
2428
+ filePath: m.filePath
2429
+ });
2430
+ }
2431
+ return results.slice(0, limit);
2432
+ }
2433
+ for (const m of filtered) {
2434
+ if (options.status && m.status !== options.status) continue;
2435
+ let events = [];
2436
+ try {
2437
+ events = await readTraceEvents(m.runId, options.traceDir);
2438
+ } catch {
2439
+ continue;
2440
+ }
2441
+ if (events.length === 0) continue;
2442
+ const runMatches = matchRunLevel(m, {
2443
+ stepTypeFilter,
2444
+ nameQuery,
2445
+ toolQuery,
2446
+ durationFilter,
2447
+ statusFilter: options.status
2448
+ });
2449
+ results.push(...runMatches);
2450
+ const stepMatches = matchStepLevel(m, events, {
2451
+ stepTypeFilter,
2452
+ nameQuery,
2453
+ toolQuery,
2454
+ durationFilter,
2455
+ statusFilter: options.status
2456
+ });
2457
+ results.push(...stepMatches);
2458
+ }
2459
+ results.sort((a, b) => {
2460
+ const ta = a.timestamp ?? 0;
2461
+ const tb = b.timestamp ?? 0;
2462
+ if (ta !== tb) return ta - tb;
2463
+ const runCmp = a.runId.localeCompare(b.runId);
2464
+ if (runCmp !== 0) return runCmp;
2465
+ return (a.stepName ?? "").localeCompare(b.stepName ?? "");
2466
+ });
2467
+ return results.slice(0, limit);
2468
+ }
2469
+ function matchRunLevel(m, opts) {
2470
+ if (opts.stepTypeFilter || opts.toolQuery) return [];
2471
+ const out = [];
2472
+ const fields = [];
2473
+ if (opts.statusFilter && m.status === opts.statusFilter) {
2474
+ fields.push("run.status");
2475
+ }
2476
+ if (opts.nameQuery && nameMatches(m.name ?? m.runId, opts.nameQuery)) {
2477
+ fields.push("run.name");
2478
+ }
2479
+ if (opts.durationFilter && durationMatches(m.durationMs, opts.durationFilter)) {
2480
+ fields.push("run.durationMs");
2481
+ }
2482
+ if (fields.length === 0) return out;
2483
+ out.push({
2484
+ runId: m.runId,
2485
+ runName: m.name,
2486
+ runStatus: m.status,
2487
+ timestamp: m.startedAt,
2488
+ durationMs: m.durationMs,
2489
+ matchReason: `run match: ${fields.join(", ")}`,
2490
+ matchedFields: fields,
2491
+ filePath: m.filePath
2492
+ });
2493
+ return out;
2494
+ }
2495
+ function matchStepLevel(m, events, opts) {
2496
+ const out = [];
2497
+ const started = /* @__PURE__ */ new Map();
2498
+ for (const e of events) {
2499
+ if (e.event === "step_started") {
2500
+ started.set(e.stepId, e);
2501
+ }
2502
+ }
2503
+ for (const e of events) {
2504
+ if (e.event !== "step_completed") continue;
2505
+ const c = e;
2506
+ const s = started.get(c.stepId);
2507
+ if (!s) continue;
2508
+ const fields = [];
2509
+ const stepType = s.type;
2510
+ if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
2511
+ continue;
2512
+ }
2513
+ const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
2514
+ if (!hasStepFilters) continue;
2515
+ if (opts.statusFilter === "error" && c.status === "error") {
2516
+ fields.push("step.status");
2517
+ } else if (opts.statusFilter === "success" && c.status === "success") {
2518
+ fields.push("step.status");
2519
+ } else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
2520
+ continue;
2521
+ }
2522
+ if (opts.nameQuery) {
2523
+ if (!nameMatches(s.name, opts.nameQuery)) continue;
2524
+ fields.push("step.name");
2525
+ }
2526
+ if (opts.toolQuery) {
2527
+ const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
2528
+ if (!nameMatches(toolName, opts.toolQuery)) continue;
2529
+ fields.push("step.tool");
2530
+ }
2531
+ if (opts.durationFilter) {
2532
+ if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
2533
+ fields.push("step.durationMs");
2534
+ }
2535
+ if (opts.stepTypeFilter) {
2536
+ fields.push("step.type");
2537
+ }
2538
+ if (fields.length === 0) continue;
2539
+ out.push({
2540
+ runId: m.runId,
2541
+ runName: m.name,
2542
+ runStatus: m.status,
2543
+ stepId: c.stepId,
2544
+ stepName: s.name,
2545
+ stepType,
2546
+ timestamp: s.startTime ?? s.timestamp,
2547
+ durationMs: c.durationMs,
2548
+ matchReason: `step match: ${fields.join(", ")}`,
2549
+ matchedFields: fields,
2550
+ filePath: m.filePath
2551
+ });
2552
+ }
2553
+ return out;
2554
+ }
2555
+ async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
2556
+ const metas = [];
2557
+ for (const fileName of fileNames) {
2558
+ try {
2559
+ const filePath = getPath(fileName);
2560
+ const meta = await extractMetadata(filePath);
2561
+ metas.push(meta);
2562
+ } catch {
2563
+ }
2564
+ }
2565
+ return metas;
2566
+ }
1889
2567
  var KNOWN_EVENTS = /* @__PURE__ */ new Set([
1890
2568
  "run_started",
1891
2569
  "run_completed",
@@ -3207,6 +3885,134 @@ Object.assign(stepImpl, {
3207
3885
  // packages/core/src/exporters/types.ts
3208
3886
  var EXPORT_PAYLOAD_VERSION = "0.1.2";
3209
3887
 
3888
+ // packages/core/src/exporters/redact-export.ts
3889
+ function isRecord9(value) {
3890
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3891
+ }
3892
+ function deepClone(value) {
3893
+ if (value === null || typeof value !== "object") {
3894
+ return value;
3895
+ }
3896
+ if (Array.isArray(value)) {
3897
+ return value.map((item) => deepClone(item));
3898
+ }
3899
+ const out = {};
3900
+ for (const [k, v] of Object.entries(value)) {
3901
+ out[k] = deepClone(v);
3902
+ }
3903
+ return out;
3904
+ }
3905
+ function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
3906
+ if (depth > 32) {
3907
+ return { truncated: true, reason: "maxDepth" };
3908
+ }
3909
+ const out = {};
3910
+ for (const [key, value] of Object.entries(record)) {
3911
+ out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
3912
+ }
3913
+ return out;
3914
+ }
3915
+ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
3916
+ if (value === null || typeof value !== "object") {
3917
+ if (typeof value === "string") {
3918
+ return truncateStringForProfile(
3919
+ value,
3920
+ key,
3921
+ maxMetadataValueLength,
3922
+ maxPreviewLength
3923
+ );
3924
+ }
3925
+ return value;
3926
+ }
3927
+ if (seen.has(value)) return "[Circular]";
3928
+ seen.add(value);
3929
+ if (Array.isArray(value)) {
3930
+ return value.slice(0, 50).map(
3931
+ (item, index) => boundValue(
3932
+ item,
3933
+ String(index),
3934
+ maxMetadataValueLength,
3935
+ maxPreviewLength,
3936
+ seen,
3937
+ depth + 1
3938
+ )
3939
+ );
3940
+ }
3941
+ return boundAttributeValues(
3942
+ value,
3943
+ maxMetadataValueLength,
3944
+ maxPreviewLength,
3945
+ seen,
3946
+ depth + 1
3947
+ );
3948
+ }
3949
+ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPreviewLength) {
3950
+ if (!attrs || Object.keys(attrs).length === 0) {
3951
+ return attrs;
3952
+ }
3953
+ const redacted = redactor.redactRecord(attrs);
3954
+ const seen = /* @__PURE__ */ new WeakSet();
3955
+ const bounded = boundAttributeValues(
3956
+ redacted,
3957
+ maxMetadataValueLength,
3958
+ maxPreviewLength,
3959
+ seen,
3960
+ 0
3961
+ );
3962
+ const err = bounded.error;
3963
+ if (isRecord9(err) && typeof err.message === "string") {
3964
+ bounded.error = {
3965
+ ...err,
3966
+ message: truncateStringForProfile(
3967
+ err.message,
3968
+ "message",
3969
+ maxMetadataValueLength,
3970
+ maxPreviewLength
3971
+ ),
3972
+ ...typeof err.stack === "string" ? {
3973
+ stack: truncateStringForProfile(
3974
+ err.stack,
3975
+ "stack",
3976
+ maxMetadataValueLength,
3977
+ maxPreviewLength
3978
+ )
3979
+ } : {}
3980
+ };
3981
+ }
3982
+ return bounded;
3983
+ }
3984
+ function redactRunTreeForExport(tree, options) {
3985
+ const profile = options?.redactionProfile ?? "local";
3986
+ if (profile === "local") {
3987
+ return deepClone(tree);
3988
+ }
3989
+ const resolved = resolveRedactionProfile(profile);
3990
+ const { maxMetadataValueLength, maxPreviewLength } = applyProfileMetadataCaps(
3991
+ 2e3,
3992
+ 500,
3993
+ resolved
3994
+ );
3995
+ const redactor = new Redactor({ extraKeys: resolved.extraKeys });
3996
+ const clone = deepClone(tree);
3997
+ function walk(nodes) {
3998
+ for (const node of nodes) {
3999
+ if (node.event.attributes !== void 0) {
4000
+ node.event.attributes = redactEventAttributes(
4001
+ node.event.attributes,
4002
+ redactor,
4003
+ maxMetadataValueLength,
4004
+ maxPreviewLength
4005
+ );
4006
+ }
4007
+ if (node.children.length > 0) {
4008
+ walk(node.children);
4009
+ }
4010
+ }
4011
+ }
4012
+ walk(clone.children);
4013
+ return clone;
4014
+ }
4015
+
3210
4016
  // packages/core/src/exporters/html-exporter.ts
3211
4017
  function renderTreeHtml(nodes, ulClass = "tree") {
3212
4018
  if (nodes.length === 0) return "";
@@ -3941,20 +4747,22 @@ function mergeExportDefaults(options) {
3941
4747
  includeErrors: options.includeErrors ?? true,
3942
4748
  pretty: options.pretty,
3943
4749
  redacted: options.redacted,
3944
- maxAttributeLength: options.maxAttributeLength
4750
+ maxAttributeLength: options.maxAttributeLength,
4751
+ redactionProfile: options.redactionProfile ?? "local"
3945
4752
  };
3946
4753
  }
3947
4754
  function exportRunTree(tree, options) {
3948
4755
  const opts = mergeExportDefaults(options);
4756
+ const exportTree = opts.redactionProfile === "local" ? tree : redactRunTreeForExport(tree, { redactionProfile: opts.redactionProfile });
3949
4757
  switch (opts.format) {
3950
4758
  case "markdown":
3951
- return exportMarkdown(tree, opts);
4759
+ return exportMarkdown(exportTree, opts);
3952
4760
  case "html":
3953
- return exportHtml(tree, opts);
4761
+ return exportHtml(exportTree, opts);
3954
4762
  case "openinference":
3955
- return exportOpenInference(tree, opts);
4763
+ return exportOpenInference(exportTree, opts);
3956
4764
  case "otlp-json":
3957
- return exportOtlpJson(tree, opts);
4765
+ return exportOtlpJson(exportTree, opts);
3958
4766
  default: {
3959
4767
  const _x = opts.format;
3960
4768
  throw new Error(`Unsupported export format: ${String(_x)}`);
@@ -4790,6 +5598,15 @@ async function tail(options = {}) {
4790
5598
  process.exitCode = 1;
4791
5599
  }
4792
5600
  }
5601
+ function parseRedactionProfile(s) {
5602
+ const v = (s ?? "local").trim().toLowerCase();
5603
+ if (v === "local" || v === "share" || v === "strict") {
5604
+ return v;
5605
+ }
5606
+ throw new Error(
5607
+ `Unsupported --redaction-profile "${s ?? ""}". Use local, share, or strict.`
5608
+ );
5609
+ }
4793
5610
  function parseExportFormat(s) {
4794
5611
  const v = (s ?? "markdown").trim().toLowerCase();
4795
5612
  if (v === "markdown" || v === "html" || v === "openinference" || v === "otlp-json") {
@@ -4807,8 +5624,10 @@ async function exportCommand(runId, options = {}) {
4807
5624
  return;
4808
5625
  }
4809
5626
  let format;
5627
+ let redactionProfile;
4810
5628
  try {
4811
5629
  format = parseExportFormat(options.format);
5630
+ redactionProfile = parseRedactionProfile(options.redactionProfile);
4812
5631
  } catch (e) {
4813
5632
  const msg = e instanceof Error ? e.message : String(e);
4814
5633
  console.error(msg);
@@ -4847,7 +5666,8 @@ Trace directory: ${traceDir}`);
4847
5666
  includeErrors: options.noErrors === true ? false : true,
4848
5667
  pretty: true,
4849
5668
  redacted: true,
4850
- maxAttributeLength: 500
5669
+ maxAttributeLength: 500,
5670
+ redactionProfile
4851
5671
  };
4852
5672
  const result = exportRunTree(tree, exportOpts);
4853
5673
  const validation = options.validate === true ? validateExport(result) : void 0;
@@ -4996,6 +5816,163 @@ Trace directory: ${traceDir}`
4996
5816
  );
4997
5817
  }
4998
5818
 
5819
+ // packages/cli/src/timeline.ts
5820
+ async function timelineCommand(runId, options = {}) {
5821
+ const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
5822
+ if (id === "") {
5823
+ console.error("Run id is required");
5824
+ process.exitCode = 1;
5825
+ return;
5826
+ }
5827
+ const traceDir = resolveTraceDir({ dir: options.dir });
5828
+ let events;
5829
+ try {
5830
+ events = await readTraceEvents(id, traceDir);
5831
+ } catch (e) {
5832
+ const msg = e instanceof Error ? e.message : String(e);
5833
+ console.error(`[AgentInspect] timeline failed: ${msg}`);
5834
+ process.exitCode = 1;
5835
+ return;
5836
+ }
5837
+ if (events.length === 0) {
5838
+ console.log(`Run not found: ${id}`);
5839
+ console.log(`Trace directory: ${traceDir}`);
5840
+ process.exitCode = 1;
5841
+ return;
5842
+ }
5843
+ const focus = options.focus?.trim().toLowerCase() === "slow" ? "slow" : "all";
5844
+ const timeline = buildRunTimeline(events, {
5845
+ focus: focus === "slow" ? "slow" : "all"
5846
+ });
5847
+ if (options.json) {
5848
+ console.log(JSON.stringify(timeline, null, 2));
5849
+ return;
5850
+ }
5851
+ console.log(renderTimeline(timeline, { focus }));
5852
+ }
5853
+
5854
+ // packages/cli/src/stats.ts
5855
+ async function statsCommand(options = {}) {
5856
+ try {
5857
+ const traceDir = resolveTraceDir({ dir: options.dir });
5858
+ const td = new TraceDirectory({ dir: traceDir });
5859
+ if (typeof options.since === "string" && options.since.trim() !== "") {
5860
+ parseDuration(options.since.trim());
5861
+ }
5862
+ const files = await td.list();
5863
+ if (files.length === 0) {
5864
+ if (options.json) {
5865
+ console.log(
5866
+ JSON.stringify({
5867
+ traceDir,
5868
+ totalRuns: 0,
5869
+ successCount: 0,
5870
+ errorCount: 0,
5871
+ runningCount: 0,
5872
+ unknownCount: 0,
5873
+ errorRate: 0,
5874
+ duration: {},
5875
+ totalSteps: 0,
5876
+ avgStepsPerRun: 0,
5877
+ totalLlmSteps: 0,
5878
+ totalToolSteps: 0,
5879
+ totalErrorSteps: 0,
5880
+ slowestRuns: [],
5881
+ slowestSteps: []
5882
+ })
5883
+ );
5884
+ } else {
5885
+ console.log("No AgentInspect runs found");
5886
+ console.log(`Trace directory: ${traceDir}`);
5887
+ }
5888
+ return;
5889
+ }
5890
+ const metas = [];
5891
+ for (const fileName of files) {
5892
+ try {
5893
+ metas.push(await extractMetadata(td.getPath(fileName)));
5894
+ } catch {
5895
+ }
5896
+ }
5897
+ const stats = await buildTraceStats(metas, {
5898
+ traceDir,
5899
+ since: options.since,
5900
+ correlationId: options.correlationId,
5901
+ groupId: options.groupId
5902
+ });
5903
+ if (options.json) {
5904
+ console.log(JSON.stringify(stats, null, 2));
5905
+ return;
5906
+ }
5907
+ console.log(renderTraceStats(stats));
5908
+ } catch (e) {
5909
+ const msg = e instanceof Error ? e.message : String(e);
5910
+ console.error(`[AgentInspect] stats failed: ${msg}`);
5911
+ process.exitCode = 1;
5912
+ }
5913
+ }
5914
+
5915
+ // packages/cli/src/search.ts
5916
+ function parseLimit2(raw) {
5917
+ if (raw === void 0 || raw.trim() === "") return 50;
5918
+ const n = Number.parseInt(raw, 10);
5919
+ if (!Number.isFinite(n) || n <= 0) return 50;
5920
+ return Math.min(n, 500);
5921
+ }
5922
+ async function searchCommand(options = {}) {
5923
+ try {
5924
+ const traceDir = resolveTraceDir({ dir: options.dir });
5925
+ const td = new TraceDirectory({ dir: traceDir });
5926
+ if (typeof options.since === "string" && options.since.trim() !== "") {
5927
+ parseDuration(options.since.trim());
5928
+ }
5929
+ if (options.duration) {
5930
+ parseDurationFilter(options.duration);
5931
+ }
5932
+ const files = await td.list();
5933
+ const metas = await loadTraceMetadataList(
5934
+ traceDir,
5935
+ files,
5936
+ (f) => td.getPath(f)
5937
+ );
5938
+ const status = options.status === "success" || options.status === "error" || options.status === "running" || options.status === "unknown" ? options.status : void 0;
5939
+ const results = await searchTraces(metas, {
5940
+ traceDir,
5941
+ since: options.since,
5942
+ status,
5943
+ kind: options.kind,
5944
+ type: options.type,
5945
+ name: options.name,
5946
+ tool: options.tool,
5947
+ duration: options.duration,
5948
+ limit: parseLimit2(options.limit)
5949
+ });
5950
+ if (options.json) {
5951
+ console.log(JSON.stringify(results, null, 2));
5952
+ return;
5953
+ }
5954
+ if (results.length === 0) {
5955
+ console.log("No matching traces found");
5956
+ console.log(`Trace directory: ${traceDir}`);
5957
+ return;
5958
+ }
5959
+ console.log(`Search results (${results.length})`);
5960
+ for (const r of results) {
5961
+ const step2 = r.stepName !== void 0 ? ` | ${r.stepType ?? "step"}:${r.stepName}` : "";
5962
+ const dur = r.durationMs !== void 0 ? ` | ${r.durationMs}ms` : "";
5963
+ console.log(
5964
+ `${r.runId}${step2} | ${r.runStatus}${dur} | ${r.matchReason}`
5965
+ );
5966
+ }
5967
+ console.log("");
5968
+ console.log(`Trace directory: ${traceDir}`);
5969
+ } catch (e) {
5970
+ const msg = e instanceof Error ? e.message : String(e);
5971
+ console.error(`[AgentInspect] search failed: ${msg}`);
5972
+ process.exitCode = 1;
5973
+ }
5974
+ }
5975
+
4999
5976
  // packages/cli/src/index.ts
5000
5977
  function runCommand(action) {
5001
5978
  void action().catch((error) => {
@@ -5080,7 +6057,12 @@ function createCliProgram() {
5080
6057
  "openinference",
5081
6058
  "otlp-json"
5082
6059
  ])
5083
- ).option("-o, --output <path>", "write export to file (creates parent dirs)").option("--json", "emit JSON wrapper about the export (includes content when writing to stdout)").option("--validate", "validate exported payload shape after generation").option("--include-attributes", "include bounded attributes (review before sharing)").option("--no-metadata", "omit summary / metadata sections").option("--no-errors", "omit error sections").action((runId, opts) => {
6060
+ ).option("-o, --output <path>", "write export to file (creates parent dirs)").option("--json", "emit JSON wrapper about the export (includes content when writing to stdout)").option("--validate", "validate exported payload shape after generation").option("--include-attributes", "include bounded attributes (review before sharing)").option("--no-metadata", "omit summary / metadata sections").option("--no-errors", "omit error sections").addOption(
6061
+ new Option(
6062
+ "--redaction-profile <profile>",
6063
+ "redaction profile for exported copies: local, share, strict (default: local)"
6064
+ ).choices(["local", "share", "strict"])
6065
+ ).action((runId, opts) => {
5084
6066
  runCommand(() => exportCommand(runId, opts));
5085
6067
  });
5086
6068
  program.command("diff").description("Compare two local AgentInspect JSONL traces (read-only)").argument("<left-run-id>", "first run id").argument("<right-run-id>", "second run id").option("--dir <path>", "trace directory").option("--json", "print diff result as JSON").option("--ignore-duration", "omit duration comparisons").option(
@@ -5104,6 +6086,29 @@ function createCliProgram() {
5104
6086
  ).option("--verbose", "show more left/right detail").action((leftRunId, rightRunId, opts) => {
5105
6087
  runCommand(() => diffCommand(leftRunId, rightRunId, opts));
5106
6088
  });
6089
+ program.command("timeline").description("Chronological timeline for a single run (local JSONL)").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").option("--json", "print timeline as JSON").addOption(
6090
+ new Option("--focus <mode>", "highlight slowest steps by duration").choices([
6091
+ "slow"
6092
+ ])
6093
+ ).action((runId, opts) => {
6094
+ runCommand(() => timelineCommand(runId, opts));
6095
+ });
6096
+ program.command("stats").description("Local aggregate stats over trace files (read-only)").option("--dir <path>", "trace directory").option("--since <duration>", "only include runs since a duration (e.g. 7d)").option("--correlation-id <id>", "filter by run_started.metadata.correlationId").option("--group-id <id>", "filter by run_started.metadata.groupId").option("--json", "print stats as JSON").action((opts) => {
6097
+ runCommand(() => statsCommand(opts));
6098
+ });
6099
+ program.command("search").description("Deterministic local search over trace files (read-only)").option("--dir <path>", "trace directory").option("--since <duration>", "only search runs since a duration").addOption(
6100
+ new Option("--status <status>", "filter by run or step status").choices([
6101
+ "success",
6102
+ "error",
6103
+ "running",
6104
+ "unknown"
6105
+ ])
6106
+ ).option("--kind <kind>", "filter by step kind/type (llm, tool, logic, \u2026)").option("--type <type>", "alias for --kind on manual trace step type").option("--name <query>", "substring match on run or step name").option("--tool <query>", "substring match on tool step name or metadata.toolName").option(
6107
+ "--duration <expr>",
6108
+ "duration filter on run or step (e.g. >5s, >=500ms)"
6109
+ ).option("--limit <number>", "max results (default 50)").option("--json", "print results as JSON").action((opts) => {
6110
+ runCommand(() => searchCommand(opts));
6111
+ });
5107
6112
  return program;
5108
6113
  }
5109
6114
  function isPrimaryModule() {