agent-inspect 4.1.0 → 4.3.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 +18 -0
- package/docs/CLI.md +50 -6
- package/package.json +1 -1
- package/packages/cli/dist/{chunk-MT5G7JFO.mjs → chunk-BS5LSKZ3.mjs} +451 -14
- package/packages/cli/dist/chunk-BS5LSKZ3.mjs.map +1 -0
- package/packages/cli/dist/index.cjs +2662 -1561
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +2118 -1510
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/cli/dist/{src-DUGEOAZ7.mjs → src-YFMPWEIS.mjs} +3 -3
- package/packages/cli/dist/{src-DUGEOAZ7.mjs.map → src-YFMPWEIS.mjs.map} +1 -1
- package/packages/core/dist/advanced.cjs +461 -11
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +171 -1
- package/packages/core/dist/advanced.d.ts +171 -1
- package/packages/core/dist/advanced.mjs +450 -12
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/cli/dist/chunk-MT5G7JFO.mjs.map +0 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { resolveTraceDir, TraceDirectory,
|
|
2
|
+
import { resolveTraceDir, parseDuration, buildSessionIndex, enrichSessionRunRecord, TraceDirectory, loadTraceMetadataList, loadSessionRunRecords, extractMetadata, filterTraces, truncateName, getTraceFilePath, buildRunSummary, formatDuration, formatTimestamp, isAgentInspectTrace, filterMetasBySessionScope, aggregateSessionCheckResults, resolveBundleRunIds, aggregateBundleSafeStatus, bundleFailsOnSafety, buildPlaceholderArtifact, buildBundleMetadata, buildBundleSummaryMarkdown, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, buildActivitySummary, renderActivitySummaryHuman, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, persistedInspectEventsToTraceEvents, renderStepLine, renderErrorLine, getIndent, Redactor, validateEvent, isPersistedInspectEvent, normalizeBundleOutputPath, defaultBundleOutputPath, nanoid, resolveRedactionProfile, applyProfileMetadataCaps, extractCorrelationMetadata, truncateStringForProfile, parseTraceJsonl } from './chunk-BS5LSKZ3.mjs';
|
|
3
3
|
import { realpathSync, constants as constants$1 } from 'fs';
|
|
4
|
-
import
|
|
4
|
+
import path13 from 'path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
6
6
|
import { Command, Option } from 'commander';
|
|
7
|
-
import { unlink, stat, mkdir, writeFile, appendFile, rm, access,
|
|
7
|
+
import { unlink, stat, mkdir, writeFile, appendFile, readFile, rm, access, open, readdir, constants } from 'fs/promises';
|
|
8
8
|
import process2, { stdin, stdout } from 'process';
|
|
9
9
|
import crypto, { createHash } from 'crypto';
|
|
10
10
|
import { createServer } from 'http';
|
|
11
11
|
import { createRequire } from 'module';
|
|
12
12
|
|
|
13
13
|
// package.json
|
|
14
|
-
var version = "4.
|
|
14
|
+
var version = "4.3.0";
|
|
15
15
|
|
|
16
16
|
// packages/cli/src/trace-dir-scale.ts
|
|
17
17
|
var TRACE_COUNT_WARN = 1e3;
|
|
@@ -895,7 +895,7 @@ function findReaderByFormat(format, readers) {
|
|
|
895
895
|
}
|
|
896
896
|
async function jsonlFilesInDirectory(dirPath) {
|
|
897
897
|
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
898
|
-
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) =>
|
|
898
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path13.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
|
|
899
899
|
}
|
|
900
900
|
async function resolveInput(input3) {
|
|
901
901
|
const cached = resolvedInputCache.get(input3);
|
|
@@ -4361,7 +4361,8 @@ function exportOpenInference(tree, options) {
|
|
|
4361
4361
|
];
|
|
4362
4362
|
const traceId = hexFrom(`trace:${tree.runId}`, 16);
|
|
4363
4363
|
const includeAttributes = options?.includeAttributes ?? false;
|
|
4364
|
-
const maxLen = options?.maxAttributeLength;
|
|
4364
|
+
const maxLen = options?.maxAttributeLength ?? 500;
|
|
4365
|
+
const pretty = options?.pretty ?? true;
|
|
4365
4366
|
const spans = [];
|
|
4366
4367
|
for (const n of flattenTree(tree)) {
|
|
4367
4368
|
const ev = n.event;
|
|
@@ -4435,7 +4436,7 @@ function exportOpenInference(tree, options) {
|
|
|
4435
4436
|
};
|
|
4436
4437
|
return {
|
|
4437
4438
|
format: "openinference",
|
|
4438
|
-
content: JSON.stringify(payload, null, 2 ),
|
|
4439
|
+
content: JSON.stringify(payload, null, pretty ? 2 : void 0),
|
|
4439
4440
|
contentType: "application/json",
|
|
4440
4441
|
fileExtension: ".openinference.json",
|
|
4441
4442
|
warnings
|
|
@@ -4469,7 +4470,8 @@ function exportOtlpJson(tree, options) {
|
|
|
4469
4470
|
];
|
|
4470
4471
|
const traceId = hexFrom2(`trace:${tree.runId}`, 16);
|
|
4471
4472
|
const includeAttributes = options?.includeAttributes ?? false;
|
|
4472
|
-
const maxLen = options?.maxAttributeLength;
|
|
4473
|
+
const maxLen = options?.maxAttributeLength ?? 500;
|
|
4474
|
+
const pretty = options?.pretty ?? true;
|
|
4473
4475
|
const flat = flattenTree(tree);
|
|
4474
4476
|
const spans = [];
|
|
4475
4477
|
for (const n of flat) {
|
|
@@ -4565,7 +4567,7 @@ function exportOtlpJson(tree, options) {
|
|
|
4565
4567
|
};
|
|
4566
4568
|
return {
|
|
4567
4569
|
format: "otlp-json",
|
|
4568
|
-
content: JSON.stringify(payload, null, 2 ),
|
|
4570
|
+
content: JSON.stringify(payload, null, pretty ? 2 : void 0),
|
|
4569
4571
|
contentType: "application/json",
|
|
4570
4572
|
fileExtension: ".otlp.json",
|
|
4571
4573
|
warnings
|
|
@@ -4788,9 +4790,9 @@ function mergeExportDefaults(options) {
|
|
|
4788
4790
|
includeMetadata: options.includeMetadata ?? true,
|
|
4789
4791
|
includeAttributes: options.includeAttributes ?? false,
|
|
4790
4792
|
includeErrors: options.includeErrors ?? true,
|
|
4791
|
-
pretty: options.pretty,
|
|
4792
|
-
redacted: options.redacted,
|
|
4793
|
-
maxAttributeLength: options.maxAttributeLength,
|
|
4793
|
+
pretty: options.pretty ?? true,
|
|
4794
|
+
redacted: options.redacted ?? true,
|
|
4795
|
+
maxAttributeLength: options.maxAttributeLength ?? 500,
|
|
4794
4796
|
redactionProfile: options.redactionProfile ?? "local"
|
|
4795
4797
|
};
|
|
4796
4798
|
}
|
|
@@ -5024,9 +5026,9 @@ Trace directory: ${traceDir}`);
|
|
|
5024
5026
|
if (validation !== void 0 && !validation.ok) {
|
|
5025
5027
|
process.exitCode = 1;
|
|
5026
5028
|
}
|
|
5027
|
-
const outPath = options.output !== void 0 && options.output.trim() !== "" ?
|
|
5029
|
+
const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
|
|
5028
5030
|
if (outPath !== void 0) {
|
|
5029
|
-
await mkdir(
|
|
5031
|
+
await mkdir(path13.dirname(outPath), { recursive: true });
|
|
5030
5032
|
await writeFile(outPath, result.content, "utf-8");
|
|
5031
5033
|
const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
|
|
5032
5034
|
console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
|
|
@@ -5203,13 +5205,13 @@ function pairSteps(left, right) {
|
|
|
5203
5205
|
return pairs;
|
|
5204
5206
|
}
|
|
5205
5207
|
function compareLeafSteps(L, R, segments, opts, out) {
|
|
5206
|
-
const
|
|
5208
|
+
const path19 = buildPath(segments);
|
|
5207
5209
|
if (L.name !== R.name) {
|
|
5208
5210
|
out.push({
|
|
5209
5211
|
kind: "structure",
|
|
5210
5212
|
severity: "warning",
|
|
5211
5213
|
message: "Step name differs",
|
|
5212
|
-
path:
|
|
5214
|
+
path: path19,
|
|
5213
5215
|
left: L.name,
|
|
5214
5216
|
right: R.name
|
|
5215
5217
|
});
|
|
@@ -5219,7 +5221,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
5219
5221
|
kind: "step-type",
|
|
5220
5222
|
severity: "warning",
|
|
5221
5223
|
message: "Step type differs",
|
|
5222
|
-
path:
|
|
5224
|
+
path: path19,
|
|
5223
5225
|
left: L.type,
|
|
5224
5226
|
right: R.type
|
|
5225
5227
|
});
|
|
@@ -5229,7 +5231,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
5229
5231
|
kind: "step-status",
|
|
5230
5232
|
severity: "warning",
|
|
5231
5233
|
message: "Step status differs",
|
|
5232
|
-
path:
|
|
5234
|
+
path: path19,
|
|
5233
5235
|
left: L.status,
|
|
5234
5236
|
right: R.status
|
|
5235
5237
|
});
|
|
@@ -5241,7 +5243,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
5241
5243
|
kind: "error",
|
|
5242
5244
|
severity: "error",
|
|
5243
5245
|
message: "Step error message differs",
|
|
5244
|
-
path:
|
|
5246
|
+
path: path19,
|
|
5245
5247
|
left: le || void 0,
|
|
5246
5248
|
right: re || void 0
|
|
5247
5249
|
});
|
|
@@ -5259,7 +5261,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
5259
5261
|
kind: "duration",
|
|
5260
5262
|
severity: "info",
|
|
5261
5263
|
message: "Step duration differs",
|
|
5262
|
-
path:
|
|
5264
|
+
path: path19,
|
|
5263
5265
|
left: ld,
|
|
5264
5266
|
right: rd
|
|
5265
5267
|
});
|
|
@@ -5272,7 +5274,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
5272
5274
|
kind: "metadata",
|
|
5273
5275
|
severity: "info",
|
|
5274
5276
|
message: "Step metadata differs",
|
|
5275
|
-
path:
|
|
5277
|
+
path: path19,
|
|
5276
5278
|
left: L.metadata,
|
|
5277
5279
|
right: R.metadata
|
|
5278
5280
|
});
|
|
@@ -5284,7 +5286,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
5284
5286
|
kind: "output",
|
|
5285
5287
|
severity: "info",
|
|
5286
5288
|
message: "Output preview differs",
|
|
5287
|
-
path:
|
|
5289
|
+
path: path19,
|
|
5288
5290
|
left: L.outputPreview,
|
|
5289
5291
|
right: R.outputPreview
|
|
5290
5292
|
});
|
|
@@ -5444,11 +5446,11 @@ function diffRuns(left, right, options) {
|
|
|
5444
5446
|
}
|
|
5445
5447
|
|
|
5446
5448
|
// packages/core/src/diff/renderer.ts
|
|
5447
|
-
function formatPath(
|
|
5448
|
-
if (
|
|
5449
|
+
function formatPath(path19) {
|
|
5450
|
+
if (path19 === void 0 || path19.path.length === 0) {
|
|
5449
5451
|
return "(run)";
|
|
5450
5452
|
}
|
|
5451
|
-
return
|
|
5453
|
+
return path19.path.map((s) => s.name).join(" > ");
|
|
5452
5454
|
}
|
|
5453
5455
|
function formatValue(v, verbose) {
|
|
5454
5456
|
if (v === void 0) return "(undefined)";
|
|
@@ -5810,9 +5812,28 @@ async function searchCommand(options = {}) {
|
|
|
5810
5812
|
process.exitCode = 1;
|
|
5811
5813
|
}
|
|
5812
5814
|
}
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5815
|
+
function isModuleNotFound2(e) {
|
|
5816
|
+
return e !== null && typeof e === "object" && "code" in e && (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND");
|
|
5817
|
+
}
|
|
5818
|
+
function toStatus(raw) {
|
|
5819
|
+
if (raw === "success" || raw === "error" || raw === "running") return raw;
|
|
5820
|
+
return "unknown";
|
|
5821
|
+
}
|
|
5822
|
+
function indexedToMetadata(row, traceDir) {
|
|
5823
|
+
return {
|
|
5824
|
+
runId: row.runId,
|
|
5825
|
+
name: row.name ?? void 0,
|
|
5826
|
+
status: toStatus(row.status),
|
|
5827
|
+
startedAt: row.startedAt ?? void 0,
|
|
5828
|
+
endedAt: row.endedAt ?? void 0,
|
|
5829
|
+
durationMs: row.durationMs ?? void 0,
|
|
5830
|
+
eventCount: 0,
|
|
5831
|
+
filePath: path13.join(traceDir, row.file),
|
|
5832
|
+
fileSize: 0,
|
|
5833
|
+
createdAt: new Date(row.mtimeMs)
|
|
5834
|
+
};
|
|
5835
|
+
}
|
|
5836
|
+
async function loadFromScan(traceDir) {
|
|
5816
5837
|
const td = new TraceDirectory({ dir: traceDir });
|
|
5817
5838
|
const files = await td.list();
|
|
5818
5839
|
const metas = await loadTraceMetadataList(
|
|
@@ -5820,12 +5841,77 @@ async function loadSessionIndex(traceDir, correlateGroup) {
|
|
|
5820
5841
|
files,
|
|
5821
5842
|
(fileName) => td.getPath(fileName)
|
|
5822
5843
|
);
|
|
5823
|
-
|
|
5824
|
-
|
|
5844
|
+
return loadSessionRunRecords(metas);
|
|
5845
|
+
}
|
|
5846
|
+
async function newestTraceMtimeMs(traceDir) {
|
|
5847
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
5848
|
+
let newest = 0;
|
|
5849
|
+
for (const file of await td.list()) {
|
|
5850
|
+
try {
|
|
5851
|
+
const stats = await td.getFileStats(file);
|
|
5852
|
+
if (stats.mtimeMs > newest) newest = stats.mtimeMs;
|
|
5853
|
+
} catch {
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
return newest;
|
|
5857
|
+
}
|
|
5858
|
+
async function loadSessionRuns(traceDir) {
|
|
5859
|
+
try {
|
|
5860
|
+
const mod = await import('./src-YFMPWEIS.mjs');
|
|
5861
|
+
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
5862
|
+
const status = mod.indexStatus(dbPath);
|
|
5863
|
+
if (!status.healthy) {
|
|
5864
|
+
return { runs: await loadFromScan(traceDir), source: "scan" };
|
|
5865
|
+
}
|
|
5866
|
+
const newest = await newestTraceMtimeMs(traceDir);
|
|
5867
|
+
if (mod.isIndexStale(dbPath, newest)) {
|
|
5868
|
+
return { runs: await loadFromScan(traceDir), source: "scan" };
|
|
5869
|
+
}
|
|
5870
|
+
const indexed = mod.queryRuns(dbPath, { limit: 1e4 });
|
|
5871
|
+
if (indexed.length === 0) {
|
|
5872
|
+
return { runs: await loadFromScan(traceDir), source: "scan" };
|
|
5873
|
+
}
|
|
5874
|
+
const runs = [];
|
|
5875
|
+
for (const row of indexed) {
|
|
5876
|
+
runs.push(await enrichSessionRunRecord(indexedToMetadata(row, traceDir)));
|
|
5877
|
+
}
|
|
5878
|
+
runs.sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0));
|
|
5879
|
+
return { runs, source: "index" };
|
|
5880
|
+
} catch (e) {
|
|
5881
|
+
if (!isModuleNotFound2(e)) ;
|
|
5882
|
+
return { runs: await loadFromScan(traceDir), source: "scan" };
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5886
|
+
// packages/cli/src/sessions.ts
|
|
5887
|
+
async function loadSessionIndex(traceDir, options = {}) {
|
|
5888
|
+
const { runs } = await loadSessionRuns(traceDir);
|
|
5889
|
+
const staleThresholdMs = options.staleAfter && options.staleAfter.trim() !== "" ? parseDuration(options.staleAfter.trim()) : void 0;
|
|
5890
|
+
return buildSessionIndex(runs, {
|
|
5891
|
+
correlateByGroupId: options.correlateGroup === true,
|
|
5892
|
+
staleThresholdMs
|
|
5893
|
+
});
|
|
5825
5894
|
}
|
|
5826
5895
|
function findSession(index, sessionId) {
|
|
5827
5896
|
return index.sessions.find((session) => session.sessionId === sessionId);
|
|
5828
5897
|
}
|
|
5898
|
+
function latestSession(index) {
|
|
5899
|
+
if (index.sessions.length === 0) return void 0;
|
|
5900
|
+
return [...index.sessions].sort(
|
|
5901
|
+
(a, b) => Date.parse(b.lastActivity) - Date.parse(a.lastActivity)
|
|
5902
|
+
)[0];
|
|
5903
|
+
}
|
|
5904
|
+
function parseSinceCutoff(since) {
|
|
5905
|
+
if (!since || since.trim() === "") return void 0;
|
|
5906
|
+
return Date.now() - parseDuration(since.trim());
|
|
5907
|
+
}
|
|
5908
|
+
function sessionsInSinceWindow(index, since) {
|
|
5909
|
+
const cutoff = parseSinceCutoff(since);
|
|
5910
|
+
if (cutoff === void 0) return index.sessions;
|
|
5911
|
+
return index.sessions.filter(
|
|
5912
|
+
(session) => Date.parse(session.lastActivity) >= cutoff
|
|
5913
|
+
);
|
|
5914
|
+
}
|
|
5829
5915
|
function renderSessionsHuman(index, traceDir) {
|
|
5830
5916
|
if (index.sessions.length === 0) {
|
|
5831
5917
|
console.log("No sessions found");
|
|
@@ -5839,13 +5925,9 @@ function renderSessionsHuman(index, traceDir) {
|
|
|
5839
5925
|
}
|
|
5840
5926
|
console.log("Sessions:");
|
|
5841
5927
|
for (const session of index.sessions) {
|
|
5842
|
-
const
|
|
5843
|
-
(run) => session.runIds.includes(run.runId)
|
|
5844
|
-
);
|
|
5845
|
-
const workflowName = firstRun?.metadata && typeof firstRun.metadata.workflowName === "string" ? firstRun.metadata.workflowName : void 0;
|
|
5846
|
-
const suffix = workflowName ? ` workflow=${workflowName}` : "";
|
|
5928
|
+
const suffix = session.workflowId ? ` workflow=${session.workflowId}` : "";
|
|
5847
5929
|
console.log(
|
|
5848
|
-
` ${session.sessionId} (${session.runIds.length} run${session.runIds.length === 1 ? "" : "s"})${suffix}`
|
|
5930
|
+
` ${session.sessionId} [${session.status}] (${session.runIds.length} run${session.runIds.length === 1 ? "" : "s"})${suffix}`
|
|
5849
5931
|
);
|
|
5850
5932
|
}
|
|
5851
5933
|
if (index.unscopedRunIds.length > 0) {
|
|
@@ -5857,7 +5939,14 @@ function renderSessionsHuman(index, traceDir) {
|
|
|
5857
5939
|
}
|
|
5858
5940
|
function renderSessionHuman(session, index, options) {
|
|
5859
5941
|
console.log(`Session: ${session.sessionId}`);
|
|
5942
|
+
console.log(`Status: ${session.status}`);
|
|
5860
5943
|
console.log(`Runs: ${session.runIds.join(", ")}`);
|
|
5944
|
+
if (session.lastActivity) console.log(`Last activity: ${session.lastActivity}`);
|
|
5945
|
+
if (session.lastError) {
|
|
5946
|
+
console.log(
|
|
5947
|
+
`Last error: ${session.lastError.message} (run ${session.lastError.runId})`
|
|
5948
|
+
);
|
|
5949
|
+
}
|
|
5861
5950
|
if (session.handoffs.length > 0) {
|
|
5862
5951
|
console.log("");
|
|
5863
5952
|
console.log("Handoffs:");
|
|
@@ -5902,10 +5991,29 @@ function renderSessionHuman(session, index, options) {
|
|
|
5902
5991
|
}
|
|
5903
5992
|
}
|
|
5904
5993
|
}
|
|
5994
|
+
function collectHandoffs(index, sessionId) {
|
|
5995
|
+
const sessions = sessionId ? index.sessions.filter((s) => s.sessionId === sessionId) : index.sessions;
|
|
5996
|
+
const out = [];
|
|
5997
|
+
for (const session of sessions) {
|
|
5998
|
+
for (const edge of session.handoffs) {
|
|
5999
|
+
out.push({ ...edge, sessionId: session.sessionId });
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
return out.sort((a, b) => {
|
|
6003
|
+
const session = a.sessionId.localeCompare(b.sessionId);
|
|
6004
|
+
if (session !== 0) return session;
|
|
6005
|
+
const from = a.from.localeCompare(b.from);
|
|
6006
|
+
if (from !== 0) return from;
|
|
6007
|
+
return a.to.localeCompare(b.to);
|
|
6008
|
+
});
|
|
6009
|
+
}
|
|
5905
6010
|
async function sessionsCommand(options = {}) {
|
|
5906
6011
|
try {
|
|
5907
6012
|
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5908
|
-
const index = await loadSessionIndex(traceDir,
|
|
6013
|
+
const index = await loadSessionIndex(traceDir, {
|
|
6014
|
+
correlateGroup: options.correlateGroup,
|
|
6015
|
+
staleAfter: options.staleAfter
|
|
6016
|
+
});
|
|
5909
6017
|
if (options.json) {
|
|
5910
6018
|
console.log(
|
|
5911
6019
|
JSON.stringify(
|
|
@@ -5928,6 +6036,121 @@ async function sessionsCommand(options = {}) {
|
|
|
5928
6036
|
process.exitCode = 1;
|
|
5929
6037
|
}
|
|
5930
6038
|
}
|
|
6039
|
+
async function sessionsLatestCommand(options = {}) {
|
|
6040
|
+
try {
|
|
6041
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
6042
|
+
const index = await loadSessionIndex(traceDir, {
|
|
6043
|
+
correlateGroup: options.correlateGroup,
|
|
6044
|
+
staleAfter: options.staleAfter
|
|
6045
|
+
});
|
|
6046
|
+
const latest = latestSession(index);
|
|
6047
|
+
if (!latest) {
|
|
6048
|
+
if (options.json) {
|
|
6049
|
+
console.log(JSON.stringify({ ok: false, traceDir, reason: "no-sessions" }, null, 2));
|
|
6050
|
+
} else {
|
|
6051
|
+
console.log("No sessions found");
|
|
6052
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
6053
|
+
}
|
|
6054
|
+
process.exitCode = 1;
|
|
6055
|
+
return;
|
|
6056
|
+
}
|
|
6057
|
+
if (options.json) {
|
|
6058
|
+
console.log(JSON.stringify({ ok: true, traceDir, session: latest }, null, 2));
|
|
6059
|
+
return;
|
|
6060
|
+
}
|
|
6061
|
+
console.log(`Latest session: ${latest.sessionId}`);
|
|
6062
|
+
console.log(`Status: ${latest.status}`);
|
|
6063
|
+
console.log(`Last activity: ${latest.lastActivity}`);
|
|
6064
|
+
console.log(`Runs: ${latest.runIds.join(", ")}`);
|
|
6065
|
+
} catch (e) {
|
|
6066
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
6067
|
+
console.error(`[AgentInspect] sessions latest failed: ${msg}`);
|
|
6068
|
+
process.exitCode = 1;
|
|
6069
|
+
}
|
|
6070
|
+
}
|
|
6071
|
+
async function sessionsActivityCommand(options = {}) {
|
|
6072
|
+
try {
|
|
6073
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
6074
|
+
const index = await loadSessionIndex(traceDir, {
|
|
6075
|
+
correlateGroup: options.correlateGroup,
|
|
6076
|
+
staleAfter: options.staleAfter
|
|
6077
|
+
});
|
|
6078
|
+
const summary = buildActivitySummary(index, { since: options.since });
|
|
6079
|
+
if (options.json) {
|
|
6080
|
+
console.log(JSON.stringify({ ok: true, traceDir, ...summary }, null, 2));
|
|
6081
|
+
return;
|
|
6082
|
+
}
|
|
6083
|
+
console.log(renderActivitySummaryHuman(summary));
|
|
6084
|
+
} catch (e) {
|
|
6085
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
6086
|
+
console.error(`[AgentInspect] sessions activity failed: ${msg}`);
|
|
6087
|
+
process.exitCode = 1;
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
6090
|
+
async function sessionsHandoffsCommand(options = {}) {
|
|
6091
|
+
try {
|
|
6092
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
6093
|
+
const index = await loadSessionIndex(traceDir, {
|
|
6094
|
+
correlateGroup: options.correlateGroup
|
|
6095
|
+
});
|
|
6096
|
+
const handoffs = collectHandoffs(index, options.session);
|
|
6097
|
+
if (options.json) {
|
|
6098
|
+
console.log(
|
|
6099
|
+
JSON.stringify({ ok: true, traceDir, count: handoffs.length, handoffs }, null, 2)
|
|
6100
|
+
);
|
|
6101
|
+
return;
|
|
6102
|
+
}
|
|
6103
|
+
if (handoffs.length === 0) {
|
|
6104
|
+
console.log("No handoffs found");
|
|
6105
|
+
return;
|
|
6106
|
+
}
|
|
6107
|
+
for (const edge of handoffs) {
|
|
6108
|
+
console.log(
|
|
6109
|
+
`${edge.sessionId}: ${edge.from} -> ${edge.to} (${edge.confidence})`
|
|
6110
|
+
);
|
|
6111
|
+
}
|
|
6112
|
+
} catch (e) {
|
|
6113
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
6114
|
+
console.error(`[AgentInspect] sessions handoffs failed: ${msg}`);
|
|
6115
|
+
process.exitCode = 1;
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
async function sessionsErrorsCommand(options = {}) {
|
|
6119
|
+
try {
|
|
6120
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
6121
|
+
const index = await loadSessionIndex(traceDir, {
|
|
6122
|
+
correlateGroup: options.correlateGroup,
|
|
6123
|
+
staleAfter: options.staleAfter
|
|
6124
|
+
});
|
|
6125
|
+
const scoped = sessionsInSinceWindow(index, options.since);
|
|
6126
|
+
const errors = scoped.filter((session) => session.status === "error");
|
|
6127
|
+
if (options.json) {
|
|
6128
|
+
console.log(
|
|
6129
|
+
JSON.stringify(
|
|
6130
|
+
{ ok: true, traceDir, count: errors.length, sessions: errors },
|
|
6131
|
+
null,
|
|
6132
|
+
2
|
|
6133
|
+
)
|
|
6134
|
+
);
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
if (errors.length === 0) {
|
|
6138
|
+
console.log("No error sessions found");
|
|
6139
|
+
return;
|
|
6140
|
+
}
|
|
6141
|
+
for (const session of errors) {
|
|
6142
|
+
const detail = session.lastError?.message ?? "error";
|
|
6143
|
+
console.log(`${session.sessionId} ${detail} (${session.runIds.length} runs)`);
|
|
6144
|
+
}
|
|
6145
|
+
} catch (e) {
|
|
6146
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
6147
|
+
console.error(`[AgentInspect] sessions errors failed: ${msg}`);
|
|
6148
|
+
process.exitCode = 1;
|
|
6149
|
+
}
|
|
6150
|
+
}
|
|
6151
|
+
async function sessionsShowCommand(sessionId, options = {}) {
|
|
6152
|
+
await sessionCommand(sessionId, options);
|
|
6153
|
+
}
|
|
5931
6154
|
async function sessionCommand(sessionId, options = {}) {
|
|
5932
6155
|
const id = typeof sessionId === "string" && sessionId.trim() !== "" ? sessionId.trim() : "";
|
|
5933
6156
|
if (id === "") {
|
|
@@ -6081,9 +6304,9 @@ async function reportCommand(runId, options = {}) {
|
|
|
6081
6304
|
redactionProfile,
|
|
6082
6305
|
correlation: !options.noCorrelation
|
|
6083
6306
|
});
|
|
6084
|
-
const outPath = options.output !== void 0 && options.output.trim() !== "" ?
|
|
6307
|
+
const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
|
|
6085
6308
|
if (outPath !== void 0) {
|
|
6086
|
-
await mkdir(
|
|
6309
|
+
await mkdir(path13.dirname(outPath), { recursive: true });
|
|
6087
6310
|
await writeFile(outPath, result.content, "utf-8");
|
|
6088
6311
|
console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
|
|
6089
6312
|
}
|
|
@@ -6330,17 +6553,17 @@ function applyRule(rule, value, replacement) {
|
|
|
6330
6553
|
}
|
|
6331
6554
|
return value;
|
|
6332
6555
|
}
|
|
6333
|
-
function childPath(
|
|
6556
|
+
function childPath(path19, key) {
|
|
6334
6557
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
|
|
6335
|
-
return
|
|
6558
|
+
return path19 ? `${path19}.${key}` : key;
|
|
6336
6559
|
}
|
|
6337
|
-
return `${
|
|
6560
|
+
return `${path19 || "$"}[${JSON.stringify(key)}]`;
|
|
6338
6561
|
}
|
|
6339
|
-
function indexPath(
|
|
6340
|
-
return `${
|
|
6562
|
+
function indexPath(path19, index) {
|
|
6563
|
+
return `${path19 || "$"}[${index}]`;
|
|
6341
6564
|
}
|
|
6342
|
-
function makeFinding(
|
|
6343
|
-
return preview === void 0 ? { path:
|
|
6565
|
+
function makeFinding(path19, detector, action, matchKind, severity = "warning", preview) {
|
|
6566
|
+
return preview === void 0 ? { path: path19, detector, action, severity, matchKind } : { path: path19, detector, action, severity, matchKind, preview };
|
|
6344
6567
|
}
|
|
6345
6568
|
function createRedactionProfile(profile = "local") {
|
|
6346
6569
|
switch (profile) {
|
|
@@ -6409,11 +6632,11 @@ var Redactor2 = class {
|
|
|
6409
6632
|
#recordFinding(state, finding) {
|
|
6410
6633
|
if (this.#collectFindings) state.findings.push(finding);
|
|
6411
6634
|
}
|
|
6412
|
-
#redactValue(value, key,
|
|
6635
|
+
#redactValue(value, key, path19, depth, state) {
|
|
6413
6636
|
if (depth > this.#maxDepth) {
|
|
6414
6637
|
this.#recordFinding(
|
|
6415
6638
|
state,
|
|
6416
|
-
makeFinding(
|
|
6639
|
+
makeFinding(path19, "structure.maxDepth", "truncate", "value", "warning")
|
|
6417
6640
|
);
|
|
6418
6641
|
return "[Truncated]";
|
|
6419
6642
|
}
|
|
@@ -6422,19 +6645,19 @@ var Redactor2 = class {
|
|
|
6422
6645
|
if (rule) {
|
|
6423
6646
|
this.#recordFinding(
|
|
6424
6647
|
state,
|
|
6425
|
-
makeFinding(
|
|
6648
|
+
makeFinding(path19, `key.${rule.key}`, actionForRule(rule), "key", "warning")
|
|
6426
6649
|
);
|
|
6427
6650
|
return applyRule(rule, value, this.#replacement);
|
|
6428
6651
|
}
|
|
6429
6652
|
}
|
|
6430
6653
|
for (const detector of this.#detectors) {
|
|
6431
|
-
const detections = detector.detect({ path:
|
|
6654
|
+
const detections = detector.detect({ path: path19, key, value });
|
|
6432
6655
|
for (const detection of detections) {
|
|
6433
6656
|
const action = detection.action ?? "replace";
|
|
6434
6657
|
this.#recordFinding(
|
|
6435
6658
|
state,
|
|
6436
6659
|
makeFinding(
|
|
6437
|
-
|
|
6660
|
+
path19,
|
|
6438
6661
|
detector.id,
|
|
6439
6662
|
action,
|
|
6440
6663
|
detection.matchKind ?? detector.matchKind ?? "custom",
|
|
@@ -6452,7 +6675,7 @@ var Redactor2 = class {
|
|
|
6452
6675
|
const out = [];
|
|
6453
6676
|
state.seen.set(value, out);
|
|
6454
6677
|
value.forEach((item, index) => {
|
|
6455
|
-
out[index] = this.#redactValue(item, void 0, indexPath(
|
|
6678
|
+
out[index] = this.#redactValue(item, void 0, indexPath(path19, index), depth + 1, state);
|
|
6456
6679
|
});
|
|
6457
6680
|
return out;
|
|
6458
6681
|
}
|
|
@@ -6464,7 +6687,7 @@ var Redactor2 = class {
|
|
|
6464
6687
|
out[entryKey] = this.#redactValue(
|
|
6465
6688
|
entryValue,
|
|
6466
6689
|
entryKey,
|
|
6467
|
-
childPath(
|
|
6690
|
+
childPath(path19 === "$" ? "" : path19, entryKey),
|
|
6468
6691
|
depth + 1,
|
|
6469
6692
|
state
|
|
6470
6693
|
);
|
|
@@ -6590,6 +6813,9 @@ function redactDocument(content, profile) {
|
|
|
6590
6813
|
return redactJsonlText(content, profile);
|
|
6591
6814
|
}
|
|
6592
6815
|
}
|
|
6816
|
+
function redactTraceContent(content, profile) {
|
|
6817
|
+
return redactDocument(content, profile);
|
|
6818
|
+
}
|
|
6593
6819
|
async function redactCommand(target, options = {}, stdin = process.stdin) {
|
|
6594
6820
|
const profile = parseRedactionProfile3(options.profile);
|
|
6595
6821
|
const source = await contentFromTarget(target, options, stdin);
|
|
@@ -6911,14 +7137,14 @@ function uniqueSorted(values) {
|
|
|
6911
7137
|
return [...new Set(values)].sort();
|
|
6912
7138
|
}
|
|
6913
7139
|
function isWithinDirectory(child, parent) {
|
|
6914
|
-
const relative =
|
|
6915
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
7140
|
+
const relative = path13.relative(parent, child);
|
|
7141
|
+
return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
|
|
6916
7142
|
}
|
|
6917
7143
|
async function resolveOutputPath(inputPath, output2, force) {
|
|
6918
7144
|
if (output2 === void 0 || output2.trim() === "") return void 0;
|
|
6919
|
-
const inputAbs =
|
|
6920
|
-
const outputAbs =
|
|
6921
|
-
const inputDir =
|
|
7145
|
+
const inputAbs = path13.resolve(inputPath);
|
|
7146
|
+
const outputAbs = path13.resolve(output2.trim());
|
|
7147
|
+
const inputDir = path13.dirname(inputAbs);
|
|
6922
7148
|
if (!isWithinDirectory(outputAbs, inputDir)) {
|
|
6923
7149
|
throw new Error("Refusing to write migrated output outside the input directory.");
|
|
6924
7150
|
}
|
|
@@ -7039,7 +7265,7 @@ async function migrateCommand(input3, options = {}) {
|
|
|
7039
7265
|
process.exitCode = 1;
|
|
7040
7266
|
return;
|
|
7041
7267
|
}
|
|
7042
|
-
const inputPath =
|
|
7268
|
+
const inputPath = path13.resolve(input3.trim());
|
|
7043
7269
|
const dryRun = options.dryRun === true;
|
|
7044
7270
|
if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
|
|
7045
7271
|
console.error("migrate requires --dry-run or --output <path>.");
|
|
@@ -7058,7 +7284,7 @@ async function migrateCommand(input3, options = {}) {
|
|
|
7058
7284
|
);
|
|
7059
7285
|
const result = await buildMigration(inputPath, outputPath);
|
|
7060
7286
|
if (!dryRun && outputPath !== void 0) {
|
|
7061
|
-
await mkdir(
|
|
7287
|
+
await mkdir(path13.dirname(outputPath), { recursive: true });
|
|
7062
7288
|
await writeFile(outputPath, result.content, "utf-8");
|
|
7063
7289
|
}
|
|
7064
7290
|
printSummary2(result, dryRun);
|
|
@@ -7336,7 +7562,7 @@ function stripPrefix(name, prefixes) {
|
|
|
7336
7562
|
}
|
|
7337
7563
|
return name;
|
|
7338
7564
|
}
|
|
7339
|
-
function eventEvidence(event,
|
|
7565
|
+
function eventEvidence(event, path19) {
|
|
7340
7566
|
return {
|
|
7341
7567
|
runId: event.runId,
|
|
7342
7568
|
eventId: event.eventId,
|
|
@@ -7346,7 +7572,7 @@ function eventEvidence(event, path17) {
|
|
|
7346
7572
|
kind: event.kind,
|
|
7347
7573
|
name: event.name,
|
|
7348
7574
|
status: event.status,
|
|
7349
|
-
...
|
|
7575
|
+
...path19 ? { path: path19 } : {}
|
|
7350
7576
|
};
|
|
7351
7577
|
}
|
|
7352
7578
|
function runEvidence(run) {
|
|
@@ -7409,9 +7635,9 @@ function eventEndMs(event) {
|
|
|
7409
7635
|
function normalizedKey(value) {
|
|
7410
7636
|
return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
|
|
7411
7637
|
}
|
|
7412
|
-
function lastPathSegment(
|
|
7413
|
-
const parts =
|
|
7414
|
-
return parts[parts.length - 1] ??
|
|
7638
|
+
function lastPathSegment(path19) {
|
|
7639
|
+
const parts = path19.split(".");
|
|
7640
|
+
return parts[parts.length - 1] ?? path19;
|
|
7415
7641
|
}
|
|
7416
7642
|
function valueType(value) {
|
|
7417
7643
|
if (Array.isArray(value)) return "array";
|
|
@@ -7425,12 +7651,12 @@ function serializedByteLength(value) {
|
|
|
7425
7651
|
return void 0;
|
|
7426
7652
|
}
|
|
7427
7653
|
}
|
|
7428
|
-
function pushValueEntries(entries, event, value,
|
|
7429
|
-
entries.push({ event, path:
|
|
7654
|
+
function pushValueEntries(entries, event, value, path19, key, depth = 0) {
|
|
7655
|
+
entries.push({ event, path: path19, key, value });
|
|
7430
7656
|
if (depth >= 8) return;
|
|
7431
7657
|
if (Array.isArray(value)) {
|
|
7432
7658
|
for (const [index, item] of value.entries()) {
|
|
7433
|
-
pushValueEntries(entries, event, item, `${
|
|
7659
|
+
pushValueEntries(entries, event, item, `${path19}.${index}`, String(index), depth + 1);
|
|
7434
7660
|
}
|
|
7435
7661
|
return;
|
|
7436
7662
|
}
|
|
@@ -7440,7 +7666,7 @@ function pushValueEntries(entries, event, value, path17, key, depth = 0) {
|
|
|
7440
7666
|
entries,
|
|
7441
7667
|
event,
|
|
7442
7668
|
value[nestedKey],
|
|
7443
|
-
`${
|
|
7669
|
+
`${path19}.${nestedKey}`,
|
|
7444
7670
|
nestedKey,
|
|
7445
7671
|
depth + 1
|
|
7446
7672
|
);
|
|
@@ -7521,9 +7747,9 @@ function eventDurationMs(event) {
|
|
|
7521
7747
|
}
|
|
7522
7748
|
function treeShape(nodes) {
|
|
7523
7749
|
const lines = [];
|
|
7524
|
-
const visit = (node,
|
|
7525
|
-
lines.push(`${
|
|
7526
|
-
node.children.forEach((child, index) => visit(child, `${
|
|
7750
|
+
const visit = (node, path19) => {
|
|
7751
|
+
lines.push(`${path19}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
|
|
7752
|
+
node.children.forEach((child, index) => visit(child, `${path19}.${index}`));
|
|
7527
7753
|
};
|
|
7528
7754
|
nodes.forEach((node, index) => visit(node, String(index)));
|
|
7529
7755
|
return lines;
|
|
@@ -7572,9 +7798,9 @@ function retrievalShape(context) {
|
|
|
7572
7798
|
function guardrailShape(context) {
|
|
7573
7799
|
return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
|
|
7574
7800
|
}
|
|
7575
|
-
function firstEvidenceForKind(context, kind,
|
|
7801
|
+
function firstEvidenceForKind(context, kind, path19) {
|
|
7576
7802
|
const event = context.events.find((candidate) => candidate.kind === kind);
|
|
7577
|
-
return event ? [eventEvidence(event,
|
|
7803
|
+
return event ? [eventEvidence(event, path19)] : runEvidence(context.selectedRun);
|
|
7578
7804
|
}
|
|
7579
7805
|
function baselineDiffFinding(message, evidence, expected, actual) {
|
|
7580
7806
|
return failFinding("baseline.regression", message, evidence, expected, actual);
|
|
@@ -7924,13 +8150,13 @@ function createStructureCycleRule() {
|
|
|
7924
8150
|
const seenCycles = /* @__PURE__ */ new Set();
|
|
7925
8151
|
const findings = [];
|
|
7926
8152
|
for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
|
|
7927
|
-
const
|
|
8153
|
+
const path19 = [];
|
|
7928
8154
|
const seenAt = /* @__PURE__ */ new Map();
|
|
7929
8155
|
let current = event;
|
|
7930
8156
|
while (current) {
|
|
7931
8157
|
const existing = seenAt.get(current.eventId);
|
|
7932
8158
|
if (existing !== void 0) {
|
|
7933
|
-
const cycle =
|
|
8159
|
+
const cycle = path19.slice(existing);
|
|
7934
8160
|
const key = cycle.map((item) => item.eventId).sort().join("\0");
|
|
7935
8161
|
if (!seenCycles.has(key)) {
|
|
7936
8162
|
seenCycles.add(key);
|
|
@@ -7946,8 +8172,8 @@ function createStructureCycleRule() {
|
|
|
7946
8172
|
}
|
|
7947
8173
|
break;
|
|
7948
8174
|
}
|
|
7949
|
-
seenAt.set(current.eventId,
|
|
7950
|
-
|
|
8175
|
+
seenAt.set(current.eventId, path19.length);
|
|
8176
|
+
path19.push(current);
|
|
7951
8177
|
current = current.parentId ? byId.get(current.parentId) : void 0;
|
|
7952
8178
|
}
|
|
7953
8179
|
}
|
|
@@ -8744,23 +8970,23 @@ function evaluatePromptInjection(text, options = {}) {
|
|
|
8744
8970
|
}
|
|
8745
8971
|
return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
|
|
8746
8972
|
}
|
|
8747
|
-
function validateSchemaField(value, field,
|
|
8973
|
+
function validateSchemaField(value, field, path19, evidence) {
|
|
8748
8974
|
const ruleId = "guardrail.structured-output";
|
|
8749
8975
|
if (field.type) {
|
|
8750
8976
|
const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
8751
8977
|
if (actual !== field.type) {
|
|
8752
|
-
evidence.push({ ruleId, path:
|
|
8978
|
+
evidence.push({ ruleId, path: path19, preview: `expected ${field.type}, got ${actual}` });
|
|
8753
8979
|
return;
|
|
8754
8980
|
}
|
|
8755
8981
|
}
|
|
8756
8982
|
if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
|
|
8757
|
-
evidence.push({ ruleId, path:
|
|
8983
|
+
evidence.push({ ruleId, path: path19, preview: "value not in enum" });
|
|
8758
8984
|
}
|
|
8759
8985
|
if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
|
|
8760
8986
|
const record = value;
|
|
8761
8987
|
for (const key of field.required) {
|
|
8762
8988
|
if (!(key in record)) {
|
|
8763
|
-
evidence.push({ ruleId, path: `${
|
|
8989
|
+
evidence.push({ ruleId, path: `${path19}.${key}`, preview: "missing required key" });
|
|
8764
8990
|
}
|
|
8765
8991
|
}
|
|
8766
8992
|
}
|
|
@@ -9087,7 +9313,7 @@ function asConfig(value) {
|
|
|
9087
9313
|
}
|
|
9088
9314
|
async function loadConfig(configPath) {
|
|
9089
9315
|
if (configPath === void 0) return {};
|
|
9090
|
-
const extension =
|
|
9316
|
+
const extension = path13.extname(configPath);
|
|
9091
9317
|
if (TS_CONFIG_EXTENSIONS.has(extension)) {
|
|
9092
9318
|
throw new Error(
|
|
9093
9319
|
"TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
|
|
@@ -9096,7 +9322,7 @@ async function loadConfig(configPath) {
|
|
|
9096
9322
|
if (!CONFIG_EXTENSIONS.has(extension)) {
|
|
9097
9323
|
throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
|
|
9098
9324
|
}
|
|
9099
|
-
const absolute =
|
|
9325
|
+
const absolute = path13.resolve(configPath);
|
|
9100
9326
|
if (extension === ".json") {
|
|
9101
9327
|
const raw = await readFile(absolute, "utf-8");
|
|
9102
9328
|
return asConfig(JSON.parse(raw));
|
|
@@ -9245,10 +9471,10 @@ function printHuman(result) {
|
|
|
9245
9471
|
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
9246
9472
|
}
|
|
9247
9473
|
for (const finding of result.findings) {
|
|
9248
|
-
const
|
|
9474
|
+
const path19 = finding.evidence[0]?.path;
|
|
9249
9475
|
const run = finding.evidence[0]?.runId;
|
|
9250
9476
|
const runPrefix = run ? `[${run}] ` : "";
|
|
9251
|
-
console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${
|
|
9477
|
+
console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
|
|
9252
9478
|
}
|
|
9253
9479
|
}
|
|
9254
9480
|
function readErrorResult(error) {
|
|
@@ -9448,7 +9674,7 @@ function createViewerServer(options = {}) {
|
|
|
9448
9674
|
return sendJson(res, 200, {
|
|
9449
9675
|
ok: true,
|
|
9450
9676
|
readOnly: true,
|
|
9451
|
-
traceDir:
|
|
9677
|
+
traceDir: path13.resolve(traceDir)
|
|
9452
9678
|
});
|
|
9453
9679
|
}
|
|
9454
9680
|
const td = new TraceDirectory({ dir: traceDir });
|
|
@@ -9466,7 +9692,7 @@ function createViewerServer(options = {}) {
|
|
|
9466
9692
|
runId: meta.runId,
|
|
9467
9693
|
name: meta.name,
|
|
9468
9694
|
status: meta.status,
|
|
9469
|
-
file:
|
|
9695
|
+
file: path13.basename(meta.filePath),
|
|
9470
9696
|
startedAt: meta.startedAt,
|
|
9471
9697
|
durationMs: meta.durationMs
|
|
9472
9698
|
}))
|
|
@@ -9581,7 +9807,7 @@ function startViewerServer(options = {}) {
|
|
|
9581
9807
|
resolve({
|
|
9582
9808
|
host,
|
|
9583
9809
|
port: resolvedPort,
|
|
9584
|
-
traceDir:
|
|
9810
|
+
traceDir: path13.resolve(traceDir),
|
|
9585
9811
|
url: `http://${host}:${resolvedPort}`
|
|
9586
9812
|
});
|
|
9587
9813
|
});
|
|
@@ -9786,10 +10012,10 @@ async function evalRun(input3, options = {}) {
|
|
|
9786
10012
|
diagnostics: []
|
|
9787
10013
|
};
|
|
9788
10014
|
}
|
|
9789
|
-
function evidenceForRun(run,
|
|
9790
|
-
return [{ runId: run.runId, ...
|
|
10015
|
+
function evidenceForRun(run, path19) {
|
|
10016
|
+
return [{ runId: run.runId, ...path19 !== void 0 ? { path: path19 } : {} }];
|
|
9791
10017
|
}
|
|
9792
|
-
function evidenceForEvent(event,
|
|
10018
|
+
function evidenceForEvent(event, path19) {
|
|
9793
10019
|
return [
|
|
9794
10020
|
{
|
|
9795
10021
|
runId: event.runId,
|
|
@@ -9797,7 +10023,7 @@ function evidenceForEvent(event, path17) {
|
|
|
9797
10023
|
...event.parentId !== void 0 ? { parentId: event.parentId } : {},
|
|
9798
10024
|
kind: event.kind,
|
|
9799
10025
|
name: event.name,
|
|
9800
|
-
...
|
|
10026
|
+
...path19 !== void 0 ? { path: path19 } : {}
|
|
9801
10027
|
}
|
|
9802
10028
|
];
|
|
9803
10029
|
}
|
|
@@ -9955,9 +10181,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
|
|
|
9955
10181
|
function tokenize(text) {
|
|
9956
10182
|
return [...text.toLowerCase().matchAll(/[a-z0-9][a-z0-9'-]{2,}/g)].map((match) => match[0].replace(/^['-]+|['-]+$/g, "")).filter((token) => token.length > 2 && !STOP_WORDS.has(token));
|
|
9957
10183
|
}
|
|
9958
|
-
function firstEvidence(fields, run,
|
|
10184
|
+
function firstEvidence(fields, run, path19) {
|
|
9959
10185
|
const first = fields[0];
|
|
9960
|
-
return first === void 0 ? evidenceForRun(run,
|
|
10186
|
+
return first === void 0 ? evidenceForRun(run, path19) : evidenceForEvent(first.node.event, first.path);
|
|
9961
10187
|
}
|
|
9962
10188
|
function collectSourceIds(nodes, keys) {
|
|
9963
10189
|
const wanted = keySet(keys);
|
|
@@ -10334,8 +10560,8 @@ function renderEvalMarkdown(result) {
|
|
|
10334
10560
|
if (result.findings.length > 0) {
|
|
10335
10561
|
lines.push("", "## Findings");
|
|
10336
10562
|
for (const finding of result.findings) {
|
|
10337
|
-
const
|
|
10338
|
-
lines.push(`- ${finding.ruleId}: ${finding.message}${
|
|
10563
|
+
const path19 = finding.evidence[0]?.path;
|
|
10564
|
+
lines.push(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
|
|
10339
10565
|
}
|
|
10340
10566
|
}
|
|
10341
10567
|
return `${lines.join("\n")}
|
|
@@ -10382,7 +10608,7 @@ function asConfig2(value) {
|
|
|
10382
10608
|
}
|
|
10383
10609
|
async function loadConfig2(configPath) {
|
|
10384
10610
|
if (configPath === void 0) return {};
|
|
10385
|
-
const extension =
|
|
10611
|
+
const extension = path13.extname(configPath);
|
|
10386
10612
|
if (TS_CONFIG_EXTENSIONS2.has(extension)) {
|
|
10387
10613
|
throw new Error(
|
|
10388
10614
|
"TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
|
|
@@ -10391,7 +10617,7 @@ async function loadConfig2(configPath) {
|
|
|
10391
10617
|
if (!CONFIG_EXTENSIONS2.has(extension)) {
|
|
10392
10618
|
throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
|
|
10393
10619
|
}
|
|
10394
|
-
const absolute =
|
|
10620
|
+
const absolute = path13.resolve(configPath);
|
|
10395
10621
|
if (extension === ".json") {
|
|
10396
10622
|
const raw = await readFile(absolute, "utf-8");
|
|
10397
10623
|
return asConfig2(JSON.parse(raw));
|
|
@@ -10528,8 +10754,8 @@ function printHuman2(result) {
|
|
|
10528
10754
|
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
10529
10755
|
}
|
|
10530
10756
|
for (const finding of result.findings) {
|
|
10531
|
-
const
|
|
10532
|
-
console.log(`- ${finding.ruleId}: ${finding.message}${
|
|
10757
|
+
const path19 = finding.evidence[0]?.path;
|
|
10758
|
+
console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
|
|
10533
10759
|
}
|
|
10534
10760
|
}
|
|
10535
10761
|
function readErrorResult2(error) {
|
|
@@ -10767,8 +10993,8 @@ function printHuman3(result) {
|
|
|
10767
10993
|
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
10768
10994
|
}
|
|
10769
10995
|
for (const finding of result.findings) {
|
|
10770
|
-
const
|
|
10771
|
-
console.log(`- ${finding.ruleId}: ${finding.message}${
|
|
10996
|
+
const path19 = finding.evidence[0]?.path;
|
|
10997
|
+
console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
|
|
10772
10998
|
}
|
|
10773
10999
|
console.log(`Note: ${result.note}`);
|
|
10774
11000
|
}
|
|
@@ -10814,6 +11040,38 @@ function scanCommand(target, options = {}, stdin = process.stdin) {
|
|
|
10814
11040
|
function verifySafeCommand(target, options = {}, stdin = process.stdin) {
|
|
10815
11041
|
return safetyCommand("verify-safe", target, options, stdin);
|
|
10816
11042
|
}
|
|
11043
|
+
function assessOpenedTrace(read, options = {}) {
|
|
11044
|
+
try {
|
|
11045
|
+
const rules = buildSafetyRules(options);
|
|
11046
|
+
const checkResult = runTraceChecks(
|
|
11047
|
+
{ read },
|
|
11048
|
+
{
|
|
11049
|
+
rules,
|
|
11050
|
+
...options.runId !== void 0 ? { runId: options.runId } : {},
|
|
11051
|
+
...options.run !== void 0 ? { runId: options.run } : {}
|
|
11052
|
+
}
|
|
11053
|
+
);
|
|
11054
|
+
const detectorFindings = checkResult.diagnostics.length === 0 ? redactionDetectorFindings(read, checkResult.runId) : [];
|
|
11055
|
+
return resultFromParts({
|
|
11056
|
+
command: "verify-safe",
|
|
11057
|
+
format: checkResult.format,
|
|
11058
|
+
runId: checkResult.runId,
|
|
11059
|
+
findings: [...checkResult.findings, ...detectorFindings],
|
|
11060
|
+
diagnostics: [
|
|
11061
|
+
...checkResult.diagnostics.map(diagnosticFromCheck),
|
|
11062
|
+
...warningDiagnostics(read.warnings, read.unsupportedFields)
|
|
11063
|
+
],
|
|
11064
|
+
warnings: read.warnings,
|
|
11065
|
+
unsupportedFields: read.unsupportedFields
|
|
11066
|
+
});
|
|
11067
|
+
} catch (error) {
|
|
11068
|
+
return messageStartsWithDash(error) ? invalidArgumentResult("verify-safe", error) : readErrorResult3("verify-safe", error);
|
|
11069
|
+
}
|
|
11070
|
+
}
|
|
11071
|
+
function messageStartsWithDash(error) {
|
|
11072
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11073
|
+
return message.startsWith("--");
|
|
11074
|
+
}
|
|
10817
11075
|
var NOTE = "Generated locally by AgentInspect. Artifacts are best-effort summaries, not compliance or security certification.";
|
|
10818
11076
|
var SAFETY_RULES = [
|
|
10819
11077
|
createSafetyRawContentRule(),
|
|
@@ -10887,8 +11145,8 @@ function renderCheckSection(result) {
|
|
|
10887
11145
|
`Diagnostics: ${result.diagnostics.length}`
|
|
10888
11146
|
];
|
|
10889
11147
|
for (const finding of result.findings.slice(0, 10)) {
|
|
10890
|
-
const
|
|
10891
|
-
lines.push(`- ${finding.ruleId}: ${finding.message} (${
|
|
11148
|
+
const path19 = finding.evidence[0]?.path ?? "(run)";
|
|
11149
|
+
lines.push(`- ${finding.ruleId}: ${finding.message} (${path19})`);
|
|
10892
11150
|
}
|
|
10893
11151
|
for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
|
|
10894
11152
|
lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
@@ -10966,8 +11224,8 @@ function renderHtml(trace, check, diff) {
|
|
|
10966
11224
|
`;
|
|
10967
11225
|
}
|
|
10968
11226
|
async function writeArtifact(outputDir, relativePath, content, files) {
|
|
10969
|
-
const outPath =
|
|
10970
|
-
await mkdir(
|
|
11227
|
+
const outPath = path13.join(outputDir, relativePath);
|
|
11228
|
+
await mkdir(path13.dirname(outPath), { recursive: true });
|
|
10971
11229
|
await writeFile(outPath, content, "utf-8");
|
|
10972
11230
|
files.push(relativePath);
|
|
10973
11231
|
}
|
|
@@ -10983,7 +11241,7 @@ function manifestStatus(check, diff) {
|
|
|
10983
11241
|
return "ok";
|
|
10984
11242
|
}
|
|
10985
11243
|
async function artifactsCommand(target, options = {}, stdin = process.stdin) {
|
|
10986
|
-
const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ?
|
|
11244
|
+
const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path13.resolve(options.outputDir.trim()) : "";
|
|
10987
11245
|
if (outputDir === "") {
|
|
10988
11246
|
console.error("--output-dir is required.");
|
|
10989
11247
|
process.exitCode = 1;
|
|
@@ -11049,8 +11307,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
|
|
|
11049
11307
|
await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
|
|
11050
11308
|
const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
|
|
11051
11309
|
if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
|
|
11052
|
-
await mkdir(
|
|
11053
|
-
await appendFile(
|
|
11310
|
+
await mkdir(path13.dirname(path13.resolve(summaryTarget)), { recursive: true });
|
|
11311
|
+
await appendFile(path13.resolve(summaryTarget), `
|
|
11054
11312
|
${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
11055
11313
|
}
|
|
11056
11314
|
const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
|
|
@@ -11069,10 +11327,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
|
11069
11327
|
findings: diff?.findings.length ?? 0,
|
|
11070
11328
|
diagnostics: diff?.diagnostics.length ?? 0
|
|
11071
11329
|
},
|
|
11072
|
-
...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary:
|
|
11330
|
+
...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path13.resolve(summaryTarget) } : {},
|
|
11073
11331
|
note: NOTE
|
|
11074
11332
|
};
|
|
11075
|
-
await writeFile(
|
|
11333
|
+
await writeFile(path13.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
|
|
11076
11334
|
if (options.json === true) {
|
|
11077
11335
|
console.log(writeJson3(manifest).trimEnd());
|
|
11078
11336
|
} else {
|
|
@@ -11083,1568 +11341,1893 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
|
11083
11341
|
}
|
|
11084
11342
|
}
|
|
11085
11343
|
}
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
11096
|
-
|
|
11344
|
+
|
|
11345
|
+
// packages/core/src/workspace/types.ts
|
|
11346
|
+
var WORKSPACE_SCHEMA_VERSION = "1.0";
|
|
11347
|
+
var WORKSPACE_DIR_NAME = ".agent-inspect";
|
|
11348
|
+
var WORKSPACE_MANIFEST_FILENAME = "workspace.json";
|
|
11349
|
+
|
|
11350
|
+
// packages/core/src/workspace/manifest.ts
|
|
11351
|
+
var DEFAULT_WORKSPACE_LAYOUT = {
|
|
11352
|
+
traceDirs: ["runs"],
|
|
11353
|
+
reportsDir: "reports",
|
|
11354
|
+
artifactsDir: "artifacts",
|
|
11355
|
+
bundlesDir: "bundles",
|
|
11356
|
+
notesDir: "notes"
|
|
11357
|
+
};
|
|
11358
|
+
var DEFAULT_REDACTION_PROFILE = "share";
|
|
11359
|
+
var REDACTION_PROFILES = [
|
|
11360
|
+
"local",
|
|
11361
|
+
"share",
|
|
11362
|
+
"strict"
|
|
11363
|
+
];
|
|
11364
|
+
var INDEX_TYPES = ["none", "sqlite", "custom"];
|
|
11365
|
+
var MAX_WORKSPACE_MANIFEST_BYTES = 64 * 1024;
|
|
11366
|
+
function createDefaultWorkspaceManifest(options) {
|
|
11367
|
+
const project = typeof options.project === "string" ? options.project.trim() : "";
|
|
11368
|
+
const index = {
|
|
11369
|
+
enabled: options.index?.enabled ?? false,
|
|
11370
|
+
type: options.index?.type ?? "none",
|
|
11371
|
+
...options.index?.path !== void 0 ? { path: options.index.path } : {}
|
|
11372
|
+
};
|
|
11373
|
+
return {
|
|
11374
|
+
schemaVersion: WORKSPACE_SCHEMA_VERSION,
|
|
11375
|
+
project,
|
|
11376
|
+
createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
11377
|
+
traceDirs: options.traceDirs ? [...options.traceDirs] : [...DEFAULT_WORKSPACE_LAYOUT.traceDirs],
|
|
11378
|
+
reportsDir: options.reportsDir ?? DEFAULT_WORKSPACE_LAYOUT.reportsDir,
|
|
11379
|
+
artifactsDir: options.artifactsDir ?? DEFAULT_WORKSPACE_LAYOUT.artifactsDir,
|
|
11380
|
+
bundlesDir: options.bundlesDir ?? DEFAULT_WORKSPACE_LAYOUT.bundlesDir,
|
|
11381
|
+
notesDir: options.notesDir ?? DEFAULT_WORKSPACE_LAYOUT.notesDir,
|
|
11382
|
+
redactionProfile: options.redactionProfile ?? DEFAULT_REDACTION_PROFILE,
|
|
11383
|
+
index
|
|
11384
|
+
};
|
|
11385
|
+
}
|
|
11386
|
+
function isPlainObject(value) {
|
|
11387
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11388
|
+
}
|
|
11389
|
+
function isSafeRelativeWorkspacePath(p) {
|
|
11390
|
+
if (typeof p !== "string") return false;
|
|
11391
|
+
const trimmed = p.trim();
|
|
11392
|
+
if (trimmed === "") return false;
|
|
11393
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return false;
|
|
11394
|
+
if (/^[a-zA-Z]:/.test(trimmed)) return false;
|
|
11395
|
+
const segments = trimmed.split(/[/\\]+/);
|
|
11396
|
+
return !segments.some((seg) => seg === "..");
|
|
11397
|
+
}
|
|
11398
|
+
function validateDirField(value, field, errors) {
|
|
11399
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
11400
|
+
errors.push(`${field} must be a non-empty string`);
|
|
11401
|
+
return;
|
|
11097
11402
|
}
|
|
11098
|
-
if (
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
message: "Reporter artifact path must not contain null bytes.",
|
|
11103
|
-
target: rawPath
|
|
11104
|
-
});
|
|
11105
|
-
return { ok: false, outputDir, diagnostics };
|
|
11403
|
+
if (!isSafeRelativeWorkspacePath(value)) {
|
|
11404
|
+
errors.push(
|
|
11405
|
+
`${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
|
|
11406
|
+
);
|
|
11106
11407
|
}
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
target: rawPath
|
|
11113
|
-
});
|
|
11114
|
-
return { ok: false, outputDir, diagnostics };
|
|
11408
|
+
}
|
|
11409
|
+
function validateIndex(value, errors) {
|
|
11410
|
+
if (!isPlainObject(value)) {
|
|
11411
|
+
errors.push("index must be an object");
|
|
11412
|
+
return void 0;
|
|
11115
11413
|
}
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
|
|
11119
|
-
diagnostics.push({
|
|
11120
|
-
code: "artifact_path_escape",
|
|
11121
|
-
severity: "error",
|
|
11122
|
-
message: "Reporter artifact path must stay under the output directory.",
|
|
11123
|
-
target: rawPath
|
|
11124
|
-
});
|
|
11125
|
-
return { ok: false, outputDir, diagnostics };
|
|
11414
|
+
if (typeof value.enabled !== "boolean") {
|
|
11415
|
+
errors.push("index.enabled must be a boolean");
|
|
11126
11416
|
}
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path10.isAbsolute(relFromOutput)) {
|
|
11130
|
-
diagnostics.push({
|
|
11131
|
-
code: "artifact_path_escape",
|
|
11132
|
-
severity: "error",
|
|
11133
|
-
message: "Reporter artifact path resolved outside the output directory.",
|
|
11134
|
-
target: rawPath
|
|
11135
|
-
});
|
|
11136
|
-
return { ok: false, outputDir, diagnostics };
|
|
11417
|
+
if (!INDEX_TYPES.includes(value.type)) {
|
|
11418
|
+
errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
|
|
11137
11419
|
}
|
|
11420
|
+
if (value.path !== void 0 && !isSafeRelativeWorkspacePath(value.path)) {
|
|
11421
|
+
errors.push(
|
|
11422
|
+
'index.path must be a relative path inside the workspace (no absolute paths or ".." traversal)'
|
|
11423
|
+
);
|
|
11424
|
+
}
|
|
11425
|
+
if (errors.length > 0) return void 0;
|
|
11138
11426
|
return {
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
absolutePath,
|
|
11143
|
-
diagnostics
|
|
11427
|
+
enabled: value.enabled,
|
|
11428
|
+
type: value.type,
|
|
11429
|
+
...value.path !== void 0 ? { path: value.path } : {}
|
|
11144
11430
|
};
|
|
11145
11431
|
}
|
|
11146
|
-
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
|
|
11152
|
-
var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
|
|
11153
|
-
var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
|
|
11154
|
-
var REDACTION_PROFILES = /* @__PURE__ */ new Set(["local", "share", "strict"]);
|
|
11155
|
-
function stable6(value) {
|
|
11156
|
-
if (Array.isArray(value)) return value.map(stable6);
|
|
11157
|
-
if (value === null || typeof value !== "object") return value;
|
|
11158
|
-
const record = value;
|
|
11159
|
-
return Object.fromEntries(
|
|
11160
|
-
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
|
|
11161
|
-
);
|
|
11162
|
-
}
|
|
11163
|
-
function writeJson4(value) {
|
|
11164
|
-
return `${JSON.stringify(stable6(value), null, 2)}
|
|
11165
|
-
`;
|
|
11166
|
-
}
|
|
11167
|
-
function isObject(value) {
|
|
11168
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11169
|
-
}
|
|
11170
|
-
function readString(value, label) {
|
|
11171
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
11172
|
-
throw new Error(`${label} must be a non-empty string.`);
|
|
11432
|
+
function validateWorkspaceManifest(input3) {
|
|
11433
|
+
const errors = [];
|
|
11434
|
+
const warnings = [];
|
|
11435
|
+
if (!isPlainObject(input3)) {
|
|
11436
|
+
return { ok: false, errors: ["manifest must be an object"], warnings };
|
|
11173
11437
|
}
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
}
|
|
11179
|
-
|
|
11180
|
-
const framework = readString(value, "manifest.framework");
|
|
11181
|
-
if (!FRAMEWORKS.has(framework)) {
|
|
11182
|
-
throw new Error(`Unsupported reporter framework: ${framework}.`);
|
|
11438
|
+
if (input3.schemaVersion !== WORKSPACE_SCHEMA_VERSION) {
|
|
11439
|
+
errors.push(
|
|
11440
|
+
`schemaVersion must be "${WORKSPACE_SCHEMA_VERSION}" (received ${JSON.stringify(
|
|
11441
|
+
input3.schemaVersion
|
|
11442
|
+
)})`
|
|
11443
|
+
);
|
|
11183
11444
|
}
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
function readStatus(value) {
|
|
11187
|
-
const status = readString(value, "result.status");
|
|
11188
|
-
if (!STATUSES.has(status)) {
|
|
11189
|
-
throw new Error(`Unsupported reporter test status: ${status}.`);
|
|
11445
|
+
if (typeof input3.project !== "string" || input3.project.trim() === "") {
|
|
11446
|
+
errors.push("project must be a non-empty string");
|
|
11190
11447
|
}
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
|
|
11196
|
-
const format = readString(value.format, `manifest.artifacts[${index}].format`);
|
|
11197
|
-
const redactionProfile = readString(
|
|
11198
|
-
value.redactionProfile,
|
|
11199
|
-
`manifest.artifacts[${index}].redactionProfile`
|
|
11200
|
-
);
|
|
11201
|
-
if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
|
|
11202
|
-
if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
|
|
11203
|
-
if (!REDACTION_PROFILES.has(redactionProfile)) {
|
|
11204
|
-
throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
|
|
11448
|
+
if (typeof input3.createdAt !== "string" || input3.createdAt.trim() === "") {
|
|
11449
|
+
errors.push("createdAt must be a non-empty ISO-8601 string");
|
|
11450
|
+
} else if (Number.isNaN(Date.parse(input3.createdAt))) {
|
|
11451
|
+
errors.push("createdAt must be a valid ISO-8601 date string");
|
|
11205
11452
|
}
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11212
|
-
|
|
11453
|
+
if (!Array.isArray(input3.traceDirs) || input3.traceDirs.length === 0) {
|
|
11454
|
+
errors.push("traceDirs must be a non-empty array");
|
|
11455
|
+
} else {
|
|
11456
|
+
input3.traceDirs.forEach((dir, i) => {
|
|
11457
|
+
if (typeof dir !== "string" || dir.trim() === "") {
|
|
11458
|
+
errors.push(`traceDirs[${i}] must be a non-empty string`);
|
|
11459
|
+
} else if (!isSafeRelativeWorkspacePath(dir)) {
|
|
11460
|
+
errors.push(
|
|
11461
|
+
`traceDirs[${i}] must be a relative path inside the workspace (no absolute paths or ".." traversal)`
|
|
11462
|
+
);
|
|
11463
|
+
}
|
|
11464
|
+
});
|
|
11213
11465
|
}
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11466
|
+
validateDirField(input3.reportsDir, "reportsDir", errors);
|
|
11467
|
+
validateDirField(input3.artifactsDir, "artifactsDir", errors);
|
|
11468
|
+
validateDirField(input3.bundlesDir, "bundlesDir", errors);
|
|
11469
|
+
validateDirField(input3.notesDir, "notesDir", errors);
|
|
11470
|
+
if (!REDACTION_PROFILES.includes(input3.redactionProfile)) {
|
|
11471
|
+
errors.push(`redactionProfile must be one of: ${REDACTION_PROFILES.join(", ")}`);
|
|
11472
|
+
}
|
|
11473
|
+
const indexErrors = [];
|
|
11474
|
+
const index = validateIndex(input3.index, indexErrors);
|
|
11475
|
+
errors.push(...indexErrors);
|
|
11476
|
+
if (index && index.type !== "none" && !index.enabled) {
|
|
11477
|
+
warnings.push(`index.type is "${index.type}" but index.enabled is false`);
|
|
11478
|
+
}
|
|
11479
|
+
if (errors.length > 0 || index === void 0) {
|
|
11480
|
+
return { ok: false, errors, warnings };
|
|
11481
|
+
}
|
|
11482
|
+
const manifest = {
|
|
11483
|
+
schemaVersion: WORKSPACE_SCHEMA_VERSION,
|
|
11484
|
+
project: input3.project.trim(),
|
|
11485
|
+
createdAt: input3.createdAt,
|
|
11486
|
+
traceDirs: input3.traceDirs.map((d) => d.trim()),
|
|
11487
|
+
reportsDir: input3.reportsDir.trim(),
|
|
11488
|
+
artifactsDir: input3.artifactsDir.trim(),
|
|
11489
|
+
bundlesDir: input3.bundlesDir.trim(),
|
|
11490
|
+
notesDir: input3.notesDir.trim(),
|
|
11491
|
+
redactionProfile: input3.redactionProfile,
|
|
11492
|
+
index
|
|
11219
11493
|
};
|
|
11494
|
+
return { ok: true, manifest, errors, warnings };
|
|
11220
11495
|
}
|
|
11221
|
-
function
|
|
11222
|
-
|
|
11223
|
-
if (
|
|
11224
|
-
|
|
11496
|
+
function parseWorkspaceManifest(json) {
|
|
11497
|
+
const warnings = [];
|
|
11498
|
+
if (typeof json !== "string") {
|
|
11499
|
+
return { ok: false, errors: ["manifest input must be a string"], warnings };
|
|
11500
|
+
}
|
|
11501
|
+
if (json.length > MAX_WORKSPACE_MANIFEST_BYTES) {
|
|
11502
|
+
return {
|
|
11503
|
+
ok: false,
|
|
11504
|
+
errors: [
|
|
11505
|
+
`manifest exceeds maximum size of ${MAX_WORKSPACE_MANIFEST_BYTES} bytes`
|
|
11506
|
+
],
|
|
11507
|
+
warnings
|
|
11508
|
+
};
|
|
11509
|
+
}
|
|
11510
|
+
let parsed;
|
|
11511
|
+
try {
|
|
11512
|
+
parsed = JSON.parse(json);
|
|
11513
|
+
} catch {
|
|
11514
|
+
return { ok: false, errors: ["manifest is not valid JSON"], warnings };
|
|
11515
|
+
}
|
|
11516
|
+
return validateWorkspaceManifest(parsed);
|
|
11225
11517
|
}
|
|
11226
|
-
function
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
return value.length;
|
|
11518
|
+
function serializeWorkspaceManifest(manifest) {
|
|
11519
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
11520
|
+
`;
|
|
11230
11521
|
}
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
const
|
|
11234
|
-
const
|
|
11522
|
+
var INDEX_DIR_NAME = "index";
|
|
11523
|
+
function resolveWorkspaceLocation(cwd = process.cwd()) {
|
|
11524
|
+
const projectRoot = path13.resolve(cwd);
|
|
11525
|
+
const workspaceDir = path13.join(projectRoot, WORKSPACE_DIR_NAME);
|
|
11235
11526
|
return {
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
status: readStatus(value.status),
|
|
11240
|
-
...tracePath === void 0 ? {} : { tracePath },
|
|
11241
|
-
artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
|
|
11242
|
-
diagnostics: readDiagnosticsCount(
|
|
11243
|
-
value.diagnostics,
|
|
11244
|
-
`manifest.results[${index}].diagnostics`
|
|
11245
|
-
)
|
|
11527
|
+
projectRoot,
|
|
11528
|
+
workspaceDir,
|
|
11529
|
+
manifestPath: path13.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
|
|
11246
11530
|
};
|
|
11247
11531
|
}
|
|
11248
|
-
function
|
|
11249
|
-
|
|
11250
|
-
const
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
throw new Error(`Unsupported reporter manifest schemaVersion: ${schemaVersion}.`);
|
|
11532
|
+
function resolveInsideWorkspace(workspaceDir, relative) {
|
|
11533
|
+
const base = path13.resolve(workspaceDir);
|
|
11534
|
+
const resolved = path13.resolve(base, relative);
|
|
11535
|
+
const rel = path13.relative(base, resolved);
|
|
11536
|
+
if (rel === "" || rel === "." || !rel.startsWith("..") && !path13.isAbsolute(rel)) {
|
|
11537
|
+
return resolved;
|
|
11255
11538
|
}
|
|
11256
|
-
|
|
11257
|
-
|
|
11539
|
+
throw new Error(
|
|
11540
|
+
`Workspace path "${relative}" resolves outside the workspace directory`
|
|
11541
|
+
);
|
|
11542
|
+
}
|
|
11543
|
+
async function pathExists(p) {
|
|
11544
|
+
try {
|
|
11545
|
+
await access(p);
|
|
11546
|
+
return true;
|
|
11547
|
+
} catch {
|
|
11548
|
+
return false;
|
|
11258
11549
|
}
|
|
11259
|
-
const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
|
|
11260
|
-
const results = candidate.results.map((item, index) => readResult(item, index));
|
|
11261
|
-
const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
|
|
11262
|
-
const manifest = {
|
|
11263
|
-
framework: readFramework(candidate.framework),
|
|
11264
|
-
generatedAt: readString(candidate.generatedAt, "manifest.generatedAt"),
|
|
11265
|
-
results,
|
|
11266
|
-
artifacts
|
|
11267
|
-
};
|
|
11268
|
-
return {
|
|
11269
|
-
packageName: readOptionalString(value.package),
|
|
11270
|
-
manifest,
|
|
11271
|
-
diagnostics
|
|
11272
|
-
};
|
|
11273
11550
|
}
|
|
11274
|
-
function
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
return
|
|
11551
|
+
async function isWritable(p) {
|
|
11552
|
+
try {
|
|
11553
|
+
await access(p, constants$1.W_OK);
|
|
11554
|
+
return true;
|
|
11555
|
+
} catch {
|
|
11556
|
+
return false;
|
|
11278
11557
|
}
|
|
11279
|
-
return relative;
|
|
11280
11558
|
}
|
|
11281
|
-
async function
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
name: safeText(result.name),
|
|
11289
|
-
...result.file === void 0 ? {} : { file: safeText(path10.basename(result.file)) },
|
|
11290
|
-
status: result.status,
|
|
11291
|
-
...result.tracePath === void 0 ? {} : { tracePath: safeText(path10.basename(result.tracePath)) },
|
|
11292
|
-
artifacts: result.artifacts,
|
|
11293
|
-
diagnostics: result.diagnostics
|
|
11294
|
-
}));
|
|
11295
|
-
return {
|
|
11296
|
-
...document.packageName === void 0 ? {} : { packageName: document.packageName },
|
|
11297
|
-
manifestFile: cwdRelative(absolute),
|
|
11298
|
-
framework: manifest.framework,
|
|
11299
|
-
generatedAt: manifest.generatedAt,
|
|
11300
|
-
results,
|
|
11301
|
-
artifacts: manifest.artifacts,
|
|
11302
|
-
diagnostics: document.diagnostics
|
|
11303
|
-
};
|
|
11559
|
+
async function listJsonl(dir) {
|
|
11560
|
+
try {
|
|
11561
|
+
const entries = await readdir(dir);
|
|
11562
|
+
return entries.filter((f) => f.endsWith(".jsonl"));
|
|
11563
|
+
} catch {
|
|
11564
|
+
return [];
|
|
11565
|
+
}
|
|
11304
11566
|
}
|
|
11305
|
-
function
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
for (const result of manifest.results) {
|
|
11320
|
-
summary.tests += 1;
|
|
11321
|
-
if (result.status === "failed") summary.failed += 1;
|
|
11322
|
-
else if (result.status === "passed") summary.passed += 1;
|
|
11323
|
-
else if (result.status === "skipped") summary.skipped += 1;
|
|
11324
|
-
else summary.todo += 1;
|
|
11325
|
-
summary.artifacts += result.artifacts.length;
|
|
11326
|
-
summary.diagnostics += result.diagnostics;
|
|
11327
|
-
}
|
|
11567
|
+
async function countFiles(dir) {
|
|
11568
|
+
try {
|
|
11569
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
11570
|
+
return entries.filter((e) => e.isFile()).length;
|
|
11571
|
+
} catch {
|
|
11572
|
+
return 0;
|
|
11573
|
+
}
|
|
11574
|
+
}
|
|
11575
|
+
async function readWorkspaceManifestFile(location) {
|
|
11576
|
+
let raw;
|
|
11577
|
+
try {
|
|
11578
|
+
raw = await readFile(location.manifestPath, "utf-8");
|
|
11579
|
+
} catch {
|
|
11580
|
+
return { exists: false, ok: false, errors: ["workspace.json not found"], warnings: [] };
|
|
11328
11581
|
}
|
|
11582
|
+
const parsed = parseWorkspaceManifest(raw);
|
|
11329
11583
|
return {
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11584
|
+
exists: true,
|
|
11585
|
+
ok: parsed.ok,
|
|
11586
|
+
...parsed.manifest ? { manifest: parsed.manifest } : {},
|
|
11587
|
+
errors: parsed.errors,
|
|
11588
|
+
warnings: parsed.warnings
|
|
11334
11589
|
};
|
|
11335
11590
|
}
|
|
11336
|
-
function
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
const
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
`| Tests | ${result.summary.tests} |`,
|
|
11350
|
-
`| Failed | ${result.summary.failed} |`,
|
|
11351
|
-
`| Passed | ${result.summary.passed} |`,
|
|
11352
|
-
`| Skipped | ${result.summary.skipped} |`,
|
|
11353
|
-
`| Todo | ${result.summary.todo} |`,
|
|
11354
|
-
`| Artifacts | ${result.summary.artifacts} |`,
|
|
11355
|
-
`| Diagnostics | ${result.summary.diagnostics} |`,
|
|
11356
|
-
"",
|
|
11357
|
-
"## Tests",
|
|
11358
|
-
"",
|
|
11359
|
-
"| Framework | Status | Test | File | Trace | Artifacts |",
|
|
11360
|
-
"| --- | --- | --- | --- | --- | --- |"
|
|
11361
|
-
];
|
|
11362
|
-
const rows = result.manifests.flatMap(
|
|
11363
|
-
(manifest) => manifest.results.map((test) => ({
|
|
11364
|
-
framework: manifest.framework,
|
|
11365
|
-
test
|
|
11366
|
-
}))
|
|
11367
|
-
);
|
|
11368
|
-
if (rows.length === 0) {
|
|
11369
|
-
lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
|
|
11591
|
+
async function createWorkspace(options = {}) {
|
|
11592
|
+
const location = resolveWorkspaceLocation(options.cwd);
|
|
11593
|
+
const dryRun = options.dryRun === true;
|
|
11594
|
+
const existing = await readWorkspaceManifestFile(location);
|
|
11595
|
+
const topLevelTraces = await listJsonl(location.workspaceDir);
|
|
11596
|
+
const detectedExistingTraces = topLevelTraces.length > 0;
|
|
11597
|
+
let manifest;
|
|
11598
|
+
let created;
|
|
11599
|
+
let adopted;
|
|
11600
|
+
if (existing.exists && existing.ok && existing.manifest) {
|
|
11601
|
+
manifest = existing.manifest;
|
|
11602
|
+
created = false;
|
|
11603
|
+
adopted = true;
|
|
11370
11604
|
} else {
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11605
|
+
const project = options.project?.trim() || path13.basename(location.projectRoot) || "workspace";
|
|
11606
|
+
const traceDirs = detectedExistingTraces ? ["runs", "."] : ["runs"];
|
|
11607
|
+
manifest = createDefaultWorkspaceManifest({
|
|
11608
|
+
project,
|
|
11609
|
+
traceDirs,
|
|
11610
|
+
...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {}
|
|
11611
|
+
});
|
|
11612
|
+
created = true;
|
|
11613
|
+
adopted = detectedExistingTraces || existing.exists && !existing.ok;
|
|
11376
11614
|
}
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11615
|
+
const relDirs = uniqueDirs([
|
|
11616
|
+
...manifest.traceDirs.filter((d) => d !== "."),
|
|
11617
|
+
manifest.reportsDir,
|
|
11618
|
+
manifest.artifactsDir,
|
|
11619
|
+
manifest.bundlesDir,
|
|
11620
|
+
manifest.notesDir,
|
|
11621
|
+
INDEX_DIR_NAME
|
|
11622
|
+
]);
|
|
11623
|
+
const createdDirs = [];
|
|
11624
|
+
for (const rel of relDirs) {
|
|
11625
|
+
const abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
11626
|
+
if (await pathExists(abs)) continue;
|
|
11627
|
+
createdDirs.push(rel);
|
|
11628
|
+
if (!dryRun) await mkdir(abs, { recursive: true });
|
|
11382
11629
|
}
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
(
|
|
11386
|
-
);
|
|
11387
|
-
if (artifacts.length === 0) {
|
|
11388
|
-
lines.push("| unknown | unknown | No artifacts found | unknown | unknown |");
|
|
11389
|
-
} else {
|
|
11390
|
-
for (const row of artifacts) {
|
|
11391
|
-
lines.push(
|
|
11392
|
-
`| ${markdownCell(row.framework)} | ${markdownCell(row.artifact.kind)} | ${markdownCell(row.artifact.path)} | ${markdownCell(row.artifact.format)} | ${markdownCell(row.artifact.redactionProfile)} |`
|
|
11393
|
-
);
|
|
11394
|
-
}
|
|
11630
|
+
if (!dryRun && created) {
|
|
11631
|
+
await mkdir(location.workspaceDir, { recursive: true });
|
|
11632
|
+
await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
|
|
11395
11633
|
}
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11634
|
+
return {
|
|
11635
|
+
location,
|
|
11636
|
+
manifest,
|
|
11637
|
+
created,
|
|
11638
|
+
adopted,
|
|
11639
|
+
createdDirs,
|
|
11640
|
+
detectedExistingTraces,
|
|
11641
|
+
dryRun
|
|
11642
|
+
};
|
|
11399
11643
|
}
|
|
11400
|
-
function
|
|
11401
|
-
const
|
|
11402
|
-
const
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11644
|
+
function uniqueDirs(dirs) {
|
|
11645
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11646
|
+
const out = [];
|
|
11647
|
+
for (const d of dirs) {
|
|
11648
|
+
const t = d.trim();
|
|
11649
|
+
if (t === "" || t === "." || seen.has(t)) continue;
|
|
11650
|
+
seen.add(t);
|
|
11651
|
+
out.push(t);
|
|
11652
|
+
}
|
|
11653
|
+
return out;
|
|
11408
11654
|
}
|
|
11409
|
-
async function
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11655
|
+
async function getWorkspaceStatus(location, manifest) {
|
|
11656
|
+
let traceFiles = 0;
|
|
11657
|
+
for (const rel of manifest.traceDirs) {
|
|
11658
|
+
const abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
11659
|
+
traceFiles += (await listJsonl(abs)).length;
|
|
11414
11660
|
}
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11661
|
+
const reports = await countFiles(
|
|
11662
|
+
resolveInsideWorkspace(location.workspaceDir, manifest.reportsDir)
|
|
11663
|
+
);
|
|
11664
|
+
const artifacts = await countFiles(
|
|
11665
|
+
resolveInsideWorkspace(location.workspaceDir, manifest.artifactsDir)
|
|
11666
|
+
);
|
|
11667
|
+
const bundles = await countFiles(
|
|
11668
|
+
resolveInsideWorkspace(location.workspaceDir, manifest.bundlesDir)
|
|
11669
|
+
);
|
|
11670
|
+
const notes = await countFiles(
|
|
11671
|
+
resolveInsideWorkspace(location.workspaceDir, manifest.notesDir)
|
|
11672
|
+
);
|
|
11673
|
+
const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
|
|
11674
|
+
return {
|
|
11675
|
+
project: manifest.project,
|
|
11676
|
+
traceFiles,
|
|
11677
|
+
reports,
|
|
11678
|
+
artifacts,
|
|
11679
|
+
bundles,
|
|
11680
|
+
notes,
|
|
11681
|
+
index: {
|
|
11682
|
+
enabled: manifest.index.enabled,
|
|
11683
|
+
type: manifest.index.type,
|
|
11684
|
+
exists: await pathExists(indexPath2)
|
|
11420
11685
|
}
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
await writeFile(outputPath, markdown, "utf-8");
|
|
11434
|
-
}
|
|
11435
|
-
const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
|
|
11436
|
-
if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
|
|
11437
|
-
const summaryPath = path10.resolve(summaryTarget);
|
|
11438
|
-
await mkdir(path10.dirname(summaryPath), { recursive: true });
|
|
11439
|
-
await appendFile(summaryPath, `
|
|
11440
|
-
${markdown}`, "utf-8");
|
|
11686
|
+
};
|
|
11687
|
+
}
|
|
11688
|
+
async function doctorWorkspace(location) {
|
|
11689
|
+
const checks2 = [];
|
|
11690
|
+
const manifestResult = await readWorkspaceManifestFile(location);
|
|
11691
|
+
if (!manifestResult.exists) {
|
|
11692
|
+
checks2.push({
|
|
11693
|
+
id: "manifest",
|
|
11694
|
+
status: "fail",
|
|
11695
|
+
message: "workspace.json not found (run `agent-inspect workspace init`)"
|
|
11696
|
+
});
|
|
11697
|
+
return { ok: false, checks: checks2 };
|
|
11441
11698
|
}
|
|
11442
|
-
if (
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11699
|
+
if (!manifestResult.ok || !manifestResult.manifest) {
|
|
11700
|
+
checks2.push({
|
|
11701
|
+
id: "manifest",
|
|
11702
|
+
status: "fail",
|
|
11703
|
+
message: `workspace.json is invalid: ${manifestResult.errors.join("; ")}`
|
|
11704
|
+
});
|
|
11705
|
+
return { ok: false, checks: checks2 };
|
|
11449
11706
|
}
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
function normalizeFramework(value) {
|
|
11455
|
-
const raw = (value ?? "custom").trim();
|
|
11456
|
-
if (raw === "ai-sdk" || raw === "openai-agents" || raw === "langchain" || raw === "custom") {
|
|
11457
|
-
return raw;
|
|
11707
|
+
const manifest = manifestResult.manifest;
|
|
11708
|
+
checks2.push({ id: "manifest", status: "pass", message: "workspace.json is valid" });
|
|
11709
|
+
for (const warning of manifestResult.warnings) {
|
|
11710
|
+
checks2.push({ id: "manifest-warning", status: "warn", message: warning });
|
|
11458
11711
|
}
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11712
|
+
const dirFields = [
|
|
11713
|
+
...manifest.traceDirs.filter((d) => d !== ".").map((d, i) => [`traceDir[${i}]`, d]),
|
|
11714
|
+
["reportsDir", manifest.reportsDir],
|
|
11715
|
+
["artifactsDir", manifest.artifactsDir],
|
|
11716
|
+
["bundlesDir", manifest.bundlesDir],
|
|
11717
|
+
["notesDir", manifest.notesDir]
|
|
11718
|
+
];
|
|
11719
|
+
for (const [id, rel] of dirFields) {
|
|
11720
|
+
let abs;
|
|
11721
|
+
try {
|
|
11722
|
+
abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
11723
|
+
} catch (error) {
|
|
11724
|
+
checks2.push({
|
|
11725
|
+
id,
|
|
11726
|
+
status: "fail",
|
|
11727
|
+
message: error instanceof Error ? error.message : String(error)
|
|
11728
|
+
});
|
|
11729
|
+
continue;
|
|
11730
|
+
}
|
|
11731
|
+
if (!await pathExists(abs)) {
|
|
11732
|
+
checks2.push({ id, status: "warn", message: `${rel}/ does not exist yet` });
|
|
11733
|
+
} else if (!await isWritable(abs)) {
|
|
11734
|
+
checks2.push({ id, status: "fail", message: `${rel}/ is not writable` });
|
|
11735
|
+
} else {
|
|
11736
|
+
checks2.push({ id, status: "pass", message: `${rel}/ is present and writable` });
|
|
11737
|
+
}
|
|
11738
|
+
}
|
|
11739
|
+
let newestTraceMtime = 0;
|
|
11740
|
+
for (const rel of manifest.traceDirs) {
|
|
11741
|
+
const abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
11742
|
+
for (const file of await listJsonl(abs)) {
|
|
11743
|
+
try {
|
|
11744
|
+
const s = await stat(path13.join(abs, file));
|
|
11745
|
+
newestTraceMtime = Math.max(newestTraceMtime, s.mtimeMs);
|
|
11746
|
+
} catch {
|
|
11747
|
+
checks2.push({ id: "trace-readability", status: "warn", message: `cannot stat ${rel}/${file}` });
|
|
11748
|
+
}
|
|
11749
|
+
}
|
|
11750
|
+
}
|
|
11751
|
+
if (manifest.index.enabled) {
|
|
11752
|
+
const indexPath2 = manifest.index.path ? resolveInsideWorkspace(location.workspaceDir, manifest.index.path) : resolveInsideWorkspace(location.workspaceDir, INDEX_DIR_NAME);
|
|
11753
|
+
if (!await pathExists(indexPath2)) {
|
|
11754
|
+
checks2.push({ id: "index", status: "warn", message: "index enabled but not built" });
|
|
11755
|
+
} else {
|
|
11756
|
+
try {
|
|
11757
|
+
const s = await stat(indexPath2);
|
|
11758
|
+
if (newestTraceMtime > s.mtimeMs) {
|
|
11759
|
+
checks2.push({ id: "index", status: "warn", message: "index is stale (traces are newer)" });
|
|
11760
|
+
} else {
|
|
11761
|
+
checks2.push({ id: "index", status: "pass", message: "index is present" });
|
|
11762
|
+
}
|
|
11763
|
+
} catch {
|
|
11764
|
+
checks2.push({ id: "index", status: "warn", message: "cannot stat index" });
|
|
11765
|
+
}
|
|
11766
|
+
}
|
|
11767
|
+
}
|
|
11768
|
+
const ok = !checks2.some((c) => c.status === "fail");
|
|
11769
|
+
return { ok, checks: checks2 };
|
|
11478
11770
|
}
|
|
11479
|
-
function
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11771
|
+
async function cleanWorkspace(location, manifest, options = {}) {
|
|
11772
|
+
const dryRun = options.confirm !== true;
|
|
11773
|
+
const targets = uniqueDirs([
|
|
11774
|
+
manifest.reportsDir,
|
|
11775
|
+
manifest.artifactsDir,
|
|
11776
|
+
manifest.bundlesDir,
|
|
11777
|
+
manifest.index.path ?? INDEX_DIR_NAME
|
|
11778
|
+
]);
|
|
11779
|
+
const removed = [];
|
|
11780
|
+
for (const rel of targets) {
|
|
11781
|
+
const abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
11782
|
+
let entries;
|
|
11783
|
+
try {
|
|
11784
|
+
entries = await readdir(abs);
|
|
11785
|
+
} catch {
|
|
11786
|
+
continue;
|
|
11787
|
+
}
|
|
11788
|
+
for (const entry of entries) {
|
|
11789
|
+
const relPath = `${rel}/${entry}`;
|
|
11790
|
+
removed.push(relPath);
|
|
11791
|
+
if (!dryRun) {
|
|
11792
|
+
await rm(path13.join(abs, entry), { recursive: true, force: true });
|
|
11793
|
+
}
|
|
11794
|
+
}
|
|
11795
|
+
}
|
|
11796
|
+
return { dryRun, removed };
|
|
11493
11797
|
}
|
|
11494
11798
|
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
*/
|
|
11505
|
-
import { inspectRun, step } from "agent-inspect";
|
|
11506
|
-
|
|
11507
|
-
async function main() {
|
|
11508
|
-
await inspectRun("openai-agents-demo", async () => {
|
|
11509
|
-
await step.tool("mock-agent-run", async () => "ok");
|
|
11510
|
-
}, { traceDir: ".agent-inspect", silent: true });
|
|
11511
|
-
console.log("Trace written to .agent-inspect/");
|
|
11799
|
+
// packages/cli/src/bundle.ts
|
|
11800
|
+
var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification.";
|
|
11801
|
+
function stable6(value) {
|
|
11802
|
+
if (Array.isArray(value)) return value.map(stable6);
|
|
11803
|
+
if (value === null || typeof value !== "object") return value;
|
|
11804
|
+
const record = value;
|
|
11805
|
+
return Object.fromEntries(
|
|
11806
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
|
|
11807
|
+
);
|
|
11512
11808
|
}
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
console.error(error);
|
|
11516
|
-
process.exitCode = 1;
|
|
11517
|
-
});
|
|
11809
|
+
function writeJson4(value) {
|
|
11810
|
+
return `${JSON.stringify(stable6(value), null, 2)}
|
|
11518
11811
|
`;
|
|
11519
|
-
case "langchain":
|
|
11520
|
-
return `/**
|
|
11521
|
-
* LangChain starter \u2014 wire @agent-inspect/langchain callbacks in your app.
|
|
11522
|
-
* This demo uses manual steps (no API keys).
|
|
11523
|
-
*/
|
|
11524
|
-
import { inspectRun, step } from "agent-inspect";
|
|
11525
|
-
|
|
11526
|
-
async function main() {
|
|
11527
|
-
await inspectRun("langchain-demo", async () => {
|
|
11528
|
-
await step.tool("mock-chain", async () => "ok");
|
|
11529
|
-
}, { traceDir: ".agent-inspect", silent: true });
|
|
11530
|
-
console.log("Trace written to .agent-inspect/");
|
|
11531
11812
|
}
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
process.exitCode = 1;
|
|
11536
|
-
});
|
|
11537
|
-
`;
|
|
11538
|
-
default:
|
|
11539
|
-
return `import { observe } from "agent-inspect";
|
|
11540
|
-
|
|
11541
|
-
class DemoAgent {
|
|
11542
|
-
async run(input: { question: string }) {
|
|
11543
|
-
return { answer: \`Echo: \${input.question}\` };
|
|
11813
|
+
function parseBundleProfile(value) {
|
|
11814
|
+
if (value === void 0 || value === "local" || value === "share" || value === "strict") {
|
|
11815
|
+
return value ?? "share";
|
|
11544
11816
|
}
|
|
11817
|
+
throw new Error(`Unsupported --profile "${value}". Use local, share, or strict.`);
|
|
11545
11818
|
}
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
traceDir: ".agent-inspect",
|
|
11549
|
-
silent: true,
|
|
11550
|
-
});
|
|
11551
|
-
|
|
11552
|
-
await agent.run({ question: "hello" });
|
|
11553
|
-
console.log("Trace written to .agent-inspect/");
|
|
11554
|
-
`;
|
|
11555
|
-
}
|
|
11819
|
+
function toReportProfile(profile) {
|
|
11820
|
+
return profile;
|
|
11556
11821
|
}
|
|
11557
|
-
function
|
|
11558
|
-
return
|
|
11559
|
-
|
|
11560
|
-
on:
|
|
11561
|
-
workflow_dispatch:
|
|
11562
|
-
push:
|
|
11563
|
-
branches: [main]
|
|
11564
|
-
|
|
11565
|
-
jobs:
|
|
11566
|
-
trace-artifacts:
|
|
11567
|
-
runs-on: ubuntu-latest
|
|
11568
|
-
steps:
|
|
11569
|
-
- uses: actions/checkout@v4
|
|
11570
|
-
- uses: actions/setup-node@v4
|
|
11571
|
-
with:
|
|
11572
|
-
node-version: "22"
|
|
11573
|
-
- run: npm ci
|
|
11574
|
-
- run: npm test
|
|
11575
|
-
- name: Upload AgentInspect traces
|
|
11576
|
-
if: always()
|
|
11577
|
-
uses: actions/upload-artifact@v4
|
|
11578
|
-
with:
|
|
11579
|
-
name: agent-inspect-traces
|
|
11580
|
-
path: .agent-inspect/**/*.jsonl
|
|
11581
|
-
if-no-files-found: ignore
|
|
11582
|
-
`;
|
|
11822
|
+
function escapeHtml3(value) {
|
|
11823
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
11583
11824
|
}
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
const demoPath = framework === "custom" ? path10.join("examples", "agent-inspect-demo.mjs") : path10.join("examples", `agent-inspect-${framework}-demo.mjs`);
|
|
11588
|
-
const candidates = [
|
|
11589
|
-
{ rel: CONFIG_FILE, content: configTemplate(framework) },
|
|
11590
|
-
{ rel: GITKEEP, content: "" },
|
|
11591
|
-
{ rel: demoPath, content: demoTemplate(framework) }
|
|
11592
|
-
];
|
|
11593
|
-
if (options.ci === "github") {
|
|
11594
|
-
candidates.push({
|
|
11595
|
-
rel: ".github/workflows/agent-inspect-artifacts.yml",
|
|
11596
|
-
content: githubWorkflowTemplate()
|
|
11597
|
-
});
|
|
11825
|
+
function safetyStatusFromAssess(status) {
|
|
11826
|
+
if (status === "SAFE" || status === "SAFE WITH WARNINGS" || status === "UNSAFE" || status === "UNKNOWN") {
|
|
11827
|
+
return status;
|
|
11598
11828
|
}
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11829
|
+
return "UNKNOWN";
|
|
11830
|
+
}
|
|
11831
|
+
async function resolveOutputDir(options, runIds, cwd) {
|
|
11832
|
+
if (options.out !== void 0 && options.out.trim() !== "") {
|
|
11833
|
+
const normalized = normalizeBundleOutputPath(options.out);
|
|
11602
11834
|
try {
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11835
|
+
const location = resolveWorkspaceLocation(cwd);
|
|
11836
|
+
const manifest = await readWorkspaceManifestFile(location);
|
|
11837
|
+
if (manifest.ok && manifest.manifest) {
|
|
11838
|
+
const rel = path13.relative(location.workspaceDir, normalized);
|
|
11839
|
+
if (!rel.startsWith("..") && !path13.isAbsolute(rel)) {
|
|
11840
|
+
return resolveInsideWorkspace(location.workspaceDir, rel);
|
|
11841
|
+
}
|
|
11842
|
+
}
|
|
11843
|
+
} catch {
|
|
11612
11844
|
}
|
|
11845
|
+
return normalized;
|
|
11613
11846
|
}
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
written.push(entry.path);
|
|
11625
|
-
continue;
|
|
11847
|
+
try {
|
|
11848
|
+
const location = resolveWorkspaceLocation(cwd);
|
|
11849
|
+
const manifest = await readWorkspaceManifestFile(location);
|
|
11850
|
+
if (manifest.ok && manifest.manifest) {
|
|
11851
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11852
|
+
const label = runIds.length === 1 ? runIds[0] : `multi-${runIds.length}`;
|
|
11853
|
+
return resolveInsideWorkspace(
|
|
11854
|
+
location.workspaceDir,
|
|
11855
|
+
path13.join(manifest.manifest.bundlesDir, `bundle-${label}-${stamp}`)
|
|
11856
|
+
);
|
|
11626
11857
|
}
|
|
11627
|
-
|
|
11628
|
-
const content = entry.path === CONFIG_FILE ? configTemplate(plan.framework) : entry.path === GITKEEP ? "" : entry.path.endsWith(".yml") ? githubWorkflowTemplate() : demoTemplate(plan.framework);
|
|
11629
|
-
await writeFile(abs, content, "utf-8");
|
|
11630
|
-
written.push(entry.path);
|
|
11858
|
+
} catch {
|
|
11631
11859
|
}
|
|
11632
|
-
return
|
|
11860
|
+
return defaultBundleOutputPath(runIds);
|
|
11633
11861
|
}
|
|
11634
|
-
async function
|
|
11635
|
-
const
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11862
|
+
async function writeBundleFile(outputDir, relativePath, content, files) {
|
|
11863
|
+
const outPath = path13.join(outputDir, relativePath);
|
|
11864
|
+
await mkdir(path13.dirname(outPath), { recursive: true });
|
|
11865
|
+
await writeFile(outPath, content, "utf-8");
|
|
11866
|
+
files.push(relativePath);
|
|
11867
|
+
}
|
|
11868
|
+
function renderBundleIndexHtml(parts) {
|
|
11869
|
+
const links = parts.runIds.map(
|
|
11870
|
+
(runId) => `<li><a href="assets/runs/${escapeHtml3(runId)}.html">${escapeHtml3(runId)}</a></li>`
|
|
11871
|
+
).join("");
|
|
11872
|
+
const sections = parts.runIds.map((runId) => {
|
|
11873
|
+
const html = parts.reports.get(runId) ?? "";
|
|
11874
|
+
return `<section id="run-${escapeHtml3(runId)}"><h2>${escapeHtml3(runId)}</h2>${html}</section>`;
|
|
11875
|
+
}).join("\n");
|
|
11876
|
+
return `<!doctype html>
|
|
11877
|
+
<html lang="en">
|
|
11878
|
+
<head>
|
|
11879
|
+
<meta charset="utf-8"/>
|
|
11880
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
11881
|
+
<title>AgentInspect Bundle</title>
|
|
11882
|
+
<style>
|
|
11883
|
+
body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}
|
|
11884
|
+
a{color:#0366d6}
|
|
11885
|
+
section{border-top:1px solid #ddd;margin-top:1.5rem;padding-top:1rem}
|
|
11886
|
+
</style>
|
|
11887
|
+
</head>
|
|
11888
|
+
<body>
|
|
11889
|
+
<h1>AgentInspect trace bundle</h1>
|
|
11890
|
+
<p>${escapeHtml3(BUNDLE_NOTE)}</p>
|
|
11891
|
+
<h2>Runs</h2>
|
|
11892
|
+
<ul>${links}</ul>
|
|
11893
|
+
${sections}
|
|
11894
|
+
</body>
|
|
11895
|
+
</html>
|
|
11896
|
+
`;
|
|
11897
|
+
}
|
|
11898
|
+
function extractHtmlBody(content) {
|
|
11899
|
+
const match = content.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
11900
|
+
return match?.[1]?.trim() ?? content;
|
|
11901
|
+
}
|
|
11902
|
+
async function bundleCommand(runIdArg, options = {}) {
|
|
11903
|
+
const profile = parseBundleProfile(options.profile);
|
|
11904
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
11905
|
+
const cwd = process.cwd();
|
|
11906
|
+
let resolveResult;
|
|
11907
|
+
try {
|
|
11908
|
+
const { runs } = await loadSessionRuns(traceDir);
|
|
11909
|
+
const staleThresholdMs = options.staleAfter && options.staleAfter.trim() !== "" ? parseDuration(options.staleAfter.trim()) : void 0;
|
|
11910
|
+
const index = buildSessionIndex(runs, {
|
|
11911
|
+
correlateByGroupId: options.correlateGroup === true,
|
|
11912
|
+
staleThresholdMs
|
|
11913
|
+
});
|
|
11914
|
+
resolveResult = resolveBundleRunIds(index, runs, {
|
|
11915
|
+
...runIdArg !== void 0 && runIdArg.trim() !== "" ? { runId: runIdArg.trim() } : {},
|
|
11916
|
+
...options.session ? { sessionId: options.session } : {},
|
|
11917
|
+
...options.since ? { since: options.since } : {}
|
|
11918
|
+
});
|
|
11919
|
+
} catch (error) {
|
|
11920
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11640
11921
|
if (options.json) {
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
framework: plan.framework,
|
|
11645
|
-
ci: plan.ci ?? null,
|
|
11646
|
-
dryRun: options.dryRun === true,
|
|
11647
|
-
planned: plan.files,
|
|
11648
|
-
wouldWrite: options.dryRun ? toWrite : void 0
|
|
11649
|
-
};
|
|
11650
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
11651
|
-
if (options.dryRun) return;
|
|
11922
|
+
console.log(writeJson4({ ok: false, error: message }).trimEnd());
|
|
11923
|
+
} else {
|
|
11924
|
+
console.error(`[AgentInspect] bundle failed: ${message}`);
|
|
11652
11925
|
}
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11926
|
+
process.exitCode = 1;
|
|
11927
|
+
return;
|
|
11928
|
+
}
|
|
11929
|
+
const outputDir = await resolveOutputDir(options, resolveResult.runIds, cwd);
|
|
11930
|
+
const files = [];
|
|
11931
|
+
const checkRuns = [];
|
|
11932
|
+
const redactionRuns = [];
|
|
11933
|
+
const htmlByRun = /* @__PURE__ */ new Map();
|
|
11934
|
+
const redactedJsonlByRun = /* @__PURE__ */ new Map();
|
|
11935
|
+
let combinedJsonl = "";
|
|
11936
|
+
for (const runId of resolveResult.runIds) {
|
|
11937
|
+
const tracePath = getTraceFilePath(runId, traceDir);
|
|
11938
|
+
let rawContent;
|
|
11939
|
+
let sourceMtimeMs;
|
|
11940
|
+
try {
|
|
11941
|
+
rawContent = await readFile(tracePath, "utf-8");
|
|
11942
|
+
sourceMtimeMs = (await stat(tracePath)).mtimeMs;
|
|
11943
|
+
} catch (error) {
|
|
11944
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11945
|
+
if (options.json) {
|
|
11946
|
+
console.log(writeJson4({ ok: false, error: message, runId }).trimEnd());
|
|
11947
|
+
} else {
|
|
11948
|
+
console.error(`[AgentInspect] bundle failed: ${message}`);
|
|
11665
11949
|
}
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11950
|
+
process.exitCode = 1;
|
|
11951
|
+
return;
|
|
11952
|
+
}
|
|
11953
|
+
let read;
|
|
11954
|
+
try {
|
|
11955
|
+
read = await openTrace(
|
|
11956
|
+
{ type: "file", path: tracePath },
|
|
11957
|
+
{ format: "agent-inspect-jsonl" }
|
|
11958
|
+
);
|
|
11959
|
+
} catch (error) {
|
|
11960
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11961
|
+
if (options.json) {
|
|
11962
|
+
console.log(writeJson4({ ok: false, error: message, runId }).trimEnd());
|
|
11963
|
+
} else {
|
|
11964
|
+
console.error(`[AgentInspect] bundle failed: ${message}`);
|
|
11670
11965
|
}
|
|
11671
|
-
|
|
11672
|
-
|
|
11966
|
+
process.exitCode = 1;
|
|
11967
|
+
return;
|
|
11673
11968
|
}
|
|
11674
|
-
|
|
11675
|
-
const
|
|
11969
|
+
const safety = assessOpenedTrace(read, { run: runId });
|
|
11970
|
+
const status = safetyStatusFromAssess(safety.status);
|
|
11971
|
+
checkRuns.push({
|
|
11972
|
+
runId,
|
|
11973
|
+
status,
|
|
11974
|
+
errors: safety.summary.errors,
|
|
11975
|
+
warnings: safety.summary.warnings,
|
|
11976
|
+
findings: safety.summary.findings
|
|
11977
|
+
});
|
|
11978
|
+
const redacted = redactTraceContent(rawContent, toReportProfile(profile));
|
|
11979
|
+
redactedJsonlByRun.set(runId, redacted.content);
|
|
11980
|
+
combinedJsonl += redacted.content.endsWith("\n") ? redacted.content : `${redacted.content}
|
|
11981
|
+
`;
|
|
11982
|
+
const detectors = [
|
|
11983
|
+
...new Set(redacted.findings.map((finding) => finding.detector))
|
|
11984
|
+
].sort((a, b) => a.localeCompare(b));
|
|
11985
|
+
redactionRuns.push({
|
|
11986
|
+
runId,
|
|
11987
|
+
findings: redacted.findings.length,
|
|
11988
|
+
detectors
|
|
11989
|
+
});
|
|
11990
|
+
const selected = read.runs.find((run) => run.runId === runId);
|
|
11991
|
+
const runReport = exportRunTree(selected ?? read.runs[0], {
|
|
11992
|
+
format: "html",
|
|
11993
|
+
redacted: true,
|
|
11994
|
+
redactionProfile: toReportProfile(profile)
|
|
11995
|
+
});
|
|
11996
|
+
htmlByRun.set(runId, extractHtmlBody(runReport.content));
|
|
11997
|
+
const afterMtime = (await stat(tracePath)).mtimeMs;
|
|
11998
|
+
if (afterMtime !== sourceMtimeMs) {
|
|
11999
|
+
const message = `Source trace "${runId}" was modified during bundle creation.`;
|
|
12000
|
+
if (options.json) {
|
|
12001
|
+
console.log(writeJson4({ ok: false, error: message }).trimEnd());
|
|
12002
|
+
} else {
|
|
12003
|
+
console.error(`[AgentInspect] bundle failed: ${message}`);
|
|
12004
|
+
}
|
|
12005
|
+
process.exitCode = 1;
|
|
12006
|
+
return;
|
|
12007
|
+
}
|
|
12008
|
+
}
|
|
12009
|
+
const checks2 = {
|
|
12010
|
+
aggregateStatus: aggregateBundleSafeStatus(checkRuns.map((run) => run.status)),
|
|
12011
|
+
runs: checkRuns
|
|
12012
|
+
};
|
|
12013
|
+
if (bundleFailsOnSafety(checks2.aggregateStatus, options.allowUnsafe === true)) {
|
|
11676
12014
|
if (options.json) {
|
|
11677
|
-
console.log(
|
|
12015
|
+
console.log(
|
|
12016
|
+
writeJson4({
|
|
12017
|
+
ok: false,
|
|
12018
|
+
error: `Bundle safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`,
|
|
12019
|
+
checks: checks2
|
|
12020
|
+
}).trimEnd()
|
|
12021
|
+
);
|
|
11678
12022
|
} else {
|
|
11679
|
-
console.error(
|
|
12023
|
+
console.error(
|
|
12024
|
+
`[AgentInspect] bundle refused: safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`
|
|
12025
|
+
);
|
|
11680
12026
|
}
|
|
11681
12027
|
process.exitCode = 1;
|
|
12028
|
+
return;
|
|
11682
12029
|
}
|
|
11683
|
-
|
|
11684
|
-
|
|
11685
|
-
|
|
11686
|
-
|
|
11687
|
-
"openai-agents": ["@agent-inspect/openai-agents"],
|
|
11688
|
-
langchain: ["@agent-inspect/langchain"]
|
|
11689
|
-
};
|
|
11690
|
-
function nodeVersionCheck() {
|
|
11691
|
-
const major = Number(process2.versions.node.split(".")[0]);
|
|
11692
|
-
if (Number.isNaN(major) || major < 20) {
|
|
11693
|
-
return {
|
|
11694
|
-
id: "node-version",
|
|
11695
|
-
status: "fail",
|
|
11696
|
-
message: `Node ${process2.versions.node} is below the supported minimum (20).`,
|
|
11697
|
-
remediation: "Upgrade to Node 20 LTS or newer.",
|
|
11698
|
-
evidence: process2.versions.node
|
|
11699
|
-
};
|
|
11700
|
-
}
|
|
11701
|
-
return {
|
|
11702
|
-
id: "node-version",
|
|
11703
|
-
status: "pass",
|
|
11704
|
-
message: `Node ${process2.versions.node} meets the minimum (>=20).`,
|
|
11705
|
-
evidence: process2.versions.node
|
|
12030
|
+
const redactionReport = {
|
|
12031
|
+
profile,
|
|
12032
|
+
totalFindings: redactionRuns.reduce((sum, run) => sum + run.findings, 0),
|
|
12033
|
+
runs: redactionRuns
|
|
11706
12034
|
};
|
|
11707
|
-
}
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
12035
|
+
await mkdir(outputDir, { recursive: true });
|
|
12036
|
+
for (const runId of resolveResult.runIds) {
|
|
12037
|
+
const jsonl = redactedJsonlByRun.get(runId) ?? "";
|
|
12038
|
+
await writeBundleFile(outputDir, `assets/runs/${runId}.jsonl`, jsonl, files);
|
|
12039
|
+
const html = htmlByRun.get(runId) ?? "";
|
|
12040
|
+
await writeBundleFile(
|
|
12041
|
+
outputDir,
|
|
12042
|
+
`assets/runs/${runId}.html`,
|
|
12043
|
+
runReportWrap(html, runId),
|
|
12044
|
+
files
|
|
12045
|
+
);
|
|
11717
12046
|
}
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
12047
|
+
const primaryRunId = resolveResult.runIds[0];
|
|
12048
|
+
const traceHtml = resolveResult.runIds.length === 1 ? runReportWrap(htmlByRun.get(primaryRunId) ?? "", primaryRunId) : renderBundleIndexHtml({ runIds: resolveResult.runIds, reports: htmlByRun });
|
|
12049
|
+
await writeBundleFile(outputDir, "trace.jsonl", combinedJsonl, files);
|
|
12050
|
+
await writeBundleFile(outputDir, "trace.html", traceHtml, files);
|
|
12051
|
+
await writeBundleFile(
|
|
12052
|
+
outputDir,
|
|
12053
|
+
"check-results.json",
|
|
12054
|
+
writeJson4(checks2),
|
|
12055
|
+
files
|
|
12056
|
+
);
|
|
12057
|
+
await writeBundleFile(
|
|
12058
|
+
outputDir,
|
|
12059
|
+
"eval-results.json",
|
|
12060
|
+
writeJson4(buildPlaceholderArtifact()),
|
|
12061
|
+
files
|
|
12062
|
+
);
|
|
12063
|
+
await writeBundleFile(
|
|
12064
|
+
outputDir,
|
|
12065
|
+
"redaction-report.json",
|
|
12066
|
+
writeJson4(redactionReport),
|
|
12067
|
+
files
|
|
12068
|
+
);
|
|
12069
|
+
await writeBundleFile(
|
|
12070
|
+
outputDir,
|
|
12071
|
+
"performance-summary.json",
|
|
12072
|
+
writeJson4(buildPlaceholderArtifact()),
|
|
12073
|
+
files
|
|
12074
|
+
);
|
|
12075
|
+
const metadata = buildBundleMetadata({
|
|
12076
|
+
agentInspectVersion: version,
|
|
12077
|
+
profile,
|
|
12078
|
+
resolve: resolveResult,
|
|
12079
|
+
checks: checks2,
|
|
12080
|
+
files: [...files]
|
|
12081
|
+
});
|
|
12082
|
+
await writeBundleFile(outputDir, "metadata.json", writeJson4(metadata), files);
|
|
12083
|
+
await writeBundleFile(
|
|
12084
|
+
outputDir,
|
|
12085
|
+
"summary.md",
|
|
12086
|
+
buildBundleSummaryMarkdown({ metadata, checks: checks2, redaction: redactionReport }),
|
|
12087
|
+
files
|
|
12088
|
+
);
|
|
12089
|
+
metadata.files = [...files].sort((a, b) => a.localeCompare(b));
|
|
12090
|
+
await writeFile(path13.join(outputDir, "metadata.json"), writeJson4(metadata), "utf-8");
|
|
12091
|
+
if (options.json) {
|
|
12092
|
+
console.log(
|
|
12093
|
+
writeJson4({
|
|
12094
|
+
ok: true,
|
|
12095
|
+
outputDir,
|
|
12096
|
+
metadata,
|
|
12097
|
+
checks: checks2,
|
|
12098
|
+
redaction: redactionReport
|
|
12099
|
+
}).trimEnd()
|
|
12100
|
+
);
|
|
12101
|
+
return;
|
|
11744
12102
|
}
|
|
12103
|
+
console.log(`Bundle written to ${outputDir}`);
|
|
12104
|
+
console.log(`Safe status: ${metadata.safeStatus}`);
|
|
12105
|
+
console.log(`Runs: ${resolveResult.runIds.join(", ")}`);
|
|
12106
|
+
console.log(`Files: ${metadata.files.length}`);
|
|
11745
12107
|
}
|
|
11746
|
-
function
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
12108
|
+
function runReportWrap(body, runId) {
|
|
12109
|
+
return `<!doctype html>
|
|
12110
|
+
<html lang="en">
|
|
12111
|
+
<head>
|
|
12112
|
+
<meta charset="utf-8"/>
|
|
12113
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
12114
|
+
<title>AgentInspect \u2014 ${escapeHtml3(runId)}</title>
|
|
12115
|
+
<style>body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}</style>
|
|
12116
|
+
</head>
|
|
12117
|
+
<body>
|
|
12118
|
+
${body}
|
|
12119
|
+
</body>
|
|
12120
|
+
</html>
|
|
12121
|
+
`;
|
|
11755
12122
|
}
|
|
11756
|
-
function
|
|
11757
|
-
const
|
|
11758
|
-
const
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
message: "
|
|
11765
|
-
});
|
|
11766
|
-
} catch {
|
|
11767
|
-
results.push({
|
|
11768
|
-
id: "import-agent-inspect",
|
|
11769
|
-
status: "warn",
|
|
11770
|
-
message: "agent-inspect is not installed in the current project.",
|
|
11771
|
-
remediation: "Run npm install agent-inspect (or pnpm add agent-inspect)."
|
|
12123
|
+
function validateReporterArtifactPath(options) {
|
|
12124
|
+
const outputDir = path13.resolve(options.outputDir);
|
|
12125
|
+
const diagnostics = [];
|
|
12126
|
+
const rawPath = options.relativePath;
|
|
12127
|
+
if (rawPath.length === 0) {
|
|
12128
|
+
diagnostics.push({
|
|
12129
|
+
code: "artifact_path_empty",
|
|
12130
|
+
severity: "error",
|
|
12131
|
+
message: "Reporter artifact path must not be empty."
|
|
11772
12132
|
});
|
|
12133
|
+
return { ok: false, outputDir, diagnostics };
|
|
11773
12134
|
}
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
|
|
11778
|
-
|
|
11779
|
-
|
|
12135
|
+
if (rawPath.includes("\0")) {
|
|
12136
|
+
diagnostics.push({
|
|
12137
|
+
code: "invalid_artifact_path",
|
|
12138
|
+
severity: "error",
|
|
12139
|
+
message: "Reporter artifact path must not contain null bytes.",
|
|
12140
|
+
target: rawPath
|
|
11780
12141
|
});
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
12142
|
+
return { ok: false, outputDir, diagnostics };
|
|
12143
|
+
}
|
|
12144
|
+
if (path13.isAbsolute(rawPath) || path13.win32.isAbsolute(rawPath)) {
|
|
12145
|
+
diagnostics.push({
|
|
12146
|
+
code: "artifact_path_absolute",
|
|
12147
|
+
severity: "error",
|
|
12148
|
+
message: "Reporter artifact path must be relative.",
|
|
12149
|
+
target: rawPath
|
|
11786
12150
|
});
|
|
12151
|
+
return { ok: false, outputDir, diagnostics };
|
|
11787
12152
|
}
|
|
11788
|
-
|
|
11789
|
-
|
|
11790
|
-
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
return packages.map((name) => {
|
|
11799
|
-
const resolved = resolvePackage(cwd, name);
|
|
11800
|
-
if (!resolved.ok) {
|
|
11801
|
-
return {
|
|
11802
|
-
id: `optional-package-${name}`,
|
|
11803
|
-
status: framework !== void 0 ? "warn" : "skipped",
|
|
11804
|
-
message: `${name} is not installed.`,
|
|
11805
|
-
remediation: framework !== void 0 ? `npm install ${name}` : void 0
|
|
11806
|
-
};
|
|
11807
|
-
}
|
|
11808
|
-
return {
|
|
11809
|
-
id: `optional-package-${name}`,
|
|
11810
|
-
status: "pass",
|
|
11811
|
-
message: `${name} is available.`,
|
|
11812
|
-
evidence: resolved.version
|
|
11813
|
-
};
|
|
11814
|
-
});
|
|
11815
|
-
}
|
|
11816
|
-
function versionMismatchCheck(cwd) {
|
|
11817
|
-
const root = resolvePackage(cwd, "agent-inspect");
|
|
11818
|
-
if (!root.ok || root.version === void 0) {
|
|
11819
|
-
return {
|
|
11820
|
-
id: "version-alignment",
|
|
11821
|
-
status: "skipped",
|
|
11822
|
-
message: "Skipped version alignment \u2014 agent-inspect not installed locally."
|
|
11823
|
-
};
|
|
12153
|
+
const normalized = path13.posix.normalize(rawPath.replace(/\\/g, "/"));
|
|
12154
|
+
const segments = normalized.split("/");
|
|
12155
|
+
if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
|
|
12156
|
+
diagnostics.push({
|
|
12157
|
+
code: "artifact_path_escape",
|
|
12158
|
+
severity: "error",
|
|
12159
|
+
message: "Reporter artifact path must stay under the output directory.",
|
|
12160
|
+
target: rawPath
|
|
12161
|
+
});
|
|
12162
|
+
return { ok: false, outputDir, diagnostics };
|
|
11824
12163
|
}
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
12164
|
+
const absolutePath = path13.resolve(outputDir, normalized);
|
|
12165
|
+
const relFromOutput = path13.relative(outputDir, absolutePath);
|
|
12166
|
+
if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path13.isAbsolute(relFromOutput)) {
|
|
12167
|
+
diagnostics.push({
|
|
12168
|
+
code: "artifact_path_escape",
|
|
12169
|
+
severity: "error",
|
|
12170
|
+
message: "Reporter artifact path resolved outside the output directory.",
|
|
12171
|
+
target: rawPath
|
|
12172
|
+
});
|
|
12173
|
+
return { ok: false, outputDir, diagnostics };
|
|
11833
12174
|
}
|
|
11834
12175
|
return {
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
12176
|
+
ok: true,
|
|
12177
|
+
outputDir,
|
|
12178
|
+
relativePath: normalized,
|
|
12179
|
+
absolutePath,
|
|
12180
|
+
diagnostics
|
|
11838
12181
|
};
|
|
11839
12182
|
}
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
if (options.checkImports !== false) {
|
|
11857
|
-
checks2.push(...importSmoke(cwd));
|
|
11858
|
-
}
|
|
11859
|
-
checks2.push(...optionalPackageChecks(cwd, options.framework));
|
|
11860
|
-
return checks2.sort((a, b) => a.id.localeCompare(b.id));
|
|
12183
|
+
|
|
12184
|
+
// packages/cli/src/ci-summary.ts
|
|
12185
|
+
var NOTE2 = "Generated locally by AgentInspect from reporter artifact manifests. Trace contents are not embedded.";
|
|
12186
|
+
var MAX_TEXT = 180;
|
|
12187
|
+
var FRAMEWORKS = /* @__PURE__ */ new Set(["vitest", "jest", "manual"]);
|
|
12188
|
+
var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
|
|
12189
|
+
var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
|
|
12190
|
+
var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
|
|
12191
|
+
var REDACTION_PROFILES2 = /* @__PURE__ */ new Set(["local", "share", "strict"]);
|
|
12192
|
+
function stable7(value) {
|
|
12193
|
+
if (Array.isArray(value)) return value.map(stable7);
|
|
12194
|
+
if (value === null || typeof value !== "object") return value;
|
|
12195
|
+
const record = value;
|
|
12196
|
+
return Object.fromEntries(
|
|
12197
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable7(record[key])])
|
|
12198
|
+
);
|
|
11861
12199
|
}
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
const warned = checks2.filter((check) => check.status === "warn").length;
|
|
11866
|
-
if (options.json) {
|
|
11867
|
-
console.log(
|
|
11868
|
-
JSON.stringify(
|
|
11869
|
-
{
|
|
11870
|
-
ok: failed === 0,
|
|
11871
|
-
version,
|
|
11872
|
-
summary: { pass: checks2.filter((c) => c.status === "pass").length, warn: warned, fail: failed },
|
|
11873
|
-
checks: checks2
|
|
11874
|
-
},
|
|
11875
|
-
null,
|
|
11876
|
-
2
|
|
11877
|
-
)
|
|
11878
|
-
);
|
|
11879
|
-
if (failed > 0) process2.exitCode = 1;
|
|
11880
|
-
return;
|
|
11881
|
-
}
|
|
11882
|
-
console.log("AgentInspect doctor");
|
|
11883
|
-
for (const check of checks2) {
|
|
11884
|
-
const tag = check.status.toUpperCase();
|
|
11885
|
-
console.log(`[${tag}] ${check.id}: ${check.message}`);
|
|
11886
|
-
if (check.remediation) console.log(` \u2192 ${check.remediation}`);
|
|
11887
|
-
}
|
|
11888
|
-
console.log(`
|
|
11889
|
-
Summary: ${failed} failed, ${warned} warnings`);
|
|
11890
|
-
if (failed > 0) process2.exitCode = 1;
|
|
12200
|
+
function writeJson5(value) {
|
|
12201
|
+
return `${JSON.stringify(stable7(value), null, 2)}
|
|
12202
|
+
`;
|
|
11891
12203
|
}
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
function defineIndexer(indexer) {
|
|
11895
|
-
if (!indexer.id.trim()) throw new Error("indexer id is required");
|
|
11896
|
-
return indexer;
|
|
12204
|
+
function isObject(value) {
|
|
12205
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11897
12206
|
}
|
|
11898
|
-
|
|
11899
|
-
|
|
11900
|
-
|
|
11901
|
-
const td = new TraceDirectory({ dir: traceDir });
|
|
11902
|
-
const files = await td.list();
|
|
11903
|
-
for (const file of files) {
|
|
11904
|
-
const stats = await td.getFileStats(file);
|
|
11905
|
-
if (stats.mtimeMs > builtMs) return true;
|
|
12207
|
+
function readString(value, label) {
|
|
12208
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
12209
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
11906
12210
|
}
|
|
11907
|
-
return
|
|
11908
|
-
}
|
|
11909
|
-
function createTraceDirectoryIndexer() {
|
|
11910
|
-
return defineIndexer({
|
|
11911
|
-
id: "trace-directory-metadata",
|
|
11912
|
-
async rebuild(traceDir, options = {}) {
|
|
11913
|
-
const warnings = [];
|
|
11914
|
-
const td = new TraceDirectory({ dir: traceDir });
|
|
11915
|
-
const files = await td.list();
|
|
11916
|
-
const maxEntries = options.maxEntries ?? 1e4;
|
|
11917
|
-
if (files.length > maxEntries) {
|
|
11918
|
-
warnings.push(
|
|
11919
|
-
`indexer.truncated: trace directory has ${files.length} files; indexing first ${maxEntries}`
|
|
11920
|
-
);
|
|
11921
|
-
}
|
|
11922
|
-
const slice = files.slice(0, maxEntries);
|
|
11923
|
-
const metas = await loadTraceMetadataList(
|
|
11924
|
-
traceDir,
|
|
11925
|
-
slice,
|
|
11926
|
-
(fileName) => td.getPath(fileName)
|
|
11927
|
-
);
|
|
11928
|
-
const entries = metas.map((meta) => ({
|
|
11929
|
-
runId: meta.runId,
|
|
11930
|
-
path: meta.filePath,
|
|
11931
|
-
name: meta.name,
|
|
11932
|
-
startedAt: meta.startedAt,
|
|
11933
|
-
status: meta.status
|
|
11934
|
-
})).sort((a, b) => a.runId.localeCompare(b.runId));
|
|
11935
|
-
if (entries.length < slice.length) {
|
|
11936
|
-
warnings.push(
|
|
11937
|
-
`indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
|
|
11938
|
-
);
|
|
11939
|
-
}
|
|
11940
|
-
return {
|
|
11941
|
-
traceDir,
|
|
11942
|
-
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11943
|
-
entries,
|
|
11944
|
-
warnings
|
|
11945
|
-
};
|
|
11946
|
-
}
|
|
11947
|
-
});
|
|
12211
|
+
return safeText(value);
|
|
11948
12212
|
}
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
var INDEX_FILENAME = ".agent-inspect-index.json";
|
|
11952
|
-
function traceIndexPath(traceDir) {
|
|
11953
|
-
return path10.join(traceDir, INDEX_FILENAME);
|
|
12213
|
+
function readOptionalString(value) {
|
|
12214
|
+
return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
|
|
11954
12215
|
}
|
|
11955
|
-
function
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
throw new Error("--max-entries must be a positive integer.");
|
|
12216
|
+
function readFramework(value) {
|
|
12217
|
+
const framework = readString(value, "manifest.framework");
|
|
12218
|
+
if (!FRAMEWORKS.has(framework)) {
|
|
12219
|
+
throw new Error(`Unsupported reporter framework: ${framework}.`);
|
|
11960
12220
|
}
|
|
11961
|
-
return
|
|
12221
|
+
return framework;
|
|
11962
12222
|
}
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
} catch {
|
|
11968
|
-
return void 0;
|
|
12223
|
+
function readStatus(value) {
|
|
12224
|
+
const status = readString(value, "result.status");
|
|
12225
|
+
if (!STATUSES.has(status)) {
|
|
12226
|
+
throw new Error(`Unsupported reporter test status: ${status}.`);
|
|
11969
12227
|
}
|
|
12228
|
+
return status;
|
|
11970
12229
|
}
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
console.log(JSON.stringify({ ok: true, indexPath: indexPath2, ...snapshot }, null, 2));
|
|
11984
|
-
return;
|
|
11985
|
-
}
|
|
11986
|
-
console.log(`Built trace index: ${indexPath2}`);
|
|
11987
|
-
console.log(`Entries: ${snapshot.entries.length}`);
|
|
11988
|
-
if (snapshot.warnings.length > 0) {
|
|
11989
|
-
for (const warning of snapshot.warnings) {
|
|
11990
|
-
console.log(`warning: ${warning}`);
|
|
11991
|
-
}
|
|
11992
|
-
}
|
|
11993
|
-
} catch (e) {
|
|
11994
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
11995
|
-
console.error(`[AgentInspect] index build failed: ${msg}`);
|
|
11996
|
-
process.exitCode = 1;
|
|
12230
|
+
function readArtifact(value, index) {
|
|
12231
|
+
if (!isObject(value)) throw new Error(`manifest.artifacts[${index}] must be an object.`);
|
|
12232
|
+
const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
|
|
12233
|
+
const format = readString(value.format, `manifest.artifacts[${index}].format`);
|
|
12234
|
+
const redactionProfile = readString(
|
|
12235
|
+
value.redactionProfile,
|
|
12236
|
+
`manifest.artifacts[${index}].redactionProfile`
|
|
12237
|
+
);
|
|
12238
|
+
if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
|
|
12239
|
+
if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
|
|
12240
|
+
if (!REDACTION_PROFILES2.has(redactionProfile)) {
|
|
12241
|
+
throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
|
|
11997
12242
|
}
|
|
11998
|
-
}
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12243
|
+
const artifactPath = readString(value.path, `manifest.artifacts[${index}].path`);
|
|
12244
|
+
const pathCheck = validateReporterArtifactPath({
|
|
12245
|
+
outputDir: process.cwd(),
|
|
12246
|
+
relativePath: artifactPath
|
|
12247
|
+
});
|
|
12248
|
+
if (!pathCheck.ok || pathCheck.relativePath === void 0) {
|
|
12249
|
+
throw new Error(`Unsafe reporter artifact path: ${artifactPath}.`);
|
|
12250
|
+
}
|
|
12251
|
+
return {
|
|
12252
|
+
kind,
|
|
12253
|
+
path: pathCheck.relativePath,
|
|
12254
|
+
format,
|
|
12255
|
+
redactionProfile
|
|
12256
|
+
};
|
|
12257
|
+
}
|
|
12258
|
+
function readArtifacts(value, label) {
|
|
12259
|
+
if (value === void 0) return [];
|
|
12260
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
12261
|
+
return value.map((item, index) => readArtifact(item, index));
|
|
12262
|
+
}
|
|
12263
|
+
function readDiagnosticsCount(value, label) {
|
|
12264
|
+
if (value === void 0) return 0;
|
|
12265
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
12266
|
+
return value.length;
|
|
12267
|
+
}
|
|
12268
|
+
function readResult(value, index) {
|
|
12269
|
+
if (!isObject(value)) throw new Error(`manifest.results[${index}] must be an object.`);
|
|
12270
|
+
const file = readOptionalString(value.file);
|
|
12271
|
+
const tracePath = readOptionalString(value.tracePath);
|
|
12272
|
+
return {
|
|
12273
|
+
testId: readString(value.testId, `manifest.results[${index}].testId`),
|
|
12274
|
+
name: readString(value.name, `manifest.results[${index}].name`),
|
|
12275
|
+
...file === void 0 ? {} : { file },
|
|
12276
|
+
status: readStatus(value.status),
|
|
12277
|
+
...tracePath === void 0 ? {} : { tracePath },
|
|
12278
|
+
artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
|
|
12279
|
+
diagnostics: readDiagnosticsCount(
|
|
12280
|
+
value.diagnostics,
|
|
12281
|
+
`manifest.results[${index}].diagnostics`
|
|
12282
|
+
)
|
|
12283
|
+
};
|
|
12284
|
+
}
|
|
12285
|
+
function readManifestDocument(value) {
|
|
12286
|
+
if (!isObject(value)) throw new Error("Reporter manifest file must contain a JSON object.");
|
|
12287
|
+
const candidate = isObject(value.manifest) ? value.manifest : value;
|
|
12288
|
+
if (!isObject(candidate)) throw new Error("Reporter manifest must be a JSON object.");
|
|
12289
|
+
const schemaVersion = readString(candidate.schemaVersion, "manifest.schemaVersion");
|
|
12290
|
+
if (schemaVersion !== "0.1") {
|
|
12291
|
+
throw new Error(`Unsupported reporter manifest schemaVersion: ${schemaVersion}.`);
|
|
12292
|
+
}
|
|
12293
|
+
if (!Array.isArray(candidate.results)) {
|
|
12294
|
+
throw new Error("manifest.results must be an array.");
|
|
12295
|
+
}
|
|
12296
|
+
const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
|
|
12297
|
+
const results = candidate.results.map((item, index) => readResult(item, index));
|
|
12298
|
+
const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
|
|
12299
|
+
const manifest = {
|
|
12300
|
+
framework: readFramework(candidate.framework),
|
|
12301
|
+
generatedAt: readString(candidate.generatedAt, "manifest.generatedAt"),
|
|
12302
|
+
results,
|
|
12303
|
+
artifacts
|
|
12304
|
+
};
|
|
12305
|
+
return {
|
|
12306
|
+
packageName: readOptionalString(value.package),
|
|
12307
|
+
manifest,
|
|
12308
|
+
diagnostics
|
|
12309
|
+
};
|
|
12310
|
+
}
|
|
12311
|
+
function cwdRelative(filePath) {
|
|
12312
|
+
const relative = path13.relative(process.cwd(), path13.resolve(filePath)).replace(/\\/g, "/");
|
|
12313
|
+
if (relative === "" || relative.startsWith("../") || path13.isAbsolute(relative)) {
|
|
12314
|
+
return path13.basename(filePath);
|
|
12315
|
+
}
|
|
12316
|
+
return relative;
|
|
12317
|
+
}
|
|
12318
|
+
async function readReporterManifest(filePath) {
|
|
12319
|
+
const absolute = path13.resolve(filePath);
|
|
12320
|
+
const raw = await readFile(absolute, "utf-8");
|
|
12321
|
+
const document = readManifestDocument(JSON.parse(raw));
|
|
12322
|
+
const manifest = document.manifest;
|
|
12323
|
+
const results = manifest.results.map((result) => ({
|
|
12324
|
+
testId: safeText(result.testId),
|
|
12325
|
+
name: safeText(result.name),
|
|
12326
|
+
...result.file === void 0 ? {} : { file: safeText(path13.basename(result.file)) },
|
|
12327
|
+
status: result.status,
|
|
12328
|
+
...result.tracePath === void 0 ? {} : { tracePath: safeText(path13.basename(result.tracePath)) },
|
|
12329
|
+
artifacts: result.artifacts,
|
|
12330
|
+
diagnostics: result.diagnostics
|
|
12331
|
+
}));
|
|
12332
|
+
return {
|
|
12333
|
+
...document.packageName === void 0 ? {} : { packageName: document.packageName },
|
|
12334
|
+
manifestFile: cwdRelative(absolute),
|
|
12335
|
+
framework: manifest.framework,
|
|
12336
|
+
generatedAt: manifest.generatedAt,
|
|
12337
|
+
results,
|
|
12338
|
+
artifacts: manifest.artifacts,
|
|
12339
|
+
diagnostics: document.diagnostics
|
|
12340
|
+
};
|
|
12341
|
+
}
|
|
12342
|
+
function summarize3(manifests) {
|
|
12343
|
+
const summary = {
|
|
12344
|
+
manifests: manifests.length,
|
|
12345
|
+
tests: 0,
|
|
12346
|
+
failed: 0,
|
|
12347
|
+
passed: 0,
|
|
12348
|
+
skipped: 0,
|
|
12349
|
+
todo: 0,
|
|
12350
|
+
artifacts: 0,
|
|
12351
|
+
diagnostics: 0
|
|
12352
|
+
};
|
|
12353
|
+
for (const manifest of manifests) {
|
|
12354
|
+
summary.artifacts += manifest.artifacts.length;
|
|
12355
|
+
summary.diagnostics += manifest.diagnostics;
|
|
12356
|
+
for (const result of manifest.results) {
|
|
12357
|
+
summary.tests += 1;
|
|
12358
|
+
if (result.status === "failed") summary.failed += 1;
|
|
12359
|
+
else if (result.status === "passed") summary.passed += 1;
|
|
12360
|
+
else if (result.status === "skipped") summary.skipped += 1;
|
|
12361
|
+
else summary.todo += 1;
|
|
12362
|
+
summary.artifacts += result.artifacts.length;
|
|
12363
|
+
summary.diagnostics += result.diagnostics;
|
|
12013
12364
|
}
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12365
|
+
}
|
|
12366
|
+
return {
|
|
12367
|
+
status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
|
|
12368
|
+
manifests,
|
|
12369
|
+
summary,
|
|
12370
|
+
note: NOTE2
|
|
12371
|
+
};
|
|
12372
|
+
}
|
|
12373
|
+
function markdownCell(value) {
|
|
12374
|
+
return safeText(String(value ?? "unknown")).replaceAll("|", "\\|").replace(/\r?\n/g, " ");
|
|
12375
|
+
}
|
|
12376
|
+
function renderMarkdown2(result) {
|
|
12377
|
+
const lines = [
|
|
12378
|
+
"# AgentInspect CI Summary",
|
|
12379
|
+
"",
|
|
12380
|
+
NOTE2,
|
|
12381
|
+
"",
|
|
12382
|
+
"| Field | Value |",
|
|
12383
|
+
"| --- | --- |",
|
|
12384
|
+
`| Status | ${result.status} |`,
|
|
12385
|
+
`| Manifests | ${result.summary.manifests} |`,
|
|
12386
|
+
`| Tests | ${result.summary.tests} |`,
|
|
12387
|
+
`| Failed | ${result.summary.failed} |`,
|
|
12388
|
+
`| Passed | ${result.summary.passed} |`,
|
|
12389
|
+
`| Skipped | ${result.summary.skipped} |`,
|
|
12390
|
+
`| Todo | ${result.summary.todo} |`,
|
|
12391
|
+
`| Artifacts | ${result.summary.artifacts} |`,
|
|
12392
|
+
`| Diagnostics | ${result.summary.diagnostics} |`,
|
|
12393
|
+
"",
|
|
12394
|
+
"## Tests",
|
|
12395
|
+
"",
|
|
12396
|
+
"| Framework | Status | Test | File | Trace | Artifacts |",
|
|
12397
|
+
"| --- | --- | --- | --- | --- | --- |"
|
|
12398
|
+
];
|
|
12399
|
+
const rows = result.manifests.flatMap(
|
|
12400
|
+
(manifest) => manifest.results.map((test) => ({
|
|
12401
|
+
framework: manifest.framework,
|
|
12402
|
+
test
|
|
12403
|
+
}))
|
|
12404
|
+
);
|
|
12405
|
+
if (rows.length === 0) {
|
|
12406
|
+
lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
|
|
12407
|
+
} else {
|
|
12408
|
+
for (const row of rows) {
|
|
12409
|
+
lines.push(
|
|
12410
|
+
`| ${markdownCell(row.framework)} | ${markdownCell(row.test.status)} | ${markdownCell(row.test.name)} | ${markdownCell(row.test.file)} | ${markdownCell(row.test.tracePath)} | ${row.test.artifacts.length} |`
|
|
12411
|
+
);
|
|
12028
12412
|
}
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12413
|
+
}
|
|
12414
|
+
lines.push("", "## Manifests", "", "| Framework | File | Generated | Artifacts |", "| --- | --- | --- | --- |");
|
|
12415
|
+
for (const manifest of result.manifests) {
|
|
12416
|
+
lines.push(
|
|
12417
|
+
`| ${markdownCell(manifest.framework)} | ${markdownCell(manifest.manifestFile)} | ${markdownCell(manifest.generatedAt)} | ${manifest.artifacts.length} |`
|
|
12418
|
+
);
|
|
12419
|
+
}
|
|
12420
|
+
lines.push("", "## Artifacts", "", "| Framework | Kind | Path | Format | Profile |", "| --- | --- | --- | --- | --- |");
|
|
12421
|
+
const artifacts = result.manifests.flatMap(
|
|
12422
|
+
(manifest) => manifest.artifacts.map((artifact) => ({ framework: manifest.framework, artifact }))
|
|
12423
|
+
);
|
|
12424
|
+
if (artifacts.length === 0) {
|
|
12425
|
+
lines.push("| unknown | unknown | No artifacts found | unknown | unknown |");
|
|
12426
|
+
} else {
|
|
12427
|
+
for (const row of artifacts) {
|
|
12428
|
+
lines.push(
|
|
12429
|
+
`| ${markdownCell(row.framework)} | ${markdownCell(row.artifact.kind)} | ${markdownCell(row.artifact.path)} | ${markdownCell(row.artifact.format)} | ${markdownCell(row.artifact.redactionProfile)} |`
|
|
12430
|
+
);
|
|
12037
12431
|
}
|
|
12038
|
-
} catch (e) {
|
|
12039
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
12040
|
-
console.error(`[AgentInspect] index status failed: ${msg}`);
|
|
12041
|
-
process.exitCode = 1;
|
|
12042
12432
|
}
|
|
12433
|
+
lines.push("");
|
|
12434
|
+
return `${lines.join("\n")}
|
|
12435
|
+
`;
|
|
12043
12436
|
}
|
|
12044
|
-
|
|
12437
|
+
function safeText(value) {
|
|
12438
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
12439
|
+
const redacted = compact.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(
|
|
12440
|
+
/\b(?:api[_-]?key|authorization|token|secret|password)\s*[:=]\s*[^,\s]+/gi,
|
|
12441
|
+
"$1=[REDACTED]"
|
|
12442
|
+
);
|
|
12443
|
+
if (redacted.length <= MAX_TEXT) return redacted;
|
|
12444
|
+
return `${redacted.slice(0, MAX_TEXT - 12)}...[truncated]`;
|
|
12445
|
+
}
|
|
12446
|
+
async function ciSummaryCommand(manifestPaths, options = {}) {
|
|
12447
|
+
if (manifestPaths.length === 0) {
|
|
12448
|
+
console.error("At least one reporter manifest path is required.");
|
|
12449
|
+
process.exitCode = 1;
|
|
12450
|
+
return;
|
|
12451
|
+
}
|
|
12452
|
+
let result;
|
|
12045
12453
|
try {
|
|
12046
|
-
const
|
|
12047
|
-
const
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12454
|
+
const manifests = [];
|
|
12455
|
+
for (const manifestPath of manifestPaths) {
|
|
12456
|
+
manifests.push(await readReporterManifest(manifestPath));
|
|
12457
|
+
}
|
|
12458
|
+
manifests.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
|
|
12459
|
+
result = summarize3(manifests);
|
|
12460
|
+
} catch (error) {
|
|
12461
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12462
|
+
console.error(`[AgentInspect] ci-summary failed: ${message}`);
|
|
12463
|
+
process.exitCode = 1;
|
|
12464
|
+
return;
|
|
12465
|
+
}
|
|
12466
|
+
const markdown = renderMarkdown2(result);
|
|
12467
|
+
const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path13.resolve(options.output.trim()) : void 0;
|
|
12468
|
+
if (outputPath !== void 0) {
|
|
12469
|
+
await mkdir(path13.dirname(outputPath), { recursive: true });
|
|
12470
|
+
await writeFile(outputPath, markdown, "utf-8");
|
|
12471
|
+
}
|
|
12472
|
+
const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
|
|
12473
|
+
if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
|
|
12474
|
+
const summaryPath = path13.resolve(summaryTarget);
|
|
12475
|
+
await mkdir(path13.dirname(summaryPath), { recursive: true });
|
|
12476
|
+
await appendFile(summaryPath, `
|
|
12477
|
+
${markdown}`, "utf-8");
|
|
12478
|
+
}
|
|
12479
|
+
if (options.json === true) {
|
|
12480
|
+
console.log(writeJson5(result).trimEnd());
|
|
12481
|
+
} else if (outputPath !== void 0) {
|
|
12482
|
+
console.log(`Wrote AgentInspect CI summary to ${outputPath}`);
|
|
12483
|
+
console.log(`Status: ${result.status}`);
|
|
12484
|
+
} else {
|
|
12485
|
+
console.log(markdown.trimEnd());
|
|
12486
|
+
}
|
|
12487
|
+
}
|
|
12488
|
+
var CONFIG_FILE = "agent-inspect.config.ts";
|
|
12489
|
+
var TRACE_DIR = ".agent-inspect";
|
|
12490
|
+
var GITKEEP = ".agent-inspect/.gitkeep";
|
|
12491
|
+
function normalizeFramework(value) {
|
|
12492
|
+
const raw = (value ?? "custom").trim();
|
|
12493
|
+
if (raw === "ai-sdk" || raw === "openai-agents" || raw === "langchain" || raw === "custom") {
|
|
12494
|
+
return raw;
|
|
12495
|
+
}
|
|
12496
|
+
throw new Error(
|
|
12497
|
+
"Unsupported --framework value. Use ai-sdk, openai-agents, langchain, or custom."
|
|
12498
|
+
);
|
|
12499
|
+
}
|
|
12500
|
+
function configTemplate(framework) {
|
|
12501
|
+
const base = `/**
|
|
12502
|
+
* AgentInspect local config (metadata-only capture by default).
|
|
12503
|
+
* See https://github.com/rajudandigam/agent-inspect/blob/main/docs/SAFE-TRACE-SHARING.md
|
|
12504
|
+
*/
|
|
12505
|
+
export const agentInspectConfig = {
|
|
12506
|
+
traceDir: ".agent-inspect",
|
|
12507
|
+
enabled: process.env.AGENT_INSPECT !== "0",
|
|
12508
|
+
redactionProfile: "local" as const,
|
|
12509
|
+
};
|
|
12510
|
+
`;
|
|
12511
|
+
if (framework === "custom") return base;
|
|
12512
|
+
return `${base}
|
|
12513
|
+
export const framework = "${framework}" as const;
|
|
12514
|
+
`;
|
|
12515
|
+
}
|
|
12516
|
+
function demoTemplate(framework) {
|
|
12517
|
+
switch (framework) {
|
|
12518
|
+
case "ai-sdk":
|
|
12519
|
+
return `/**
|
|
12520
|
+
* AI SDK starter \u2014 metadata-only telemetry (no real model calls in this demo).
|
|
12521
|
+
* Install: npm install agent-inspect @agent-inspect/ai-sdk ai
|
|
12522
|
+
*/
|
|
12523
|
+
import { inspectRun, step } from "agent-inspect";
|
|
12524
|
+
|
|
12525
|
+
async function main() {
|
|
12526
|
+
await inspectRun("ai-sdk-demo", async () => {
|
|
12527
|
+
await step.tool("mock-generate", async () => ({ text: "ok" }));
|
|
12528
|
+
}, { traceDir: ".agent-inspect", silent: true });
|
|
12529
|
+
console.log("Trace written to .agent-inspect/");
|
|
12530
|
+
}
|
|
12531
|
+
|
|
12532
|
+
main().catch((error) => {
|
|
12533
|
+
console.error(error);
|
|
12534
|
+
process.exitCode = 1;
|
|
12535
|
+
});
|
|
12536
|
+
`;
|
|
12537
|
+
case "openai-agents":
|
|
12538
|
+
return `/**
|
|
12539
|
+
* OpenAI Agents starter \u2014 use @agent-inspect/openai-agents for local-only processors.
|
|
12540
|
+
* This demo uses manual steps (no API keys).
|
|
12541
|
+
*/
|
|
12542
|
+
import { inspectRun, step } from "agent-inspect";
|
|
12543
|
+
|
|
12544
|
+
async function main() {
|
|
12545
|
+
await inspectRun("openai-agents-demo", async () => {
|
|
12546
|
+
await step.tool("mock-agent-run", async () => "ok");
|
|
12547
|
+
}, { traceDir: ".agent-inspect", silent: true });
|
|
12548
|
+
console.log("Trace written to .agent-inspect/");
|
|
12549
|
+
}
|
|
12550
|
+
|
|
12551
|
+
main().catch((error) => {
|
|
12552
|
+
console.error(error);
|
|
12553
|
+
process.exitCode = 1;
|
|
12554
|
+
});
|
|
12555
|
+
`;
|
|
12556
|
+
case "langchain":
|
|
12557
|
+
return `/**
|
|
12558
|
+
* LangChain starter \u2014 wire @agent-inspect/langchain callbacks in your app.
|
|
12559
|
+
* This demo uses manual steps (no API keys).
|
|
12560
|
+
*/
|
|
12561
|
+
import { inspectRun, step } from "agent-inspect";
|
|
12562
|
+
|
|
12563
|
+
async function main() {
|
|
12564
|
+
await inspectRun("langchain-demo", async () => {
|
|
12565
|
+
await step.tool("mock-chain", async () => "ok");
|
|
12566
|
+
}, { traceDir: ".agent-inspect", silent: true });
|
|
12567
|
+
console.log("Trace written to .agent-inspect/");
|
|
12568
|
+
}
|
|
12569
|
+
|
|
12570
|
+
main().catch((error) => {
|
|
12571
|
+
console.error(error);
|
|
12572
|
+
process.exitCode = 1;
|
|
12573
|
+
});
|
|
12574
|
+
`;
|
|
12575
|
+
default:
|
|
12576
|
+
return `import { observe } from "agent-inspect";
|
|
12577
|
+
|
|
12578
|
+
class DemoAgent {
|
|
12579
|
+
async run(input: { question: string }) {
|
|
12580
|
+
return { answer: \`Echo: \${input.question}\` };
|
|
12581
|
+
}
|
|
12582
|
+
}
|
|
12583
|
+
|
|
12584
|
+
const agent = observe(new DemoAgent(), {
|
|
12585
|
+
traceDir: ".agent-inspect",
|
|
12586
|
+
silent: true,
|
|
12587
|
+
});
|
|
12588
|
+
|
|
12589
|
+
await agent.run({ question: "hello" });
|
|
12590
|
+
console.log("Trace written to .agent-inspect/");
|
|
12591
|
+
`;
|
|
12592
|
+
}
|
|
12593
|
+
}
|
|
12594
|
+
function githubWorkflowTemplate() {
|
|
12595
|
+
return `name: AgentInspect artifacts
|
|
12596
|
+
|
|
12597
|
+
on:
|
|
12598
|
+
workflow_dispatch:
|
|
12599
|
+
push:
|
|
12600
|
+
branches: [main]
|
|
12601
|
+
|
|
12602
|
+
jobs:
|
|
12603
|
+
trace-artifacts:
|
|
12604
|
+
runs-on: ubuntu-latest
|
|
12605
|
+
steps:
|
|
12606
|
+
- uses: actions/checkout@v4
|
|
12607
|
+
- uses: actions/setup-node@v4
|
|
12608
|
+
with:
|
|
12609
|
+
node-version: "22"
|
|
12610
|
+
- run: npm ci
|
|
12611
|
+
- run: npm test
|
|
12612
|
+
- name: Upload AgentInspect traces
|
|
12613
|
+
if: always()
|
|
12614
|
+
uses: actions/upload-artifact@v4
|
|
12615
|
+
with:
|
|
12616
|
+
name: agent-inspect-traces
|
|
12617
|
+
path: .agent-inspect/**/*.jsonl
|
|
12618
|
+
if-no-files-found: ignore
|
|
12619
|
+
`;
|
|
12620
|
+
}
|
|
12621
|
+
async function planInit(options = {}) {
|
|
12622
|
+
const framework = normalizeFramework(options.framework);
|
|
12623
|
+
const cwd = path13.resolve(options.cwd ?? process.cwd());
|
|
12624
|
+
const demoPath = framework === "custom" ? path13.join("examples", "agent-inspect-demo.mjs") : path13.join("examples", `agent-inspect-${framework}-demo.mjs`);
|
|
12625
|
+
const candidates = [
|
|
12626
|
+
{ rel: CONFIG_FILE, content: configTemplate(framework) },
|
|
12627
|
+
{ rel: GITKEEP, content: "" },
|
|
12628
|
+
{ rel: demoPath, content: demoTemplate(framework) }
|
|
12629
|
+
];
|
|
12630
|
+
if (options.ci === "github") {
|
|
12631
|
+
candidates.push({
|
|
12632
|
+
rel: ".github/workflows/agent-inspect-artifacts.yml",
|
|
12633
|
+
content: githubWorkflowTemplate()
|
|
12634
|
+
});
|
|
12635
|
+
}
|
|
12636
|
+
const files = [];
|
|
12637
|
+
for (const candidate of candidates) {
|
|
12638
|
+
const abs = path13.join(cwd, candidate.rel);
|
|
12639
|
+
try {
|
|
12640
|
+
await access(abs);
|
|
12641
|
+
files.push({
|
|
12642
|
+
path: candidate.rel,
|
|
12643
|
+
action: "skip",
|
|
12644
|
+
reason: "file already exists"
|
|
12645
|
+
});
|
|
12646
|
+
} catch (error) {
|
|
12647
|
+
if (error.code !== "ENOENT") throw error;
|
|
12648
|
+
files.push({ path: candidate.rel, action: "create" });
|
|
12052
12649
|
}
|
|
12053
|
-
console.log(`Removed index: ${indexPath2}`);
|
|
12054
|
-
} catch (e) {
|
|
12055
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
12056
|
-
console.error(`[AgentInspect] index clean failed: ${msg}`);
|
|
12057
|
-
process.exitCode = 1;
|
|
12058
12650
|
}
|
|
12651
|
+
return { framework, ...options.ci ? { ci: options.ci } : {}, files };
|
|
12059
12652
|
}
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
try {
|
|
12066
|
-
return await import('./src-DUGEOAZ7.mjs');
|
|
12067
|
-
} catch (e) {
|
|
12068
|
-
if (isModuleNotFound2(e)) {
|
|
12069
|
-
console.error(
|
|
12070
|
-
`The optional SQLite index is not installed. Run: npm install ${PACKAGE}`
|
|
12071
|
-
);
|
|
12072
|
-
process.exitCode = 1;
|
|
12073
|
-
return null;
|
|
12653
|
+
async function writePlannedFiles(plan, cwd, options) {
|
|
12654
|
+
const written = [];
|
|
12655
|
+
for (const entry of plan.files) {
|
|
12656
|
+
if (entry.action === "skip") {
|
|
12657
|
+
continue;
|
|
12074
12658
|
}
|
|
12075
|
-
const
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
}
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
12085
|
-
throw new Error(`${flag} must be a positive integer.`);
|
|
12659
|
+
const abs = path13.join(cwd, entry.path);
|
|
12660
|
+
if (options.dryRun) {
|
|
12661
|
+
written.push(entry.path);
|
|
12662
|
+
continue;
|
|
12663
|
+
}
|
|
12664
|
+
await mkdir(path13.dirname(abs), { recursive: true });
|
|
12665
|
+
const content = entry.path === CONFIG_FILE ? configTemplate(plan.framework) : entry.path === GITKEEP ? "" : entry.path.endsWith(".yml") ? githubWorkflowTemplate() : demoTemplate(plan.framework);
|
|
12666
|
+
await writeFile(abs, content, "utf-8");
|
|
12667
|
+
written.push(entry.path);
|
|
12086
12668
|
}
|
|
12087
|
-
return
|
|
12669
|
+
return written;
|
|
12088
12670
|
}
|
|
12089
|
-
async function
|
|
12090
|
-
|
|
12671
|
+
async function initCommand(options = {}) {
|
|
12672
|
+
const cwd = path13.resolve(options.cwd ?? process.cwd());
|
|
12091
12673
|
try {
|
|
12092
|
-
const
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12674
|
+
const plan = await planInit({ ...options, cwd });
|
|
12675
|
+
const toWrite = plan.files.filter((file) => file.action === "create").map((f) => f.path);
|
|
12676
|
+
const skipped = plan.files.filter((file) => file.action === "skip");
|
|
12677
|
+
if (options.json) {
|
|
12678
|
+
const payload = {
|
|
12679
|
+
ok: true,
|
|
12680
|
+
version,
|
|
12681
|
+
framework: plan.framework,
|
|
12682
|
+
ci: plan.ci ?? null,
|
|
12683
|
+
dryRun: options.dryRun === true,
|
|
12684
|
+
planned: plan.files,
|
|
12685
|
+
wouldWrite: options.dryRun ? toWrite : void 0
|
|
12686
|
+
};
|
|
12687
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
12688
|
+
if (options.dryRun) return;
|
|
12689
|
+
}
|
|
12690
|
+
const written = await writePlannedFiles(plan, cwd, options);
|
|
12691
|
+
if (!options.json) {
|
|
12692
|
+
console.log("AgentInspect init");
|
|
12693
|
+
console.log(`Framework: ${plan.framework}`);
|
|
12694
|
+
console.log(`Trace directory: ${TRACE_DIR}/`);
|
|
12695
|
+
if (options.dryRun) {
|
|
12696
|
+
console.log("Dry run \u2014 would create:");
|
|
12697
|
+
for (const file of toWrite) console.log(`- ${file}`);
|
|
12698
|
+
for (const file of skipped) {
|
|
12699
|
+
console.log(`- ${file.path} (skip: ${file.reason ?? "exists"})`);
|
|
12700
|
+
}
|
|
12701
|
+
return;
|
|
12702
|
+
}
|
|
12703
|
+
console.log("Created:");
|
|
12704
|
+
for (const file of written) console.log(`- ${file}`);
|
|
12705
|
+
for (const file of skipped) {
|
|
12706
|
+
console.log(`- ${file.path} (skipped: ${file.reason ?? "exists"})`);
|
|
12099
12707
|
}
|
|
12708
|
+
console.log("\nNext: run your demo, then `npx agent-inspect list --dir .agent-inspect`");
|
|
12709
|
+
console.log("No dependencies were installed. Add packages manually when ready.");
|
|
12100
12710
|
}
|
|
12101
|
-
} catch {
|
|
12102
|
-
|
|
12103
|
-
return newest;
|
|
12104
|
-
}
|
|
12105
|
-
async function indexSqliteBuildCommand(options = {}) {
|
|
12106
|
-
const mod = await loadIndexSqlite();
|
|
12107
|
-
if (!mod) return;
|
|
12108
|
-
const result = await mod.buildIndex({
|
|
12109
|
-
traceDir: options.dir,
|
|
12110
|
-
maxRuns: parsePositiveInt(options.maxRuns, "--max-runs")
|
|
12111
|
-
});
|
|
12112
|
-
if (options.json) {
|
|
12113
|
-
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
12114
|
-
return;
|
|
12115
|
-
}
|
|
12116
|
-
console.log(`Built SQLite index: ${result.dbPath}`);
|
|
12117
|
-
console.log(`Runs: ${result.runs} Steps: ${result.steps} Errors: ${result.errors}`);
|
|
12118
|
-
for (const warning of result.warnings) console.log(`warning: ${warning}`);
|
|
12119
|
-
}
|
|
12120
|
-
async function indexSqliteStatusCommand(options = {}) {
|
|
12121
|
-
const mod = await loadIndexSqlite();
|
|
12122
|
-
if (!mod) return;
|
|
12123
|
-
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
12124
|
-
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
12125
|
-
const status = mod.indexStatus(dbPath);
|
|
12126
|
-
const stale = mod.isIndexStale(dbPath, await newestTraceMtimeMs(traceDir));
|
|
12127
|
-
if (options.json) {
|
|
12128
|
-
console.log(JSON.stringify({ ok: true, traceDir, stale, ...status }, null, 2));
|
|
12129
|
-
return;
|
|
12130
|
-
}
|
|
12131
|
-
if (!status.exists) {
|
|
12132
|
-
console.log(`No SQLite index at ${dbPath}`);
|
|
12133
|
-
console.log("Run: agent-inspect index sqlite build");
|
|
12134
|
-
return;
|
|
12135
|
-
}
|
|
12136
|
-
console.log(`Index: ${status.dbPath}`);
|
|
12137
|
-
console.log(`Healthy: ${status.healthy ? "yes" : "no"}`);
|
|
12138
|
-
console.log(`Built: ${status.builtAt ?? "unknown"}`);
|
|
12139
|
-
console.log(`Runs: ${status.runs} Steps: ${status.steps}`);
|
|
12140
|
-
console.log(`Stale: ${stale ? "yes" : "no"}`);
|
|
12141
|
-
}
|
|
12142
|
-
async function indexSqliteQueryCommand(options = {}) {
|
|
12143
|
-
const mod = await loadIndexSqlite();
|
|
12144
|
-
if (!mod) return;
|
|
12145
|
-
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
12146
|
-
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
12147
|
-
const status = mod.indexStatus(dbPath);
|
|
12148
|
-
if (!status.exists || !status.healthy) {
|
|
12711
|
+
} catch (error) {
|
|
12712
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
12149
12713
|
if (options.json) {
|
|
12150
|
-
console.log(JSON.stringify({ ok: false,
|
|
12714
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
12151
12715
|
} else {
|
|
12152
|
-
console.
|
|
12716
|
+
console.error(`[AgentInspect] init failed: ${msg}`);
|
|
12153
12717
|
}
|
|
12154
12718
|
process.exitCode = 1;
|
|
12155
|
-
return;
|
|
12156
|
-
}
|
|
12157
|
-
const rows = mod.queryRuns(dbPath, {
|
|
12158
|
-
status: options.status,
|
|
12159
|
-
sessionId: options.session,
|
|
12160
|
-
name: options.name,
|
|
12161
|
-
kind: options.kind,
|
|
12162
|
-
tool: options.tool,
|
|
12163
|
-
limit: parsePositiveInt(options.limit, "--limit")
|
|
12164
|
-
});
|
|
12165
|
-
if (options.json) {
|
|
12166
|
-
console.log(JSON.stringify({ ok: true, count: rows.length, runs: rows }, null, 2));
|
|
12167
|
-
return;
|
|
12168
|
-
}
|
|
12169
|
-
if (rows.length === 0) {
|
|
12170
|
-
console.log("No matching runs.");
|
|
12171
|
-
return;
|
|
12172
|
-
}
|
|
12173
|
-
for (const run of rows) {
|
|
12174
|
-
const parts = [
|
|
12175
|
-
run.runId,
|
|
12176
|
-
run.status ?? "unknown",
|
|
12177
|
-
run.name ?? "",
|
|
12178
|
-
run.durationMs != null ? `${run.durationMs}ms` : ""
|
|
12179
|
-
].filter((p) => p !== "");
|
|
12180
|
-
console.log(parts.join(" "));
|
|
12181
|
-
}
|
|
12182
|
-
}
|
|
12183
|
-
async function indexSqliteCleanCommand(options = {}) {
|
|
12184
|
-
const mod = await loadIndexSqlite();
|
|
12185
|
-
if (!mod) return;
|
|
12186
|
-
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
12187
|
-
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
12188
|
-
await mod.cleanIndex(dbPath);
|
|
12189
|
-
if (options.json) {
|
|
12190
|
-
console.log(JSON.stringify({ ok: true, removed: dbPath }, null, 2));
|
|
12191
|
-
return;
|
|
12192
12719
|
}
|
|
12193
|
-
console.log(`Removed SQLite index: ${dbPath}`);
|
|
12194
12720
|
}
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
// packages/core/src/workspace/manifest.ts
|
|
12202
|
-
var DEFAULT_WORKSPACE_LAYOUT = {
|
|
12203
|
-
traceDirs: ["runs"],
|
|
12204
|
-
reportsDir: "reports",
|
|
12205
|
-
artifactsDir: "artifacts",
|
|
12206
|
-
bundlesDir: "bundles",
|
|
12207
|
-
notesDir: "notes"
|
|
12721
|
+
var OPTIONAL_PACKAGES = {
|
|
12722
|
+
custom: [],
|
|
12723
|
+
"ai-sdk": ["@agent-inspect/ai-sdk"],
|
|
12724
|
+
"openai-agents": ["@agent-inspect/openai-agents"],
|
|
12725
|
+
langchain: ["@agent-inspect/langchain"]
|
|
12208
12726
|
};
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
const index = {
|
|
12220
|
-
enabled: options.index?.enabled ?? false,
|
|
12221
|
-
type: options.index?.type ?? "none",
|
|
12222
|
-
...options.index?.path !== void 0 ? { path: options.index.path } : {}
|
|
12223
|
-
};
|
|
12224
|
-
return {
|
|
12225
|
-
schemaVersion: WORKSPACE_SCHEMA_VERSION,
|
|
12226
|
-
project,
|
|
12227
|
-
createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
12228
|
-
traceDirs: options.traceDirs ? [...options.traceDirs] : [...DEFAULT_WORKSPACE_LAYOUT.traceDirs],
|
|
12229
|
-
reportsDir: options.reportsDir ?? DEFAULT_WORKSPACE_LAYOUT.reportsDir,
|
|
12230
|
-
artifactsDir: options.artifactsDir ?? DEFAULT_WORKSPACE_LAYOUT.artifactsDir,
|
|
12231
|
-
bundlesDir: options.bundlesDir ?? DEFAULT_WORKSPACE_LAYOUT.bundlesDir,
|
|
12232
|
-
notesDir: options.notesDir ?? DEFAULT_WORKSPACE_LAYOUT.notesDir,
|
|
12233
|
-
redactionProfile: options.redactionProfile ?? DEFAULT_REDACTION_PROFILE,
|
|
12234
|
-
index
|
|
12235
|
-
};
|
|
12236
|
-
}
|
|
12237
|
-
function isPlainObject(value) {
|
|
12238
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12239
|
-
}
|
|
12240
|
-
function isSafeRelativeWorkspacePath(p) {
|
|
12241
|
-
if (typeof p !== "string") return false;
|
|
12242
|
-
const trimmed = p.trim();
|
|
12243
|
-
if (trimmed === "") return false;
|
|
12244
|
-
if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return false;
|
|
12245
|
-
if (/^[a-zA-Z]:/.test(trimmed)) return false;
|
|
12246
|
-
const segments = trimmed.split(/[/\\]+/);
|
|
12247
|
-
return !segments.some((seg) => seg === "..");
|
|
12248
|
-
}
|
|
12249
|
-
function validateDirField(value, field, errors) {
|
|
12250
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
12251
|
-
errors.push(`${field} must be a non-empty string`);
|
|
12252
|
-
return;
|
|
12253
|
-
}
|
|
12254
|
-
if (!isSafeRelativeWorkspacePath(value)) {
|
|
12255
|
-
errors.push(
|
|
12256
|
-
`${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
|
|
12257
|
-
);
|
|
12727
|
+
function nodeVersionCheck() {
|
|
12728
|
+
const major = Number(process2.versions.node.split(".")[0]);
|
|
12729
|
+
if (Number.isNaN(major) || major < 20) {
|
|
12730
|
+
return {
|
|
12731
|
+
id: "node-version",
|
|
12732
|
+
status: "fail",
|
|
12733
|
+
message: `Node ${process2.versions.node} is below the supported minimum (20).`,
|
|
12734
|
+
remediation: "Upgrade to Node 20 LTS or newer.",
|
|
12735
|
+
evidence: process2.versions.node
|
|
12736
|
+
};
|
|
12258
12737
|
}
|
|
12738
|
+
return {
|
|
12739
|
+
id: "node-version",
|
|
12740
|
+
status: "pass",
|
|
12741
|
+
message: `Node ${process2.versions.node} meets the minimum (>=20).`,
|
|
12742
|
+
evidence: process2.versions.node
|
|
12743
|
+
};
|
|
12259
12744
|
}
|
|
12260
|
-
function
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
return
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
|
|
12270
|
-
}
|
|
12271
|
-
if (value.path !== void 0 && !isSafeRelativeWorkspacePath(value.path)) {
|
|
12272
|
-
errors.push(
|
|
12273
|
-
'index.path must be a relative path inside the workspace (no absolute paths or ".." traversal)'
|
|
12274
|
-
);
|
|
12745
|
+
function envCheck(name, optional = true) {
|
|
12746
|
+
const value = process2.env[name];
|
|
12747
|
+
if (value === void 0 || value.trim() === "") {
|
|
12748
|
+
return {
|
|
12749
|
+
id: `env-${name.toLowerCase()}`,
|
|
12750
|
+
status: optional ? "skipped" : "warn",
|
|
12751
|
+
message: `${name} is not set.`,
|
|
12752
|
+
remediation: optional ? void 0 : `Set ${name}=1 to enable tracing.`
|
|
12753
|
+
};
|
|
12275
12754
|
}
|
|
12276
|
-
if (errors.length > 0) return void 0;
|
|
12277
12755
|
return {
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12756
|
+
id: `env-${name.toLowerCase()}`,
|
|
12757
|
+
status: "pass",
|
|
12758
|
+
message: `${name} is set.`,
|
|
12759
|
+
evidence: value
|
|
12281
12760
|
};
|
|
12282
12761
|
}
|
|
12283
|
-
function
|
|
12284
|
-
const
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
`
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12762
|
+
async function traceDirWritable(traceDir) {
|
|
12763
|
+
const resolved = path13.resolve(traceDir);
|
|
12764
|
+
try {
|
|
12765
|
+
await mkdir(resolved, { recursive: true });
|
|
12766
|
+
await access(resolved, constants.W_OK);
|
|
12767
|
+
return {
|
|
12768
|
+
id: "trace-dir-writable",
|
|
12769
|
+
status: "pass",
|
|
12770
|
+
message: `Trace directory is writable: ${resolved}`,
|
|
12771
|
+
evidence: resolved
|
|
12772
|
+
};
|
|
12773
|
+
} catch (error) {
|
|
12774
|
+
return {
|
|
12775
|
+
id: "trace-dir-writable",
|
|
12776
|
+
status: "fail",
|
|
12777
|
+
message: `Trace directory is not writable: ${resolved}`,
|
|
12778
|
+
remediation: "Create the directory or set AGENT_INSPECT_TRACE_DIR to a writable path.",
|
|
12779
|
+
evidence: error instanceof Error ? error.message : String(error)
|
|
12780
|
+
};
|
|
12298
12781
|
}
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12782
|
+
}
|
|
12783
|
+
function resolvePackage(cwd, name) {
|
|
12784
|
+
const require2 = createRequire(path13.join(cwd, "package.json"));
|
|
12785
|
+
try {
|
|
12786
|
+
const pkgPath = require2.resolve(`${name}/package.json`);
|
|
12787
|
+
const pkg = require2(pkgPath);
|
|
12788
|
+
return { ok: true, version: pkg.version };
|
|
12789
|
+
} catch {
|
|
12790
|
+
return { ok: false };
|
|
12303
12791
|
}
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12314
|
-
|
|
12792
|
+
}
|
|
12793
|
+
function importSmoke(cwd) {
|
|
12794
|
+
const results = [];
|
|
12795
|
+
const require2 = createRequire(path13.join(cwd, "package.json"));
|
|
12796
|
+
try {
|
|
12797
|
+
require2.resolve("agent-inspect");
|
|
12798
|
+
results.push({
|
|
12799
|
+
id: "import-agent-inspect",
|
|
12800
|
+
status: "pass",
|
|
12801
|
+
message: "agent-inspect resolves from the current project."
|
|
12802
|
+
});
|
|
12803
|
+
} catch {
|
|
12804
|
+
results.push({
|
|
12805
|
+
id: "import-agent-inspect",
|
|
12806
|
+
status: "warn",
|
|
12807
|
+
message: "agent-inspect is not installed in the current project.",
|
|
12808
|
+
remediation: "Run npm install agent-inspect (or pnpm add agent-inspect)."
|
|
12315
12809
|
});
|
|
12316
12810
|
}
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12811
|
+
try {
|
|
12812
|
+
require2.resolve("agent-inspect/package.json");
|
|
12813
|
+
results.push({
|
|
12814
|
+
id: "import-agent-inspect-cjs",
|
|
12815
|
+
status: "pass",
|
|
12816
|
+
message: "CJS resolution for agent-inspect succeeded."
|
|
12817
|
+
});
|
|
12818
|
+
} catch {
|
|
12819
|
+
results.push({
|
|
12820
|
+
id: "import-agent-inspect-cjs",
|
|
12821
|
+
status: "skipped",
|
|
12822
|
+
message: "CJS resolution check skipped (package not installed locally)."
|
|
12823
|
+
});
|
|
12323
12824
|
}
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12825
|
+
return results;
|
|
12826
|
+
}
|
|
12827
|
+
function optionalPackageChecks(cwd, framework) {
|
|
12828
|
+
const packages = framework !== void 0 ? OPTIONAL_PACKAGES[framework] : [
|
|
12829
|
+
"@agent-inspect/ai-sdk",
|
|
12830
|
+
"@agent-inspect/openai-agents",
|
|
12831
|
+
"@agent-inspect/langchain",
|
|
12832
|
+
"@agent-inspect/redact",
|
|
12833
|
+
"@agent-inspect/eval"
|
|
12834
|
+
];
|
|
12835
|
+
return packages.map((name) => {
|
|
12836
|
+
const resolved = resolvePackage(cwd, name);
|
|
12837
|
+
if (!resolved.ok) {
|
|
12838
|
+
return {
|
|
12839
|
+
id: `optional-package-${name}`,
|
|
12840
|
+
status: framework !== void 0 ? "warn" : "skipped",
|
|
12841
|
+
message: `${name} is not installed.`,
|
|
12842
|
+
remediation: framework !== void 0 ? `npm install ${name}` : void 0
|
|
12843
|
+
};
|
|
12844
|
+
}
|
|
12845
|
+
return {
|
|
12846
|
+
id: `optional-package-${name}`,
|
|
12847
|
+
status: "pass",
|
|
12848
|
+
message: `${name} is available.`,
|
|
12849
|
+
evidence: resolved.version
|
|
12850
|
+
};
|
|
12851
|
+
});
|
|
12852
|
+
}
|
|
12853
|
+
function versionMismatchCheck(cwd) {
|
|
12854
|
+
const root = resolvePackage(cwd, "agent-inspect");
|
|
12855
|
+
if (!root.ok || root.version === void 0) {
|
|
12856
|
+
return {
|
|
12857
|
+
id: "version-alignment",
|
|
12858
|
+
status: "skipped",
|
|
12859
|
+
message: "Skipped version alignment \u2014 agent-inspect not installed locally."
|
|
12860
|
+
};
|
|
12329
12861
|
}
|
|
12330
|
-
if (
|
|
12331
|
-
return {
|
|
12862
|
+
if (root.version !== version) {
|
|
12863
|
+
return {
|
|
12864
|
+
id: "version-alignment",
|
|
12865
|
+
status: "warn",
|
|
12866
|
+
message: `CLI version ${version} differs from local agent-inspect@${root.version}.`,
|
|
12867
|
+
remediation: "Align versions with npm install agent-inspect@latest",
|
|
12868
|
+
evidence: `cli=${version};local=${root.version}`
|
|
12869
|
+
};
|
|
12332
12870
|
}
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
traceDirs: input3.traceDirs.map((d) => d.trim()),
|
|
12338
|
-
reportsDir: input3.reportsDir.trim(),
|
|
12339
|
-
artifactsDir: input3.artifactsDir.trim(),
|
|
12340
|
-
bundlesDir: input3.bundlesDir.trim(),
|
|
12341
|
-
notesDir: input3.notesDir.trim(),
|
|
12342
|
-
redactionProfile: input3.redactionProfile,
|
|
12343
|
-
index
|
|
12871
|
+
return {
|
|
12872
|
+
id: "version-alignment",
|
|
12873
|
+
status: "pass",
|
|
12874
|
+
message: `CLI and local agent-inspect are aligned at ${version}.`
|
|
12344
12875
|
};
|
|
12345
|
-
return { ok: true, manifest, errors, warnings };
|
|
12346
12876
|
}
|
|
12347
|
-
function
|
|
12348
|
-
const
|
|
12349
|
-
|
|
12350
|
-
|
|
12877
|
+
async function runDoctorChecks(options = {}) {
|
|
12878
|
+
const cwd = path13.resolve(options.cwd ?? process2.cwd());
|
|
12879
|
+
const traceDir = options.traceDir?.trim() || process2.env.AGENT_INSPECT_TRACE_DIR?.trim() || ".agent-inspect";
|
|
12880
|
+
const checks2 = [
|
|
12881
|
+
nodeVersionCheck(),
|
|
12882
|
+
{
|
|
12883
|
+
id: "cli-version",
|
|
12884
|
+
status: "pass",
|
|
12885
|
+
message: `agent-inspect CLI ${version}`,
|
|
12886
|
+
evidence: version
|
|
12887
|
+
},
|
|
12888
|
+
await traceDirWritable(traceDir),
|
|
12889
|
+
envCheck("AGENT_INSPECT"),
|
|
12890
|
+
envCheck("AGENT_INSPECT_TRACE_DIR"),
|
|
12891
|
+
versionMismatchCheck(cwd)
|
|
12892
|
+
];
|
|
12893
|
+
if (options.checkImports !== false) {
|
|
12894
|
+
checks2.push(...importSmoke(cwd));
|
|
12351
12895
|
}
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12896
|
+
checks2.push(...optionalPackageChecks(cwd, options.framework));
|
|
12897
|
+
return checks2.sort((a, b) => a.id.localeCompare(b.id));
|
|
12898
|
+
}
|
|
12899
|
+
async function doctorCommand(options = {}) {
|
|
12900
|
+
const checks2 = await runDoctorChecks(options);
|
|
12901
|
+
const failed = checks2.filter((check) => check.status === "fail").length;
|
|
12902
|
+
const warned = checks2.filter((check) => check.status === "warn").length;
|
|
12903
|
+
if (options.json) {
|
|
12904
|
+
console.log(
|
|
12905
|
+
JSON.stringify(
|
|
12906
|
+
{
|
|
12907
|
+
ok: failed === 0,
|
|
12908
|
+
version,
|
|
12909
|
+
summary: { pass: checks2.filter((c) => c.status === "pass").length, warn: warned, fail: failed },
|
|
12910
|
+
checks: checks2
|
|
12911
|
+
},
|
|
12912
|
+
null,
|
|
12913
|
+
2
|
|
12914
|
+
)
|
|
12915
|
+
);
|
|
12916
|
+
if (failed > 0) process2.exitCode = 1;
|
|
12917
|
+
return;
|
|
12360
12918
|
}
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12919
|
+
console.log("AgentInspect doctor");
|
|
12920
|
+
for (const check of checks2) {
|
|
12921
|
+
const tag = check.status.toUpperCase();
|
|
12922
|
+
console.log(`[${tag}] ${check.id}: ${check.message}`);
|
|
12923
|
+
if (check.remediation) console.log(` \u2192 ${check.remediation}`);
|
|
12366
12924
|
}
|
|
12367
|
-
|
|
12368
|
-
}
|
|
12369
|
-
|
|
12370
|
-
return `${JSON.stringify(manifest, null, 2)}
|
|
12371
|
-
`;
|
|
12925
|
+
console.log(`
|
|
12926
|
+
Summary: ${failed} failed, ${warned} warnings`);
|
|
12927
|
+
if (failed > 0) process2.exitCode = 1;
|
|
12372
12928
|
}
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
return
|
|
12378
|
-
projectRoot,
|
|
12379
|
-
workspaceDir,
|
|
12380
|
-
manifestPath: path10.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
|
|
12381
|
-
};
|
|
12929
|
+
|
|
12930
|
+
// packages/adapter-sdk/src/indexer.ts
|
|
12931
|
+
function defineIndexer(indexer) {
|
|
12932
|
+
if (!indexer.id.trim()) throw new Error("indexer id is required");
|
|
12933
|
+
return indexer;
|
|
12382
12934
|
}
|
|
12383
|
-
function
|
|
12384
|
-
const
|
|
12385
|
-
|
|
12386
|
-
const
|
|
12387
|
-
|
|
12388
|
-
|
|
12935
|
+
async function indexIsStale(snapshot, traceDir) {
|
|
12936
|
+
const builtMs = Date.parse(snapshot.builtAt);
|
|
12937
|
+
if (Number.isNaN(builtMs)) return true;
|
|
12938
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
12939
|
+
const files = await td.list();
|
|
12940
|
+
for (const file of files) {
|
|
12941
|
+
const stats = await td.getFileStats(file);
|
|
12942
|
+
if (stats.mtimeMs > builtMs) return true;
|
|
12389
12943
|
}
|
|
12390
|
-
|
|
12391
|
-
`Workspace path "${relative}" resolves outside the workspace directory`
|
|
12392
|
-
);
|
|
12944
|
+
return false;
|
|
12393
12945
|
}
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12946
|
+
function createTraceDirectoryIndexer() {
|
|
12947
|
+
return defineIndexer({
|
|
12948
|
+
id: "trace-directory-metadata",
|
|
12949
|
+
async rebuild(traceDir, options = {}) {
|
|
12950
|
+
const warnings = [];
|
|
12951
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
12952
|
+
const files = await td.list();
|
|
12953
|
+
const maxEntries = options.maxEntries ?? 1e4;
|
|
12954
|
+
if (files.length > maxEntries) {
|
|
12955
|
+
warnings.push(
|
|
12956
|
+
`indexer.truncated: trace directory has ${files.length} files; indexing first ${maxEntries}`
|
|
12957
|
+
);
|
|
12958
|
+
}
|
|
12959
|
+
const slice = files.slice(0, maxEntries);
|
|
12960
|
+
const metas = await loadTraceMetadataList(
|
|
12961
|
+
traceDir,
|
|
12962
|
+
slice,
|
|
12963
|
+
(fileName) => td.getPath(fileName)
|
|
12964
|
+
);
|
|
12965
|
+
const entries = metas.map((meta) => ({
|
|
12966
|
+
runId: meta.runId,
|
|
12967
|
+
path: meta.filePath,
|
|
12968
|
+
name: meta.name,
|
|
12969
|
+
startedAt: meta.startedAt,
|
|
12970
|
+
status: meta.status
|
|
12971
|
+
})).sort((a, b) => a.runId.localeCompare(b.runId));
|
|
12972
|
+
if (entries.length < slice.length) {
|
|
12973
|
+
warnings.push(
|
|
12974
|
+
`indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
|
|
12975
|
+
);
|
|
12976
|
+
}
|
|
12977
|
+
return {
|
|
12978
|
+
traceDir,
|
|
12979
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12980
|
+
entries,
|
|
12981
|
+
warnings
|
|
12982
|
+
};
|
|
12983
|
+
}
|
|
12984
|
+
});
|
|
12401
12985
|
}
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12986
|
+
|
|
12987
|
+
// packages/cli/src/index-cmd.ts
|
|
12988
|
+
var INDEX_FILENAME = ".agent-inspect-index.json";
|
|
12989
|
+
function traceIndexPath(traceDir) {
|
|
12990
|
+
return path13.join(traceDir, INDEX_FILENAME);
|
|
12991
|
+
}
|
|
12992
|
+
function parseMaxEntries(raw) {
|
|
12993
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
12994
|
+
const parsed = Number.parseInt(raw, 10);
|
|
12995
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
12996
|
+
throw new Error("--max-entries must be a positive integer.");
|
|
12408
12997
|
}
|
|
12998
|
+
return parsed;
|
|
12409
12999
|
}
|
|
12410
|
-
async function
|
|
13000
|
+
async function readSnapshot(indexPath2) {
|
|
12411
13001
|
try {
|
|
12412
|
-
const
|
|
12413
|
-
return
|
|
13002
|
+
const raw = await readFile(indexPath2, "utf8");
|
|
13003
|
+
return JSON.parse(raw);
|
|
12414
13004
|
} catch {
|
|
12415
|
-
return
|
|
13005
|
+
return void 0;
|
|
12416
13006
|
}
|
|
12417
13007
|
}
|
|
12418
|
-
async function
|
|
13008
|
+
async function indexBuildCommand(options = {}) {
|
|
12419
13009
|
try {
|
|
12420
|
-
const
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
13010
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
13011
|
+
await mkdir(traceDir, { recursive: true });
|
|
13012
|
+
const indexer = createTraceDirectoryIndexer();
|
|
13013
|
+
const snapshot = await indexer.rebuild(traceDir, {
|
|
13014
|
+
maxEntries: parseMaxEntries(options.maxEntries)
|
|
13015
|
+
});
|
|
13016
|
+
const indexPath2 = traceIndexPath(traceDir);
|
|
13017
|
+
await writeFile(indexPath2, `${JSON.stringify(snapshot, null, 2)}
|
|
13018
|
+
`, "utf8");
|
|
13019
|
+
if (options.json) {
|
|
13020
|
+
console.log(JSON.stringify({ ok: true, indexPath: indexPath2, ...snapshot }, null, 2));
|
|
13021
|
+
return;
|
|
13022
|
+
}
|
|
13023
|
+
console.log(`Built trace index: ${indexPath2}`);
|
|
13024
|
+
console.log(`Entries: ${snapshot.entries.length}`);
|
|
13025
|
+
if (snapshot.warnings.length > 0) {
|
|
13026
|
+
for (const warning of snapshot.warnings) {
|
|
13027
|
+
console.log(`warning: ${warning}`);
|
|
13028
|
+
}
|
|
13029
|
+
}
|
|
13030
|
+
} catch (e) {
|
|
13031
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
13032
|
+
console.error(`[AgentInspect] index build failed: ${msg}`);
|
|
13033
|
+
process.exitCode = 1;
|
|
12424
13034
|
}
|
|
12425
13035
|
}
|
|
12426
|
-
async function
|
|
12427
|
-
let raw;
|
|
13036
|
+
async function indexStatusCommand(options = {}) {
|
|
12428
13037
|
try {
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
13038
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
13039
|
+
const indexPath2 = traceIndexPath(traceDir);
|
|
13040
|
+
const snapshot = await readSnapshot(indexPath2);
|
|
13041
|
+
if (!snapshot) {
|
|
13042
|
+
const payload2 = { ok: true, exists: false, indexPath: indexPath2, traceDir, stale: true };
|
|
13043
|
+
if (options.json) {
|
|
13044
|
+
console.log(JSON.stringify(payload2, null, 2));
|
|
13045
|
+
} else {
|
|
13046
|
+
console.log(`No index at ${indexPath2}`);
|
|
13047
|
+
console.log("Run: agent-inspect index build");
|
|
13048
|
+
}
|
|
13049
|
+
return;
|
|
13050
|
+
}
|
|
13051
|
+
const stale = await indexIsStale(snapshot, traceDir);
|
|
13052
|
+
const payload = {
|
|
13053
|
+
ok: true,
|
|
13054
|
+
exists: true,
|
|
13055
|
+
indexPath: indexPath2,
|
|
13056
|
+
traceDir,
|
|
13057
|
+
stale,
|
|
13058
|
+
builtAt: snapshot.builtAt,
|
|
13059
|
+
entries: snapshot.entries.length,
|
|
13060
|
+
warnings: snapshot.warnings
|
|
13061
|
+
};
|
|
13062
|
+
if (options.json) {
|
|
13063
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
13064
|
+
return;
|
|
13065
|
+
}
|
|
13066
|
+
console.log(`Index: ${indexPath2}`);
|
|
13067
|
+
console.log(`Built: ${snapshot.builtAt}`);
|
|
13068
|
+
console.log(`Entries: ${snapshot.entries.length}`);
|
|
13069
|
+
console.log(`Stale: ${stale ? "yes" : "no"}`);
|
|
13070
|
+
if (snapshot.warnings.length > 0) {
|
|
13071
|
+
for (const warning of snapshot.warnings) {
|
|
13072
|
+
console.log(`warning: ${warning}`);
|
|
13073
|
+
}
|
|
13074
|
+
}
|
|
13075
|
+
} catch (e) {
|
|
13076
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
13077
|
+
console.error(`[AgentInspect] index status failed: ${msg}`);
|
|
13078
|
+
process.exitCode = 1;
|
|
12432
13079
|
}
|
|
12433
|
-
const parsed = parseWorkspaceManifest(raw);
|
|
12434
|
-
return {
|
|
12435
|
-
exists: true,
|
|
12436
|
-
ok: parsed.ok,
|
|
12437
|
-
...parsed.manifest ? { manifest: parsed.manifest } : {},
|
|
12438
|
-
errors: parsed.errors,
|
|
12439
|
-
warnings: parsed.warnings
|
|
12440
|
-
};
|
|
12441
13080
|
}
|
|
12442
|
-
async function
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
const project = options.project?.trim() || path10.basename(location.projectRoot) || "workspace";
|
|
12457
|
-
const traceDirs = detectedExistingTraces ? ["runs", "."] : ["runs"];
|
|
12458
|
-
manifest = createDefaultWorkspaceManifest({
|
|
12459
|
-
project,
|
|
12460
|
-
traceDirs,
|
|
12461
|
-
...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {}
|
|
12462
|
-
});
|
|
12463
|
-
created = true;
|
|
12464
|
-
adopted = detectedExistingTraces || existing.exists && !existing.ok;
|
|
12465
|
-
}
|
|
12466
|
-
const relDirs = uniqueDirs([
|
|
12467
|
-
...manifest.traceDirs.filter((d) => d !== "."),
|
|
12468
|
-
manifest.reportsDir,
|
|
12469
|
-
manifest.artifactsDir,
|
|
12470
|
-
manifest.bundlesDir,
|
|
12471
|
-
manifest.notesDir,
|
|
12472
|
-
INDEX_DIR_NAME
|
|
12473
|
-
]);
|
|
12474
|
-
const createdDirs = [];
|
|
12475
|
-
for (const rel of relDirs) {
|
|
12476
|
-
const abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
12477
|
-
if (await pathExists(abs)) continue;
|
|
12478
|
-
createdDirs.push(rel);
|
|
12479
|
-
if (!dryRun) await mkdir(abs, { recursive: true });
|
|
12480
|
-
}
|
|
12481
|
-
if (!dryRun && created) {
|
|
12482
|
-
await mkdir(location.workspaceDir, { recursive: true });
|
|
12483
|
-
await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
|
|
13081
|
+
async function indexCleanCommand(options = {}) {
|
|
13082
|
+
try {
|
|
13083
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
13084
|
+
const indexPath2 = traceIndexPath(traceDir);
|
|
13085
|
+
await rm(indexPath2, { force: true });
|
|
13086
|
+
if (options.json) {
|
|
13087
|
+
console.log(JSON.stringify({ ok: true, removed: indexPath2 }, null, 2));
|
|
13088
|
+
return;
|
|
13089
|
+
}
|
|
13090
|
+
console.log(`Removed index: ${indexPath2}`);
|
|
13091
|
+
} catch (e) {
|
|
13092
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
13093
|
+
console.error(`[AgentInspect] index clean failed: ${msg}`);
|
|
13094
|
+
process.exitCode = 1;
|
|
12484
13095
|
}
|
|
12485
|
-
return {
|
|
12486
|
-
location,
|
|
12487
|
-
manifest,
|
|
12488
|
-
created,
|
|
12489
|
-
adopted,
|
|
12490
|
-
createdDirs,
|
|
12491
|
-
detectedExistingTraces,
|
|
12492
|
-
dryRun
|
|
12493
|
-
};
|
|
12494
13096
|
}
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
13097
|
+
var PACKAGE = "@agent-inspect/index-sqlite";
|
|
13098
|
+
function isModuleNotFound3(e) {
|
|
13099
|
+
return e !== null && typeof e === "object" && "code" in e && (e.code === "ERR_MODULE_NOT_FOUND" || e.code === "MODULE_NOT_FOUND");
|
|
13100
|
+
}
|
|
13101
|
+
async function loadIndexSqlite() {
|
|
13102
|
+
try {
|
|
13103
|
+
return await import('./src-YFMPWEIS.mjs');
|
|
13104
|
+
} catch (e) {
|
|
13105
|
+
if (isModuleNotFound3(e)) {
|
|
13106
|
+
console.error(
|
|
13107
|
+
`The optional SQLite index is not installed. Run: npm install ${PACKAGE}`
|
|
13108
|
+
);
|
|
13109
|
+
process.exitCode = 1;
|
|
13110
|
+
return null;
|
|
13111
|
+
}
|
|
13112
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
13113
|
+
console.error(`[AgentInspect] failed to load ${PACKAGE}: ${msg}`);
|
|
13114
|
+
process.exitCode = 1;
|
|
13115
|
+
return null;
|
|
12503
13116
|
}
|
|
12504
|
-
return out;
|
|
12505
13117
|
}
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
13118
|
+
function parsePositiveInt(raw, flag) {
|
|
13119
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
13120
|
+
const parsed = Number.parseInt(raw, 10);
|
|
13121
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
13122
|
+
throw new Error(`${flag} must be a positive integer.`);
|
|
12511
13123
|
}
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
return {
|
|
12526
|
-
project: manifest.project,
|
|
12527
|
-
traceFiles,
|
|
12528
|
-
reports,
|
|
12529
|
-
artifacts,
|
|
12530
|
-
bundles,
|
|
12531
|
-
notes,
|
|
12532
|
-
index: {
|
|
12533
|
-
enabled: manifest.index.enabled,
|
|
12534
|
-
type: manifest.index.type,
|
|
12535
|
-
exists: await pathExists(indexPath2)
|
|
13124
|
+
return parsed;
|
|
13125
|
+
}
|
|
13126
|
+
async function newestTraceMtimeMs2(traceDir) {
|
|
13127
|
+
let newest = 0;
|
|
13128
|
+
try {
|
|
13129
|
+
const files = await readdir(traceDir);
|
|
13130
|
+
for (const file of files) {
|
|
13131
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
13132
|
+
try {
|
|
13133
|
+
const s = await stat(path13.join(traceDir, file));
|
|
13134
|
+
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
13135
|
+
} catch {
|
|
13136
|
+
}
|
|
12536
13137
|
}
|
|
12537
|
-
}
|
|
13138
|
+
} catch {
|
|
13139
|
+
}
|
|
13140
|
+
return newest;
|
|
12538
13141
|
}
|
|
12539
|
-
async function
|
|
12540
|
-
const
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
});
|
|
12548
|
-
return
|
|
13142
|
+
async function indexSqliteBuildCommand(options = {}) {
|
|
13143
|
+
const mod = await loadIndexSqlite();
|
|
13144
|
+
if (!mod) return;
|
|
13145
|
+
const result = await mod.buildIndex({
|
|
13146
|
+
traceDir: options.dir,
|
|
13147
|
+
maxRuns: parsePositiveInt(options.maxRuns, "--max-runs")
|
|
13148
|
+
});
|
|
13149
|
+
if (options.json) {
|
|
13150
|
+
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
13151
|
+
return;
|
|
12549
13152
|
}
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
13153
|
+
console.log(`Built SQLite index: ${result.dbPath}`);
|
|
13154
|
+
console.log(`Runs: ${result.runs} Steps: ${result.steps} Errors: ${result.errors}`);
|
|
13155
|
+
for (const warning of result.warnings) console.log(`warning: ${warning}`);
|
|
13156
|
+
}
|
|
13157
|
+
async function indexSqliteStatusCommand(options = {}) {
|
|
13158
|
+
const mod = await loadIndexSqlite();
|
|
13159
|
+
if (!mod) return;
|
|
13160
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
13161
|
+
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
13162
|
+
const status = mod.indexStatus(dbPath);
|
|
13163
|
+
const stale = mod.isIndexStale(dbPath, await newestTraceMtimeMs2(traceDir));
|
|
13164
|
+
if (options.json) {
|
|
13165
|
+
console.log(JSON.stringify({ ok: true, traceDir, stale, ...status }, null, 2));
|
|
13166
|
+
return;
|
|
12557
13167
|
}
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
13168
|
+
if (!status.exists) {
|
|
13169
|
+
console.log(`No SQLite index at ${dbPath}`);
|
|
13170
|
+
console.log("Run: agent-inspect index sqlite build");
|
|
13171
|
+
return;
|
|
12562
13172
|
}
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
message: error instanceof Error ? error.message : String(error)
|
|
12579
|
-
});
|
|
12580
|
-
continue;
|
|
12581
|
-
}
|
|
12582
|
-
if (!await pathExists(abs)) {
|
|
12583
|
-
checks2.push({ id, status: "warn", message: `${rel}/ does not exist yet` });
|
|
12584
|
-
} else if (!await isWritable(abs)) {
|
|
12585
|
-
checks2.push({ id, status: "fail", message: `${rel}/ is not writable` });
|
|
13173
|
+
console.log(`Index: ${status.dbPath}`);
|
|
13174
|
+
console.log(`Healthy: ${status.healthy ? "yes" : "no"}`);
|
|
13175
|
+
console.log(`Built: ${status.builtAt ?? "unknown"}`);
|
|
13176
|
+
console.log(`Runs: ${status.runs} Steps: ${status.steps}`);
|
|
13177
|
+
console.log(`Stale: ${stale ? "yes" : "no"}`);
|
|
13178
|
+
}
|
|
13179
|
+
async function indexSqliteQueryCommand(options = {}) {
|
|
13180
|
+
const mod = await loadIndexSqlite();
|
|
13181
|
+
if (!mod) return;
|
|
13182
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
13183
|
+
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
13184
|
+
const status = mod.indexStatus(dbPath);
|
|
13185
|
+
if (!status.exists || !status.healthy) {
|
|
13186
|
+
if (options.json) {
|
|
13187
|
+
console.log(JSON.stringify({ ok: false, reason: "index-missing", dbPath }, null, 2));
|
|
12586
13188
|
} else {
|
|
12587
|
-
|
|
13189
|
+
console.log("No usable SQLite index. Run: agent-inspect index sqlite build");
|
|
12588
13190
|
}
|
|
13191
|
+
process.exitCode = 1;
|
|
13192
|
+
return;
|
|
12589
13193
|
}
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
|
|
13194
|
+
const rows = mod.queryRuns(dbPath, {
|
|
13195
|
+
status: options.status,
|
|
13196
|
+
sessionId: options.session,
|
|
13197
|
+
name: options.name,
|
|
13198
|
+
kind: options.kind,
|
|
13199
|
+
tool: options.tool,
|
|
13200
|
+
limit: parsePositiveInt(options.limit, "--limit")
|
|
13201
|
+
});
|
|
13202
|
+
if (options.json) {
|
|
13203
|
+
console.log(JSON.stringify({ ok: true, count: rows.length, runs: rows }, null, 2));
|
|
13204
|
+
return;
|
|
12601
13205
|
}
|
|
12602
|
-
if (
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
} catch {
|
|
12615
|
-
checks2.push({ id: "index", status: "warn", message: "cannot stat index" });
|
|
12616
|
-
}
|
|
12617
|
-
}
|
|
13206
|
+
if (rows.length === 0) {
|
|
13207
|
+
console.log("No matching runs.");
|
|
13208
|
+
return;
|
|
13209
|
+
}
|
|
13210
|
+
for (const run of rows) {
|
|
13211
|
+
const parts = [
|
|
13212
|
+
run.runId,
|
|
13213
|
+
run.status ?? "unknown",
|
|
13214
|
+
run.name ?? "",
|
|
13215
|
+
run.durationMs != null ? `${run.durationMs}ms` : ""
|
|
13216
|
+
].filter((p) => p !== "");
|
|
13217
|
+
console.log(parts.join(" "));
|
|
12618
13218
|
}
|
|
12619
|
-
const ok = !checks2.some((c) => c.status === "fail");
|
|
12620
|
-
return { ok, checks: checks2 };
|
|
12621
13219
|
}
|
|
12622
|
-
async function
|
|
12623
|
-
const
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
for (const rel of targets) {
|
|
12632
|
-
const abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
12633
|
-
let entries;
|
|
12634
|
-
try {
|
|
12635
|
-
entries = await readdir(abs);
|
|
12636
|
-
} catch {
|
|
12637
|
-
continue;
|
|
12638
|
-
}
|
|
12639
|
-
for (const entry of entries) {
|
|
12640
|
-
const relPath = `${rel}/${entry}`;
|
|
12641
|
-
removed.push(relPath);
|
|
12642
|
-
if (!dryRun) {
|
|
12643
|
-
await rm(path10.join(abs, entry), { recursive: true, force: true });
|
|
12644
|
-
}
|
|
12645
|
-
}
|
|
13220
|
+
async function indexSqliteCleanCommand(options = {}) {
|
|
13221
|
+
const mod = await loadIndexSqlite();
|
|
13222
|
+
if (!mod) return;
|
|
13223
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
13224
|
+
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
13225
|
+
await mod.cleanIndex(dbPath);
|
|
13226
|
+
if (options.json) {
|
|
13227
|
+
console.log(JSON.stringify({ ok: true, removed: dbPath }, null, 2));
|
|
13228
|
+
return;
|
|
12646
13229
|
}
|
|
12647
|
-
|
|
13230
|
+
console.log(`Removed SQLite index: ${dbPath}`);
|
|
12648
13231
|
}
|
|
12649
13232
|
|
|
12650
13233
|
// packages/cli/src/workspace.ts
|
|
@@ -12996,6 +13579,11 @@ function createCliProgram() {
|
|
|
12996
13579
|
).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--baseline <trace-path-or-run-id>", "optional baseline trace for diff artifacts").option("--baseline-run <run-id>", "select a run from the baseline trace").option("--github-summary <path>", "append a safe summary to this file, e.g. GITHUB_STEP_SUMMARY").option("--json", "print deterministic JSON manifest").action((target, opts) => {
|
|
12997
13580
|
runCommand(() => artifactsCommand(target, opts));
|
|
12998
13581
|
});
|
|
13582
|
+
program.command("bundle").description("Create a share-safe offline trace bundle (local folder)").argument("[run-id]", "run id to bundle (optional with --session or --since)").option("--dir <path>", "trace directory for run/session lookup").option("--session <session-id>", "bundle all runs in a session").option("--since <duration>", "bundle runs with activity since a duration (e.g. 24h)").addOption(
|
|
13583
|
+
new Option("--profile <profile>", "redaction profile for exported copies").choices(["local", "share", "strict"]).default("share")
|
|
13584
|
+
).option("--out <path>", "output directory (folder; .zip suffix is stripped)").option("--allow-unsafe", "write bundle even when verify-safe reports UNSAFE").option("--json", "print deterministic JSON manifest").action((runId, opts) => {
|
|
13585
|
+
runCommand(() => bundleCommand(runId, opts));
|
|
13586
|
+
});
|
|
12999
13587
|
program.command("ci-summary").description("Summarize local reporter artifact manifests for CI").argument("<manifest...>", "reporter artifact manifest JSON files").option("-o, --output <path>", "write Markdown summary to a local file").option("--github-summary <path>", "append Markdown summary to this local file, e.g. GITHUB_STEP_SUMMARY").option("--json", "print deterministic JSON summary").action((manifest, opts) => {
|
|
13000
13588
|
runCommand(() => ciSummaryCommand(manifest, opts));
|
|
13001
13589
|
});
|
|
@@ -13046,12 +13634,32 @@ function createCliProgram() {
|
|
|
13046
13634
|
).option("--json", "print results as JSON").action((opts) => {
|
|
13047
13635
|
runCommand(() => searchCommand(opts));
|
|
13048
13636
|
});
|
|
13049
|
-
program.command("sessions").description(
|
|
13637
|
+
const sessionsCmd = program.command("sessions").description(
|
|
13638
|
+
"Workflow sessions and activity from local trace metadata (read-only, v4.2+)"
|
|
13639
|
+
).option("--dir <path>", "trace directory").option(
|
|
13050
13640
|
"--correlate-group",
|
|
13051
13641
|
"treat shared groupId as a synthetic session when sessionId is absent"
|
|
13052
|
-
).option("--json", "print
|
|
13642
|
+
).option("--json", "print JSON output").option(
|
|
13643
|
+
"--stale-after <duration>",
|
|
13644
|
+
"mark sessions stale after inactivity (e.g. 24h, 7d)"
|
|
13645
|
+
).action((opts) => {
|
|
13053
13646
|
runCommand(() => sessionsCommand(opts));
|
|
13054
13647
|
});
|
|
13648
|
+
sessionsCmd.command("latest").description("Show the most recently active session").option("--dir <path>", "trace directory").option("--correlate-group", "include synthetic group: sessions").option("--stale-after <duration>", "staleness threshold for status derivation").option("--json", "print JSON result").action((opts) => {
|
|
13649
|
+
runCommand(() => sessionsLatestCommand(opts));
|
|
13650
|
+
});
|
|
13651
|
+
sessionsCmd.command("activity").description("Summarize recent session activity").option("--dir <path>", "trace directory").option("--since <duration>", "activity window (default 7d)").option("--correlate-group", "include synthetic group: sessions").option("--stale-after <duration>", "staleness threshold for status derivation").option("--json", "print JSON result").action((opts) => {
|
|
13652
|
+
runCommand(() => sessionsActivityCommand(opts));
|
|
13653
|
+
});
|
|
13654
|
+
sessionsCmd.command("show").description("Show one session (alias for session <id>)").argument("<session-id>", "session id").option("--dir <path>", "trace directory").option("--timeline", "include per-run timelines").option("--critical-path", "include critical path section").option("--diagnostics", "include ambiguity warnings").option("--json", "print JSON result").action((sessionId, opts) => {
|
|
13655
|
+
runCommand(() => sessionsShowCommand(sessionId, opts));
|
|
13656
|
+
});
|
|
13657
|
+
sessionsCmd.command("handoffs").description("List handoff edges across sessions").option("--dir <path>", "trace directory").option("--session <id>", "limit to one session").option("--correlate-group", "include synthetic group: sessions").option("--json", "print JSON result").action((opts) => {
|
|
13658
|
+
runCommand(() => sessionsHandoffsCommand(opts));
|
|
13659
|
+
});
|
|
13660
|
+
sessionsCmd.command("errors").description("List sessions with errors in a time window").option("--dir <path>", "trace directory").option("--since <duration>", "filter by last activity (default: all)").option("--correlate-group", "include synthetic group: sessions").option("--stale-after <duration>", "staleness threshold for status derivation").option("--json", "print JSON result").action((opts) => {
|
|
13661
|
+
runCommand(() => sessionsErrorsCommand(opts));
|
|
13662
|
+
});
|
|
13055
13663
|
program.command("session").description("Inspect one workflow session: runs, handoffs, retries (read-only)").argument("<session-id>", "session id (from sessions output)").option("--dir <path>", "trace directory").option("--timeline", "include per-run timelines").option("--critical-path", "include critical path section").option("--diagnostics", "include ambiguity warnings").option("--json", "print session view as JSON").action((sessionId, opts) => {
|
|
13056
13664
|
runCommand(() => sessionCommand(sessionId, opts));
|
|
13057
13665
|
});
|
|
@@ -13180,9 +13788,9 @@ function isPrimaryModule() {
|
|
|
13180
13788
|
if (!entry) return false;
|
|
13181
13789
|
const selfPath = fileURLToPath(import.meta.url);
|
|
13182
13790
|
try {
|
|
13183
|
-
return realpathSync(
|
|
13791
|
+
return realpathSync(path13.resolve(entry)) === realpathSync(path13.resolve(selfPath));
|
|
13184
13792
|
} catch {
|
|
13185
|
-
return
|
|
13793
|
+
return path13.resolve(entry) === path13.resolve(selfPath);
|
|
13186
13794
|
}
|
|
13187
13795
|
}
|
|
13188
13796
|
if (isPrimaryModule()) {
|