agent-inspect 1.3.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.
- package/CHANGELOG.md +19 -4
- package/README.md +39 -37
- package/docs/ADAPTERS.md +111 -31
- package/docs/API.md +15 -5
- package/docs/CLI.md +62 -1
- package/docs/EXPORTS.md +73 -15
- package/docs/GETTING-STARTED.md +29 -1
- package/docs/KNOWN-ISSUES.md +6 -0
- package/docs/LIMITATIONS.md +8 -2
- package/docs/SCHEMA.md +5 -4
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +748 -0
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +748 -0
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/core/dist/index.cjs +575 -0
- package/packages/core/dist/index.cjs.map +1 -1
- package/packages/core/dist/index.d.cts +126 -1
- package/packages/core/dist/index.d.ts +126 -1
- package/packages/core/dist/index.mjs +569 -1
- package/packages/core/dist/index.mjs.map +1 -1
|
@@ -2007,6 +2007,574 @@ function filterTraces(traces, options) {
|
|
|
2007
2007
|
}
|
|
2008
2008
|
return out;
|
|
2009
2009
|
}
|
|
2010
|
+
|
|
2011
|
+
// packages/core/src/timeline.ts
|
|
2012
|
+
function finite(n) {
|
|
2013
|
+
return typeof n === "number" && Number.isFinite(n);
|
|
2014
|
+
}
|
|
2015
|
+
function pickStreamingMeta(metadata) {
|
|
2016
|
+
if (!metadata || typeof metadata !== "object") return void 0;
|
|
2017
|
+
const chunkCount = metadata.chunkCount;
|
|
2018
|
+
const streamDurationMs = metadata.streamDurationMs;
|
|
2019
|
+
const streamedCharCount = metadata.streamedCharCount;
|
|
2020
|
+
if (!finite(chunkCount) && !finite(streamDurationMs) && !finite(streamedCharCount)) {
|
|
2021
|
+
return void 0;
|
|
2022
|
+
}
|
|
2023
|
+
return {
|
|
2024
|
+
...finite(chunkCount) ? { chunkCount } : {},
|
|
2025
|
+
...finite(streamDurationMs) ? { streamDurationMs } : {},
|
|
2026
|
+
...finite(streamedCharCount) ? { streamedCharCount } : {}
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
function pickCorrelation(metadata) {
|
|
2030
|
+
if (!metadata || typeof metadata !== "object") return void 0;
|
|
2031
|
+
const out = {};
|
|
2032
|
+
for (const key of [
|
|
2033
|
+
"correlationId",
|
|
2034
|
+
"requestId",
|
|
2035
|
+
"decisionId",
|
|
2036
|
+
"groupId"
|
|
2037
|
+
]) {
|
|
2038
|
+
const v = metadata[key];
|
|
2039
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
2040
|
+
out[key] = v;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2044
|
+
}
|
|
2045
|
+
function buildRunTimeline(events, options = {}) {
|
|
2046
|
+
const started = events.find(
|
|
2047
|
+
(e) => e.event === "run_started"
|
|
2048
|
+
);
|
|
2049
|
+
const completed = events.filter(
|
|
2050
|
+
(e) => e.event === "run_completed"
|
|
2051
|
+
);
|
|
2052
|
+
const lastCompleted = completed[completed.length - 1];
|
|
2053
|
+
const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
|
|
2054
|
+
const runStart = started && finite(started.startTime) ? started.startTime : started && finite(started.timestamp) ? started.timestamp : void 0;
|
|
2055
|
+
const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
|
|
2056
|
+
const steps = /* @__PURE__ */ new Map();
|
|
2057
|
+
for (const e of events) {
|
|
2058
|
+
if (e.event === "step_started") {
|
|
2059
|
+
const s = e;
|
|
2060
|
+
steps.set(s.stepId, {
|
|
2061
|
+
name: s.name,
|
|
2062
|
+
type: s.type,
|
|
2063
|
+
parentId: s.parentId,
|
|
2064
|
+
startedAt: finite(s.startTime) ? s.startTime : s.timestamp,
|
|
2065
|
+
status: "running",
|
|
2066
|
+
metadata: s.metadata
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
for (const e of events) {
|
|
2071
|
+
if (e.event !== "step_completed") continue;
|
|
2072
|
+
const c = e;
|
|
2073
|
+
const node = steps.get(c.stepId);
|
|
2074
|
+
if (!node) continue;
|
|
2075
|
+
node.status = c.status;
|
|
2076
|
+
if (finite(c.durationMs)) node.durationMs = c.durationMs;
|
|
2077
|
+
}
|
|
2078
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
2079
|
+
const computeDepth = (stepId) => {
|
|
2080
|
+
const cached = depthCache.get(stepId);
|
|
2081
|
+
if (cached !== void 0) return cached;
|
|
2082
|
+
const node = steps.get(stepId);
|
|
2083
|
+
if (!node) return 0;
|
|
2084
|
+
const parent = node.parentId;
|
|
2085
|
+
if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
|
|
2086
|
+
depthCache.set(stepId, 0);
|
|
2087
|
+
return 0;
|
|
2088
|
+
}
|
|
2089
|
+
const d = Math.min(1e3, computeDepth(parent) + 1);
|
|
2090
|
+
depthCache.set(stepId, d);
|
|
2091
|
+
return d;
|
|
2092
|
+
};
|
|
2093
|
+
const entries = [];
|
|
2094
|
+
for (const [stepId, s] of steps.entries()) {
|
|
2095
|
+
const offsetMs = runStart !== void 0 && finite(s.startedAt) ? Math.max(0, s.startedAt - runStart) : 0;
|
|
2096
|
+
entries.push({
|
|
2097
|
+
stepId,
|
|
2098
|
+
name: s.name,
|
|
2099
|
+
type: s.type,
|
|
2100
|
+
status: s.status,
|
|
2101
|
+
depth: computeDepth(stepId),
|
|
2102
|
+
startedAt: s.startedAt,
|
|
2103
|
+
offsetMs,
|
|
2104
|
+
durationMs: s.durationMs,
|
|
2105
|
+
isError: s.status === "error",
|
|
2106
|
+
streaming: pickStreamingMeta(s.metadata)
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
entries.sort((a, b) => a.startedAt - b.startedAt);
|
|
2110
|
+
const slowTopN = options.slowTopN ?? 3;
|
|
2111
|
+
if (options.focus === "slow" && entries.length > 0) {
|
|
2112
|
+
const ranked = [...entries].filter((e) => finite(e.durationMs)).sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
|
|
2113
|
+
const slowIds = new Set(
|
|
2114
|
+
ranked.slice(0, slowTopN).map((e) => e.stepId)
|
|
2115
|
+
);
|
|
2116
|
+
for (const e of entries) {
|
|
2117
|
+
if (slowIds.has(e.stepId)) e.slow = true;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return {
|
|
2121
|
+
runId,
|
|
2122
|
+
name: typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0,
|
|
2123
|
+
status,
|
|
2124
|
+
startedAt: runStart,
|
|
2125
|
+
endedAt: lastCompleted && finite(lastCompleted.endTime) ? lastCompleted.endTime : void 0,
|
|
2126
|
+
durationMs: lastCompleted && finite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0,
|
|
2127
|
+
correlation: pickCorrelation(
|
|
2128
|
+
started?.metadata
|
|
2129
|
+
),
|
|
2130
|
+
entries
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
function renderTimeline(timeline, options = {}) {
|
|
2134
|
+
const lines = [];
|
|
2135
|
+
lines.push(`Timeline: ${timeline.name ?? timeline.runId}`);
|
|
2136
|
+
lines.push(`Run ID: ${timeline.runId}`);
|
|
2137
|
+
lines.push(`Status: ${timeline.status}`);
|
|
2138
|
+
if (timeline.startedAt !== void 0) {
|
|
2139
|
+
lines.push(`Started: ${formatTimestamp(timeline.startedAt)}`);
|
|
2140
|
+
}
|
|
2141
|
+
if (timeline.durationMs !== void 0) {
|
|
2142
|
+
lines.push(`Duration: ${formatDuration2(timeline.durationMs)}`);
|
|
2143
|
+
}
|
|
2144
|
+
if (timeline.correlation) {
|
|
2145
|
+
const parts = Object.entries(timeline.correlation).filter(([, v]) => typeof v === "string").map(([k, v]) => `${k}=${v}`);
|
|
2146
|
+
if (parts.length > 0) {
|
|
2147
|
+
lines.push(`Correlation: ${parts.join(", ")}`);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
lines.push("");
|
|
2151
|
+
lines.push("Steps (chronological):");
|
|
2152
|
+
const show = timeline.entries.filter((e) => {
|
|
2153
|
+
if (options.focus === "slow") return e.slow === true;
|
|
2154
|
+
return true;
|
|
2155
|
+
});
|
|
2156
|
+
if (show.length === 0) {
|
|
2157
|
+
lines.push(
|
|
2158
|
+
options.focus === "slow" ? "(no steps with duration for slow focus)" : "(no steps)"
|
|
2159
|
+
);
|
|
2160
|
+
return lines.join("\n");
|
|
2161
|
+
}
|
|
2162
|
+
for (const e of show) {
|
|
2163
|
+
const prefix = e.slow ? "[slow] " : "";
|
|
2164
|
+
const typeTag = e.type === "llm" ? "llm" : e.type === "tool" ? "tool" : e.type;
|
|
2165
|
+
const dur = e.durationMs !== void 0 ? formatDuration2(e.durationMs) : "-";
|
|
2166
|
+
const err = e.isError ? " error" : "";
|
|
2167
|
+
const off = formatDuration2(e.offsetMs);
|
|
2168
|
+
let line = `${prefix}+${off} ${typeTag}:${e.name} (${dur})${err}`;
|
|
2169
|
+
if (e.streaming?.chunkCount !== void 0) {
|
|
2170
|
+
line += ` chunks=${e.streaming.chunkCount}`;
|
|
2171
|
+
}
|
|
2172
|
+
if (e.streaming?.streamDurationMs !== void 0) {
|
|
2173
|
+
line += ` stream=${formatDuration2(e.streaming.streamDurationMs)}`;
|
|
2174
|
+
}
|
|
2175
|
+
lines.push(line);
|
|
2176
|
+
}
|
|
2177
|
+
return lines.join("\n");
|
|
2178
|
+
}
|
|
2179
|
+
function percentile(sorted, p) {
|
|
2180
|
+
if (sorted.length === 0) return void 0;
|
|
2181
|
+
const idx = Math.min(
|
|
2182
|
+
sorted.length - 1,
|
|
2183
|
+
Math.max(0, Math.ceil(p / 100 * sorted.length) - 1)
|
|
2184
|
+
);
|
|
2185
|
+
return sorted[idx];
|
|
2186
|
+
}
|
|
2187
|
+
async function readRunStartedMetadata(filePath) {
|
|
2188
|
+
try {
|
|
2189
|
+
const raw = await promises.readFile(filePath, "utf-8");
|
|
2190
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
2191
|
+
const trimmed = line.trim();
|
|
2192
|
+
if (trimmed === "") continue;
|
|
2193
|
+
let parsed;
|
|
2194
|
+
try {
|
|
2195
|
+
parsed = JSON.parse(trimmed);
|
|
2196
|
+
} catch {
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
if (!isTraceEvent(parsed)) continue;
|
|
2200
|
+
if (parsed.event !== "run_started") continue;
|
|
2201
|
+
const rs = parsed;
|
|
2202
|
+
if (rs.metadata && typeof rs.metadata === "object") {
|
|
2203
|
+
return rs.metadata;
|
|
2204
|
+
}
|
|
2205
|
+
return void 0;
|
|
2206
|
+
}
|
|
2207
|
+
} catch {
|
|
2208
|
+
}
|
|
2209
|
+
return void 0;
|
|
2210
|
+
}
|
|
2211
|
+
function metaMatchesCorrelation(metadata, correlationId, groupId) {
|
|
2212
|
+
if (correlationId) {
|
|
2213
|
+
const v = metadata?.correlationId;
|
|
2214
|
+
if (typeof v !== "string" || v !== correlationId) return false;
|
|
2215
|
+
}
|
|
2216
|
+
if (groupId) {
|
|
2217
|
+
const v = metadata?.groupId;
|
|
2218
|
+
if (typeof v !== "string" || v !== groupId) return false;
|
|
2219
|
+
}
|
|
2220
|
+
return true;
|
|
2221
|
+
}
|
|
2222
|
+
async function buildTraceStats(metas, options) {
|
|
2223
|
+
let filtered = filterTraces(metas, { since: options.since });
|
|
2224
|
+
if (options.correlationId || options.groupId) {
|
|
2225
|
+
const next = [];
|
|
2226
|
+
for (const m of filtered) {
|
|
2227
|
+
const md = await readRunStartedMetadata(m.filePath);
|
|
2228
|
+
if (metaMatchesCorrelation(md, options.correlationId, options.groupId)) {
|
|
2229
|
+
next.push(m);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
filtered = next;
|
|
2233
|
+
}
|
|
2234
|
+
let successCount = 0;
|
|
2235
|
+
let errorCount = 0;
|
|
2236
|
+
let runningCount = 0;
|
|
2237
|
+
let unknownCount = 0;
|
|
2238
|
+
const durations = [];
|
|
2239
|
+
let totalSteps = 0;
|
|
2240
|
+
let totalLlmSteps = 0;
|
|
2241
|
+
let totalToolSteps = 0;
|
|
2242
|
+
let totalErrorSteps = 0;
|
|
2243
|
+
const slowestRuns = [];
|
|
2244
|
+
const slowestSteps = [];
|
|
2245
|
+
for (const m of filtered) {
|
|
2246
|
+
if (m.status === "success") successCount += 1;
|
|
2247
|
+
else if (m.status === "error") errorCount += 1;
|
|
2248
|
+
else if (m.status === "running") runningCount += 1;
|
|
2249
|
+
else unknownCount += 1;
|
|
2250
|
+
if (typeof m.durationMs === "number" && Number.isFinite(m.durationMs) && m.durationMs >= 0) {
|
|
2251
|
+
durations.push(m.durationMs);
|
|
2252
|
+
slowestRuns.push({
|
|
2253
|
+
runId: m.runId,
|
|
2254
|
+
name: m.name,
|
|
2255
|
+
durationMs: m.durationMs,
|
|
2256
|
+
status: m.status
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
try {
|
|
2260
|
+
const events = await readTraceEvents(m.runId, options.traceDir);
|
|
2261
|
+
if (events.length === 0) continue;
|
|
2262
|
+
const summary = buildRunSummary(events);
|
|
2263
|
+
totalSteps += summary.totalSteps;
|
|
2264
|
+
totalLlmSteps += summary.llmSteps;
|
|
2265
|
+
totalToolSteps += summary.toolSteps;
|
|
2266
|
+
totalErrorSteps += summary.errorSteps;
|
|
2267
|
+
const steps = collectCompletedSteps(events, m.runId);
|
|
2268
|
+
for (const s of steps) {
|
|
2269
|
+
slowestSteps.push(s);
|
|
2270
|
+
}
|
|
2271
|
+
} catch {
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
slowestRuns.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
|
|
2275
|
+
slowestSteps.sort((a, b) => b.durationMs - a.durationMs);
|
|
2276
|
+
const runLimit = options.slowRunLimit ?? 5;
|
|
2277
|
+
const stepLimit = options.slowStepLimit ?? 5;
|
|
2278
|
+
const sortedDur = [...durations].sort((a, b) => a - b);
|
|
2279
|
+
const totalRuns = filtered.length;
|
|
2280
|
+
const errorRate = totalRuns > 0 ? errorCount / totalRuns : 0;
|
|
2281
|
+
const sumDur = durations.reduce((a, b) => a + b, 0);
|
|
2282
|
+
return {
|
|
2283
|
+
traceDir: options.traceDir,
|
|
2284
|
+
...options.since ? { since: options.since } : {},
|
|
2285
|
+
...options.correlationId ? { correlationId: options.correlationId } : {},
|
|
2286
|
+
...options.groupId ? { groupId: options.groupId } : {},
|
|
2287
|
+
totalRuns,
|
|
2288
|
+
successCount,
|
|
2289
|
+
errorCount,
|
|
2290
|
+
runningCount,
|
|
2291
|
+
unknownCount,
|
|
2292
|
+
errorRate,
|
|
2293
|
+
duration: {
|
|
2294
|
+
...sortedDur.length > 0 ? {
|
|
2295
|
+
minMs: sortedDur[0],
|
|
2296
|
+
maxMs: sortedDur[sortedDur.length - 1],
|
|
2297
|
+
avgMs: sumDur / sortedDur.length,
|
|
2298
|
+
p50Ms: percentile(sortedDur, 50),
|
|
2299
|
+
p95Ms: percentile(sortedDur, 95)
|
|
2300
|
+
} : {}
|
|
2301
|
+
},
|
|
2302
|
+
totalSteps,
|
|
2303
|
+
avgStepsPerRun: totalRuns > 0 ? totalSteps / totalRuns : 0,
|
|
2304
|
+
totalLlmSteps,
|
|
2305
|
+
totalToolSteps,
|
|
2306
|
+
totalErrorSteps,
|
|
2307
|
+
slowestRuns: slowestRuns.slice(0, runLimit),
|
|
2308
|
+
slowestSteps: slowestSteps.slice(0, stepLimit)
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
function collectCompletedSteps(events, runId) {
|
|
2312
|
+
const started = /* @__PURE__ */ new Map();
|
|
2313
|
+
const out = [];
|
|
2314
|
+
for (const e of events) {
|
|
2315
|
+
if (e.event === "step_started") {
|
|
2316
|
+
const s = e;
|
|
2317
|
+
started.set(s.stepId, { name: s.name, type: s.type });
|
|
2318
|
+
}
|
|
2319
|
+
if (e.event === "step_completed") {
|
|
2320
|
+
const c = e;
|
|
2321
|
+
if (c.status !== "success" && c.status !== "error") continue;
|
|
2322
|
+
if (typeof c.durationMs !== "number" || !Number.isFinite(c.durationMs)) {
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
const meta = started.get(c.stepId);
|
|
2326
|
+
out.push({
|
|
2327
|
+
runId,
|
|
2328
|
+
stepName: meta?.name ?? c.stepId,
|
|
2329
|
+
stepType: meta?.type ?? "logic",
|
|
2330
|
+
durationMs: c.durationMs
|
|
2331
|
+
});
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return out;
|
|
2335
|
+
}
|
|
2336
|
+
function renderTraceStats(stats) {
|
|
2337
|
+
const lines = [];
|
|
2338
|
+
lines.push("Trace stats (local)");
|
|
2339
|
+
lines.push(`Directory: ${stats.traceDir}`);
|
|
2340
|
+
if (stats.since) lines.push(`Since: ${stats.since}`);
|
|
2341
|
+
if (stats.correlationId) lines.push(`Correlation ID: ${stats.correlationId}`);
|
|
2342
|
+
if (stats.groupId) lines.push(`Group ID: ${stats.groupId}`);
|
|
2343
|
+
lines.push("");
|
|
2344
|
+
lines.push(`Runs: ${stats.totalRuns}`);
|
|
2345
|
+
lines.push(
|
|
2346
|
+
` success: ${stats.successCount} error: ${stats.errorCount} running: ${stats.runningCount} unknown: ${stats.unknownCount}`
|
|
2347
|
+
);
|
|
2348
|
+
lines.push(`Error rate: ${(stats.errorRate * 100).toFixed(1)}%`);
|
|
2349
|
+
if (stats.duration.avgMs !== void 0) {
|
|
2350
|
+
lines.push(
|
|
2351
|
+
`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)}`
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
lines.push("");
|
|
2355
|
+
lines.push(`Steps: ${stats.totalSteps} (avg ${stats.avgStepsPerRun.toFixed(1)} per run)`);
|
|
2356
|
+
lines.push(
|
|
2357
|
+
` LLM: ${stats.totalLlmSteps} tool: ${stats.totalToolSteps} errors: ${stats.totalErrorSteps}`
|
|
2358
|
+
);
|
|
2359
|
+
if (stats.slowestRuns.length > 0) {
|
|
2360
|
+
lines.push("");
|
|
2361
|
+
lines.push("Slowest runs:");
|
|
2362
|
+
for (const r of stats.slowestRuns) {
|
|
2363
|
+
lines.push(
|
|
2364
|
+
` ${r.runId} | ${r.name ?? "-"} | ${formatDuration2(r.durationMs ?? 0)} | ${r.status}`
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
if (stats.slowestSteps.length > 0) {
|
|
2369
|
+
lines.push("");
|
|
2370
|
+
lines.push("Slowest steps:");
|
|
2371
|
+
for (const s of stats.slowestSteps) {
|
|
2372
|
+
lines.push(
|
|
2373
|
+
` ${s.runId} | ${s.stepType}:${s.stepName} | ${formatDuration2(s.durationMs)}`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
return lines.join("\n");
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// packages/core/src/search.ts
|
|
2381
|
+
function parseDurationFilter(expr) {
|
|
2382
|
+
const raw = expr.trim();
|
|
2383
|
+
const m = raw.match(/^(>=|<=|>|<)\s*(.+)$/);
|
|
2384
|
+
if (!m) {
|
|
2385
|
+
throw new Error(
|
|
2386
|
+
`Invalid --duration "${expr}". Use forms like >5s, >=500ms, <2m.`
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
const op = m[1];
|
|
2390
|
+
const ms = parseDuration(m[2].trim());
|
|
2391
|
+
return { op, ms };
|
|
2392
|
+
}
|
|
2393
|
+
function durationMatches(valueMs, filter) {
|
|
2394
|
+
if (valueMs === void 0 || !Number.isFinite(valueMs)) return false;
|
|
2395
|
+
switch (filter.op) {
|
|
2396
|
+
case ">":
|
|
2397
|
+
return valueMs > filter.ms;
|
|
2398
|
+
case ">=":
|
|
2399
|
+
return valueMs >= filter.ms;
|
|
2400
|
+
case "<":
|
|
2401
|
+
return valueMs < filter.ms;
|
|
2402
|
+
case "<=":
|
|
2403
|
+
return valueMs <= filter.ms;
|
|
2404
|
+
default:
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
function normalizeStepTypeFilter(kind, type) {
|
|
2409
|
+
const v = (kind ?? type)?.trim().toLowerCase();
|
|
2410
|
+
return v && v !== "" ? v : void 0;
|
|
2411
|
+
}
|
|
2412
|
+
function nameMatches(hay, needle) {
|
|
2413
|
+
return hay.toLowerCase().includes(needle.toLowerCase());
|
|
2414
|
+
}
|
|
2415
|
+
async function searchTraces(metas, options) {
|
|
2416
|
+
let filtered = filterTraces(metas, { since: options.since });
|
|
2417
|
+
const stepTypeFilter = normalizeStepTypeFilter(options.kind, options.type);
|
|
2418
|
+
const nameQuery = options.name?.trim();
|
|
2419
|
+
const toolQuery = options.tool?.trim();
|
|
2420
|
+
let durationFilter;
|
|
2421
|
+
if (options.duration) {
|
|
2422
|
+
durationFilter = parseDurationFilter(options.duration);
|
|
2423
|
+
}
|
|
2424
|
+
const limit = options.limit ?? 50;
|
|
2425
|
+
const hasContentFilter = Boolean(
|
|
2426
|
+
options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter
|
|
2427
|
+
);
|
|
2428
|
+
const results = [];
|
|
2429
|
+
if (!hasContentFilter) {
|
|
2430
|
+
for (const m of filtered) {
|
|
2431
|
+
results.push({
|
|
2432
|
+
runId: m.runId,
|
|
2433
|
+
runName: m.name,
|
|
2434
|
+
runStatus: m.status,
|
|
2435
|
+
timestamp: m.startedAt,
|
|
2436
|
+
durationMs: m.durationMs,
|
|
2437
|
+
matchReason: "trace in directory",
|
|
2438
|
+
matchedFields: ["run"],
|
|
2439
|
+
filePath: m.filePath
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
return results.slice(0, limit);
|
|
2443
|
+
}
|
|
2444
|
+
for (const m of filtered) {
|
|
2445
|
+
if (options.status && m.status !== options.status) continue;
|
|
2446
|
+
let events = [];
|
|
2447
|
+
try {
|
|
2448
|
+
events = await readTraceEvents(m.runId, options.traceDir);
|
|
2449
|
+
} catch {
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2452
|
+
if (events.length === 0) continue;
|
|
2453
|
+
const runMatches = matchRunLevel(m, {
|
|
2454
|
+
stepTypeFilter,
|
|
2455
|
+
nameQuery,
|
|
2456
|
+
toolQuery,
|
|
2457
|
+
durationFilter,
|
|
2458
|
+
statusFilter: options.status
|
|
2459
|
+
});
|
|
2460
|
+
results.push(...runMatches);
|
|
2461
|
+
const stepMatches = matchStepLevel(m, events, {
|
|
2462
|
+
stepTypeFilter,
|
|
2463
|
+
nameQuery,
|
|
2464
|
+
toolQuery,
|
|
2465
|
+
durationFilter,
|
|
2466
|
+
statusFilter: options.status
|
|
2467
|
+
});
|
|
2468
|
+
results.push(...stepMatches);
|
|
2469
|
+
}
|
|
2470
|
+
results.sort((a, b) => {
|
|
2471
|
+
const ta = a.timestamp ?? 0;
|
|
2472
|
+
const tb = b.timestamp ?? 0;
|
|
2473
|
+
if (ta !== tb) return ta - tb;
|
|
2474
|
+
const runCmp = a.runId.localeCompare(b.runId);
|
|
2475
|
+
if (runCmp !== 0) return runCmp;
|
|
2476
|
+
return (a.stepName ?? "").localeCompare(b.stepName ?? "");
|
|
2477
|
+
});
|
|
2478
|
+
return results.slice(0, limit);
|
|
2479
|
+
}
|
|
2480
|
+
function matchRunLevel(m, opts) {
|
|
2481
|
+
if (opts.stepTypeFilter || opts.toolQuery) return [];
|
|
2482
|
+
const out = [];
|
|
2483
|
+
const fields = [];
|
|
2484
|
+
if (opts.statusFilter && m.status === opts.statusFilter) {
|
|
2485
|
+
fields.push("run.status");
|
|
2486
|
+
}
|
|
2487
|
+
if (opts.nameQuery && nameMatches(m.name ?? m.runId, opts.nameQuery)) {
|
|
2488
|
+
fields.push("run.name");
|
|
2489
|
+
}
|
|
2490
|
+
if (opts.durationFilter && durationMatches(m.durationMs, opts.durationFilter)) {
|
|
2491
|
+
fields.push("run.durationMs");
|
|
2492
|
+
}
|
|
2493
|
+
if (fields.length === 0) return out;
|
|
2494
|
+
out.push({
|
|
2495
|
+
runId: m.runId,
|
|
2496
|
+
runName: m.name,
|
|
2497
|
+
runStatus: m.status,
|
|
2498
|
+
timestamp: m.startedAt,
|
|
2499
|
+
durationMs: m.durationMs,
|
|
2500
|
+
matchReason: `run match: ${fields.join(", ")}`,
|
|
2501
|
+
matchedFields: fields,
|
|
2502
|
+
filePath: m.filePath
|
|
2503
|
+
});
|
|
2504
|
+
return out;
|
|
2505
|
+
}
|
|
2506
|
+
function matchStepLevel(m, events, opts) {
|
|
2507
|
+
const out = [];
|
|
2508
|
+
const started = /* @__PURE__ */ new Map();
|
|
2509
|
+
for (const e of events) {
|
|
2510
|
+
if (e.event === "step_started") {
|
|
2511
|
+
started.set(e.stepId, e);
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
for (const e of events) {
|
|
2515
|
+
if (e.event !== "step_completed") continue;
|
|
2516
|
+
const c = e;
|
|
2517
|
+
const s = started.get(c.stepId);
|
|
2518
|
+
if (!s) continue;
|
|
2519
|
+
const fields = [];
|
|
2520
|
+
const stepType = s.type;
|
|
2521
|
+
if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
|
|
2525
|
+
if (!hasStepFilters) continue;
|
|
2526
|
+
if (opts.statusFilter === "error" && c.status === "error") {
|
|
2527
|
+
fields.push("step.status");
|
|
2528
|
+
} else if (opts.statusFilter === "success" && c.status === "success") {
|
|
2529
|
+
fields.push("step.status");
|
|
2530
|
+
} else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
|
|
2531
|
+
continue;
|
|
2532
|
+
}
|
|
2533
|
+
if (opts.nameQuery) {
|
|
2534
|
+
if (!nameMatches(s.name, opts.nameQuery)) continue;
|
|
2535
|
+
fields.push("step.name");
|
|
2536
|
+
}
|
|
2537
|
+
if (opts.toolQuery) {
|
|
2538
|
+
const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
|
|
2539
|
+
if (!nameMatches(toolName, opts.toolQuery)) continue;
|
|
2540
|
+
fields.push("step.tool");
|
|
2541
|
+
}
|
|
2542
|
+
if (opts.durationFilter) {
|
|
2543
|
+
if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
|
|
2544
|
+
fields.push("step.durationMs");
|
|
2545
|
+
}
|
|
2546
|
+
if (opts.stepTypeFilter) {
|
|
2547
|
+
fields.push("step.type");
|
|
2548
|
+
}
|
|
2549
|
+
if (fields.length === 0) continue;
|
|
2550
|
+
out.push({
|
|
2551
|
+
runId: m.runId,
|
|
2552
|
+
runName: m.name,
|
|
2553
|
+
runStatus: m.status,
|
|
2554
|
+
stepId: c.stepId,
|
|
2555
|
+
stepName: s.name,
|
|
2556
|
+
stepType,
|
|
2557
|
+
timestamp: s.startTime ?? s.timestamp,
|
|
2558
|
+
durationMs: c.durationMs,
|
|
2559
|
+
matchReason: `step match: ${fields.join(", ")}`,
|
|
2560
|
+
matchedFields: fields,
|
|
2561
|
+
filePath: m.filePath
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
return out;
|
|
2565
|
+
}
|
|
2566
|
+
async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
|
|
2567
|
+
const metas = [];
|
|
2568
|
+
for (const fileName of fileNames) {
|
|
2569
|
+
try {
|
|
2570
|
+
const filePath = getPath(fileName);
|
|
2571
|
+
const meta = await extractMetadata(filePath);
|
|
2572
|
+
metas.push(meta);
|
|
2573
|
+
} catch {
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
return metas;
|
|
2577
|
+
}
|
|
2010
2578
|
var KNOWN_EVENTS = /* @__PURE__ */ new Set([
|
|
2011
2579
|
"run_started",
|
|
2012
2580
|
"run_completed",
|
|
@@ -5259,6 +5827,163 @@ Trace directory: ${traceDir}`
|
|
|
5259
5827
|
);
|
|
5260
5828
|
}
|
|
5261
5829
|
|
|
5830
|
+
// packages/cli/src/timeline.ts
|
|
5831
|
+
async function timelineCommand(runId, options = {}) {
|
|
5832
|
+
const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
|
|
5833
|
+
if (id === "") {
|
|
5834
|
+
console.error("Run id is required");
|
|
5835
|
+
process.exitCode = 1;
|
|
5836
|
+
return;
|
|
5837
|
+
}
|
|
5838
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5839
|
+
let events;
|
|
5840
|
+
try {
|
|
5841
|
+
events = await readTraceEvents(id, traceDir);
|
|
5842
|
+
} catch (e) {
|
|
5843
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5844
|
+
console.error(`[AgentInspect] timeline failed: ${msg}`);
|
|
5845
|
+
process.exitCode = 1;
|
|
5846
|
+
return;
|
|
5847
|
+
}
|
|
5848
|
+
if (events.length === 0) {
|
|
5849
|
+
console.log(`Run not found: ${id}`);
|
|
5850
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5851
|
+
process.exitCode = 1;
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
const focus = options.focus?.trim().toLowerCase() === "slow" ? "slow" : "all";
|
|
5855
|
+
const timeline = buildRunTimeline(events, {
|
|
5856
|
+
focus: focus === "slow" ? "slow" : "all"
|
|
5857
|
+
});
|
|
5858
|
+
if (options.json) {
|
|
5859
|
+
console.log(JSON.stringify(timeline, null, 2));
|
|
5860
|
+
return;
|
|
5861
|
+
}
|
|
5862
|
+
console.log(renderTimeline(timeline, { focus }));
|
|
5863
|
+
}
|
|
5864
|
+
|
|
5865
|
+
// packages/cli/src/stats.ts
|
|
5866
|
+
async function statsCommand(options = {}) {
|
|
5867
|
+
try {
|
|
5868
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5869
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
5870
|
+
if (typeof options.since === "string" && options.since.trim() !== "") {
|
|
5871
|
+
parseDuration(options.since.trim());
|
|
5872
|
+
}
|
|
5873
|
+
const files = await td.list();
|
|
5874
|
+
if (files.length === 0) {
|
|
5875
|
+
if (options.json) {
|
|
5876
|
+
console.log(
|
|
5877
|
+
JSON.stringify({
|
|
5878
|
+
traceDir,
|
|
5879
|
+
totalRuns: 0,
|
|
5880
|
+
successCount: 0,
|
|
5881
|
+
errorCount: 0,
|
|
5882
|
+
runningCount: 0,
|
|
5883
|
+
unknownCount: 0,
|
|
5884
|
+
errorRate: 0,
|
|
5885
|
+
duration: {},
|
|
5886
|
+
totalSteps: 0,
|
|
5887
|
+
avgStepsPerRun: 0,
|
|
5888
|
+
totalLlmSteps: 0,
|
|
5889
|
+
totalToolSteps: 0,
|
|
5890
|
+
totalErrorSteps: 0,
|
|
5891
|
+
slowestRuns: [],
|
|
5892
|
+
slowestSteps: []
|
|
5893
|
+
})
|
|
5894
|
+
);
|
|
5895
|
+
} else {
|
|
5896
|
+
console.log("No AgentInspect runs found");
|
|
5897
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5898
|
+
}
|
|
5899
|
+
return;
|
|
5900
|
+
}
|
|
5901
|
+
const metas = [];
|
|
5902
|
+
for (const fileName of files) {
|
|
5903
|
+
try {
|
|
5904
|
+
metas.push(await extractMetadata(td.getPath(fileName)));
|
|
5905
|
+
} catch {
|
|
5906
|
+
}
|
|
5907
|
+
}
|
|
5908
|
+
const stats = await buildTraceStats(metas, {
|
|
5909
|
+
traceDir,
|
|
5910
|
+
since: options.since,
|
|
5911
|
+
correlationId: options.correlationId,
|
|
5912
|
+
groupId: options.groupId
|
|
5913
|
+
});
|
|
5914
|
+
if (options.json) {
|
|
5915
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
5916
|
+
return;
|
|
5917
|
+
}
|
|
5918
|
+
console.log(renderTraceStats(stats));
|
|
5919
|
+
} catch (e) {
|
|
5920
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5921
|
+
console.error(`[AgentInspect] stats failed: ${msg}`);
|
|
5922
|
+
process.exitCode = 1;
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
|
|
5926
|
+
// packages/cli/src/search.ts
|
|
5927
|
+
function parseLimit2(raw) {
|
|
5928
|
+
if (raw === void 0 || raw.trim() === "") return 50;
|
|
5929
|
+
const n = Number.parseInt(raw, 10);
|
|
5930
|
+
if (!Number.isFinite(n) || n <= 0) return 50;
|
|
5931
|
+
return Math.min(n, 500);
|
|
5932
|
+
}
|
|
5933
|
+
async function searchCommand(options = {}) {
|
|
5934
|
+
try {
|
|
5935
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5936
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
5937
|
+
if (typeof options.since === "string" && options.since.trim() !== "") {
|
|
5938
|
+
parseDuration(options.since.trim());
|
|
5939
|
+
}
|
|
5940
|
+
if (options.duration) {
|
|
5941
|
+
parseDurationFilter(options.duration);
|
|
5942
|
+
}
|
|
5943
|
+
const files = await td.list();
|
|
5944
|
+
const metas = await loadTraceMetadataList(
|
|
5945
|
+
traceDir,
|
|
5946
|
+
files,
|
|
5947
|
+
(f) => td.getPath(f)
|
|
5948
|
+
);
|
|
5949
|
+
const status = options.status === "success" || options.status === "error" || options.status === "running" || options.status === "unknown" ? options.status : void 0;
|
|
5950
|
+
const results = await searchTraces(metas, {
|
|
5951
|
+
traceDir,
|
|
5952
|
+
since: options.since,
|
|
5953
|
+
status,
|
|
5954
|
+
kind: options.kind,
|
|
5955
|
+
type: options.type,
|
|
5956
|
+
name: options.name,
|
|
5957
|
+
tool: options.tool,
|
|
5958
|
+
duration: options.duration,
|
|
5959
|
+
limit: parseLimit2(options.limit)
|
|
5960
|
+
});
|
|
5961
|
+
if (options.json) {
|
|
5962
|
+
console.log(JSON.stringify(results, null, 2));
|
|
5963
|
+
return;
|
|
5964
|
+
}
|
|
5965
|
+
if (results.length === 0) {
|
|
5966
|
+
console.log("No matching traces found");
|
|
5967
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5968
|
+
return;
|
|
5969
|
+
}
|
|
5970
|
+
console.log(`Search results (${results.length})`);
|
|
5971
|
+
for (const r of results) {
|
|
5972
|
+
const step2 = r.stepName !== void 0 ? ` | ${r.stepType ?? "step"}:${r.stepName}` : "";
|
|
5973
|
+
const dur = r.durationMs !== void 0 ? ` | ${r.durationMs}ms` : "";
|
|
5974
|
+
console.log(
|
|
5975
|
+
`${r.runId}${step2} | ${r.runStatus}${dur} | ${r.matchReason}`
|
|
5976
|
+
);
|
|
5977
|
+
}
|
|
5978
|
+
console.log("");
|
|
5979
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5980
|
+
} catch (e) {
|
|
5981
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5982
|
+
console.error(`[AgentInspect] search failed: ${msg}`);
|
|
5983
|
+
process.exitCode = 1;
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
|
|
5262
5987
|
// packages/cli/src/index.ts
|
|
5263
5988
|
function runCommand(action) {
|
|
5264
5989
|
void action().catch((error) => {
|
|
@@ -5372,6 +6097,29 @@ function createCliProgram() {
|
|
|
5372
6097
|
).option("--verbose", "show more left/right detail").action((leftRunId, rightRunId, opts) => {
|
|
5373
6098
|
runCommand(() => diffCommand(leftRunId, rightRunId, opts));
|
|
5374
6099
|
});
|
|
6100
|
+
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(
|
|
6101
|
+
new commander.Option("--focus <mode>", "highlight slowest steps by duration").choices([
|
|
6102
|
+
"slow"
|
|
6103
|
+
])
|
|
6104
|
+
).action((runId, opts) => {
|
|
6105
|
+
runCommand(() => timelineCommand(runId, opts));
|
|
6106
|
+
});
|
|
6107
|
+
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) => {
|
|
6108
|
+
runCommand(() => statsCommand(opts));
|
|
6109
|
+
});
|
|
6110
|
+
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(
|
|
6111
|
+
new commander.Option("--status <status>", "filter by run or step status").choices([
|
|
6112
|
+
"success",
|
|
6113
|
+
"error",
|
|
6114
|
+
"running",
|
|
6115
|
+
"unknown"
|
|
6116
|
+
])
|
|
6117
|
+
).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(
|
|
6118
|
+
"--duration <expr>",
|
|
6119
|
+
"duration filter on run or step (e.g. >5s, >=500ms)"
|
|
6120
|
+
).option("--limit <number>", "max results (default 50)").option("--json", "print results as JSON").action((opts) => {
|
|
6121
|
+
runCommand(() => searchCommand(opts));
|
|
6122
|
+
});
|
|
5375
6123
|
return program;
|
|
5376
6124
|
}
|
|
5377
6125
|
function isPrimaryModule() {
|