agent-inspect 4.2.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 +6 -0
- package/docs/CLI.md +33 -0
- package/package.json +1 -1
- package/packages/cli/dist/{chunk-5VSPJEZ7.mjs → chunk-BS5LSKZ3.mjs} +190 -6
- package/packages/cli/dist/chunk-BS5LSKZ3.mjs.map +1 -0
- package/packages/cli/dist/index.cjs +2122 -1533
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +1863 -1496
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/cli/dist/{src-VQT7QLGV.mjs → src-YFMPWEIS.mjs} +3 -3
- package/packages/cli/dist/{src-VQT7QLGV.mjs.map → src-YFMPWEIS.mjs.map} +1 -1
- package/packages/core/dist/advanced.cjs +196 -3
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +93 -1
- package/packages/core/dist/advanced.d.ts +93 -1
- package/packages/core/dist/advanced.mjs +189 -4
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/cli/dist/chunk-5VSPJEZ7.mjs.map +0 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { resolveTraceDir, parseDuration, buildSessionIndex, enrichSessionRunRecord, TraceDirectory, loadTraceMetadataList, loadSessionRunRecords, extractMetadata, filterTraces, truncateName, getTraceFilePath, buildRunSummary, formatDuration, formatTimestamp, isAgentInspectTrace, filterMetasBySessionScope, aggregateSessionCheckResults, buildRunTimeline, renderTimeline, buildTraceStats, renderTraceStats, parseDurationFilter, searchTraces, buildActivitySummary, renderActivitySummaryHuman, buildRunWhatSummary, renderRunWhat, buildLocalExplanation, persistedInspectEventsToTraceEvents, renderStepLine, renderErrorLine, getIndent, Redactor, validateEvent, isPersistedInspectEvent, nanoid, resolveRedactionProfile, applyProfileMetadataCaps, extractCorrelationMetadata, truncateStringForProfile, parseTraceJsonl } from './chunk-
|
|
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)";
|
|
@@ -5826,7 +5828,7 @@ function indexedToMetadata(row, traceDir) {
|
|
|
5826
5828
|
endedAt: row.endedAt ?? void 0,
|
|
5827
5829
|
durationMs: row.durationMs ?? void 0,
|
|
5828
5830
|
eventCount: 0,
|
|
5829
|
-
filePath:
|
|
5831
|
+
filePath: path13.join(traceDir, row.file),
|
|
5830
5832
|
fileSize: 0,
|
|
5831
5833
|
createdAt: new Date(row.mtimeMs)
|
|
5832
5834
|
};
|
|
@@ -5855,7 +5857,7 @@ async function newestTraceMtimeMs(traceDir) {
|
|
|
5855
5857
|
}
|
|
5856
5858
|
async function loadSessionRuns(traceDir) {
|
|
5857
5859
|
try {
|
|
5858
|
-
const mod = await import('./src-
|
|
5860
|
+
const mod = await import('./src-YFMPWEIS.mjs');
|
|
5859
5861
|
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
5860
5862
|
const status = mod.indexStatus(dbPath);
|
|
5861
5863
|
if (!status.healthy) {
|
|
@@ -6302,9 +6304,9 @@ async function reportCommand(runId, options = {}) {
|
|
|
6302
6304
|
redactionProfile,
|
|
6303
6305
|
correlation: !options.noCorrelation
|
|
6304
6306
|
});
|
|
6305
|
-
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;
|
|
6306
6308
|
if (outPath !== void 0) {
|
|
6307
|
-
await mkdir(
|
|
6309
|
+
await mkdir(path13.dirname(outPath), { recursive: true });
|
|
6308
6310
|
await writeFile(outPath, result.content, "utf-8");
|
|
6309
6311
|
console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
|
|
6310
6312
|
}
|
|
@@ -6551,17 +6553,17 @@ function applyRule(rule, value, replacement) {
|
|
|
6551
6553
|
}
|
|
6552
6554
|
return value;
|
|
6553
6555
|
}
|
|
6554
|
-
function childPath(
|
|
6556
|
+
function childPath(path19, key) {
|
|
6555
6557
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
|
|
6556
|
-
return
|
|
6558
|
+
return path19 ? `${path19}.${key}` : key;
|
|
6557
6559
|
}
|
|
6558
|
-
return `${
|
|
6560
|
+
return `${path19 || "$"}[${JSON.stringify(key)}]`;
|
|
6559
6561
|
}
|
|
6560
|
-
function indexPath(
|
|
6561
|
-
return `${
|
|
6562
|
+
function indexPath(path19, index) {
|
|
6563
|
+
return `${path19 || "$"}[${index}]`;
|
|
6562
6564
|
}
|
|
6563
|
-
function makeFinding(
|
|
6564
|
-
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 };
|
|
6565
6567
|
}
|
|
6566
6568
|
function createRedactionProfile(profile = "local") {
|
|
6567
6569
|
switch (profile) {
|
|
@@ -6630,11 +6632,11 @@ var Redactor2 = class {
|
|
|
6630
6632
|
#recordFinding(state, finding) {
|
|
6631
6633
|
if (this.#collectFindings) state.findings.push(finding);
|
|
6632
6634
|
}
|
|
6633
|
-
#redactValue(value, key,
|
|
6635
|
+
#redactValue(value, key, path19, depth, state) {
|
|
6634
6636
|
if (depth > this.#maxDepth) {
|
|
6635
6637
|
this.#recordFinding(
|
|
6636
6638
|
state,
|
|
6637
|
-
makeFinding(
|
|
6639
|
+
makeFinding(path19, "structure.maxDepth", "truncate", "value", "warning")
|
|
6638
6640
|
);
|
|
6639
6641
|
return "[Truncated]";
|
|
6640
6642
|
}
|
|
@@ -6643,19 +6645,19 @@ var Redactor2 = class {
|
|
|
6643
6645
|
if (rule) {
|
|
6644
6646
|
this.#recordFinding(
|
|
6645
6647
|
state,
|
|
6646
|
-
makeFinding(
|
|
6648
|
+
makeFinding(path19, `key.${rule.key}`, actionForRule(rule), "key", "warning")
|
|
6647
6649
|
);
|
|
6648
6650
|
return applyRule(rule, value, this.#replacement);
|
|
6649
6651
|
}
|
|
6650
6652
|
}
|
|
6651
6653
|
for (const detector of this.#detectors) {
|
|
6652
|
-
const detections = detector.detect({ path:
|
|
6654
|
+
const detections = detector.detect({ path: path19, key, value });
|
|
6653
6655
|
for (const detection of detections) {
|
|
6654
6656
|
const action = detection.action ?? "replace";
|
|
6655
6657
|
this.#recordFinding(
|
|
6656
6658
|
state,
|
|
6657
6659
|
makeFinding(
|
|
6658
|
-
|
|
6660
|
+
path19,
|
|
6659
6661
|
detector.id,
|
|
6660
6662
|
action,
|
|
6661
6663
|
detection.matchKind ?? detector.matchKind ?? "custom",
|
|
@@ -6673,7 +6675,7 @@ var Redactor2 = class {
|
|
|
6673
6675
|
const out = [];
|
|
6674
6676
|
state.seen.set(value, out);
|
|
6675
6677
|
value.forEach((item, index) => {
|
|
6676
|
-
out[index] = this.#redactValue(item, void 0, indexPath(
|
|
6678
|
+
out[index] = this.#redactValue(item, void 0, indexPath(path19, index), depth + 1, state);
|
|
6677
6679
|
});
|
|
6678
6680
|
return out;
|
|
6679
6681
|
}
|
|
@@ -6685,7 +6687,7 @@ var Redactor2 = class {
|
|
|
6685
6687
|
out[entryKey] = this.#redactValue(
|
|
6686
6688
|
entryValue,
|
|
6687
6689
|
entryKey,
|
|
6688
|
-
childPath(
|
|
6690
|
+
childPath(path19 === "$" ? "" : path19, entryKey),
|
|
6689
6691
|
depth + 1,
|
|
6690
6692
|
state
|
|
6691
6693
|
);
|
|
@@ -6811,6 +6813,9 @@ function redactDocument(content, profile) {
|
|
|
6811
6813
|
return redactJsonlText(content, profile);
|
|
6812
6814
|
}
|
|
6813
6815
|
}
|
|
6816
|
+
function redactTraceContent(content, profile) {
|
|
6817
|
+
return redactDocument(content, profile);
|
|
6818
|
+
}
|
|
6814
6819
|
async function redactCommand(target, options = {}, stdin = process.stdin) {
|
|
6815
6820
|
const profile = parseRedactionProfile3(options.profile);
|
|
6816
6821
|
const source = await contentFromTarget(target, options, stdin);
|
|
@@ -7132,14 +7137,14 @@ function uniqueSorted(values) {
|
|
|
7132
7137
|
return [...new Set(values)].sort();
|
|
7133
7138
|
}
|
|
7134
7139
|
function isWithinDirectory(child, parent) {
|
|
7135
|
-
const relative =
|
|
7136
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
7140
|
+
const relative = path13.relative(parent, child);
|
|
7141
|
+
return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
|
|
7137
7142
|
}
|
|
7138
7143
|
async function resolveOutputPath(inputPath, output2, force) {
|
|
7139
7144
|
if (output2 === void 0 || output2.trim() === "") return void 0;
|
|
7140
|
-
const inputAbs =
|
|
7141
|
-
const outputAbs =
|
|
7142
|
-
const inputDir =
|
|
7145
|
+
const inputAbs = path13.resolve(inputPath);
|
|
7146
|
+
const outputAbs = path13.resolve(output2.trim());
|
|
7147
|
+
const inputDir = path13.dirname(inputAbs);
|
|
7143
7148
|
if (!isWithinDirectory(outputAbs, inputDir)) {
|
|
7144
7149
|
throw new Error("Refusing to write migrated output outside the input directory.");
|
|
7145
7150
|
}
|
|
@@ -7260,7 +7265,7 @@ async function migrateCommand(input3, options = {}) {
|
|
|
7260
7265
|
process.exitCode = 1;
|
|
7261
7266
|
return;
|
|
7262
7267
|
}
|
|
7263
|
-
const inputPath =
|
|
7268
|
+
const inputPath = path13.resolve(input3.trim());
|
|
7264
7269
|
const dryRun = options.dryRun === true;
|
|
7265
7270
|
if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
|
|
7266
7271
|
console.error("migrate requires --dry-run or --output <path>.");
|
|
@@ -7279,7 +7284,7 @@ async function migrateCommand(input3, options = {}) {
|
|
|
7279
7284
|
);
|
|
7280
7285
|
const result = await buildMigration(inputPath, outputPath);
|
|
7281
7286
|
if (!dryRun && outputPath !== void 0) {
|
|
7282
|
-
await mkdir(
|
|
7287
|
+
await mkdir(path13.dirname(outputPath), { recursive: true });
|
|
7283
7288
|
await writeFile(outputPath, result.content, "utf-8");
|
|
7284
7289
|
}
|
|
7285
7290
|
printSummary2(result, dryRun);
|
|
@@ -7557,7 +7562,7 @@ function stripPrefix(name, prefixes) {
|
|
|
7557
7562
|
}
|
|
7558
7563
|
return name;
|
|
7559
7564
|
}
|
|
7560
|
-
function eventEvidence(event,
|
|
7565
|
+
function eventEvidence(event, path19) {
|
|
7561
7566
|
return {
|
|
7562
7567
|
runId: event.runId,
|
|
7563
7568
|
eventId: event.eventId,
|
|
@@ -7567,7 +7572,7 @@ function eventEvidence(event, path18) {
|
|
|
7567
7572
|
kind: event.kind,
|
|
7568
7573
|
name: event.name,
|
|
7569
7574
|
status: event.status,
|
|
7570
|
-
...
|
|
7575
|
+
...path19 ? { path: path19 } : {}
|
|
7571
7576
|
};
|
|
7572
7577
|
}
|
|
7573
7578
|
function runEvidence(run) {
|
|
@@ -7630,9 +7635,9 @@ function eventEndMs(event) {
|
|
|
7630
7635
|
function normalizedKey(value) {
|
|
7631
7636
|
return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
|
|
7632
7637
|
}
|
|
7633
|
-
function lastPathSegment(
|
|
7634
|
-
const parts =
|
|
7635
|
-
return parts[parts.length - 1] ??
|
|
7638
|
+
function lastPathSegment(path19) {
|
|
7639
|
+
const parts = path19.split(".");
|
|
7640
|
+
return parts[parts.length - 1] ?? path19;
|
|
7636
7641
|
}
|
|
7637
7642
|
function valueType(value) {
|
|
7638
7643
|
if (Array.isArray(value)) return "array";
|
|
@@ -7646,12 +7651,12 @@ function serializedByteLength(value) {
|
|
|
7646
7651
|
return void 0;
|
|
7647
7652
|
}
|
|
7648
7653
|
}
|
|
7649
|
-
function pushValueEntries(entries, event, value,
|
|
7650
|
-
entries.push({ event, path:
|
|
7654
|
+
function pushValueEntries(entries, event, value, path19, key, depth = 0) {
|
|
7655
|
+
entries.push({ event, path: path19, key, value });
|
|
7651
7656
|
if (depth >= 8) return;
|
|
7652
7657
|
if (Array.isArray(value)) {
|
|
7653
7658
|
for (const [index, item] of value.entries()) {
|
|
7654
|
-
pushValueEntries(entries, event, item, `${
|
|
7659
|
+
pushValueEntries(entries, event, item, `${path19}.${index}`, String(index), depth + 1);
|
|
7655
7660
|
}
|
|
7656
7661
|
return;
|
|
7657
7662
|
}
|
|
@@ -7661,7 +7666,7 @@ function pushValueEntries(entries, event, value, path18, key, depth = 0) {
|
|
|
7661
7666
|
entries,
|
|
7662
7667
|
event,
|
|
7663
7668
|
value[nestedKey],
|
|
7664
|
-
`${
|
|
7669
|
+
`${path19}.${nestedKey}`,
|
|
7665
7670
|
nestedKey,
|
|
7666
7671
|
depth + 1
|
|
7667
7672
|
);
|
|
@@ -7742,9 +7747,9 @@ function eventDurationMs(event) {
|
|
|
7742
7747
|
}
|
|
7743
7748
|
function treeShape(nodes) {
|
|
7744
7749
|
const lines = [];
|
|
7745
|
-
const visit = (node,
|
|
7746
|
-
lines.push(`${
|
|
7747
|
-
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}`));
|
|
7748
7753
|
};
|
|
7749
7754
|
nodes.forEach((node, index) => visit(node, String(index)));
|
|
7750
7755
|
return lines;
|
|
@@ -7793,9 +7798,9 @@ function retrievalShape(context) {
|
|
|
7793
7798
|
function guardrailShape(context) {
|
|
7794
7799
|
return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
|
|
7795
7800
|
}
|
|
7796
|
-
function firstEvidenceForKind(context, kind,
|
|
7801
|
+
function firstEvidenceForKind(context, kind, path19) {
|
|
7797
7802
|
const event = context.events.find((candidate) => candidate.kind === kind);
|
|
7798
|
-
return event ? [eventEvidence(event,
|
|
7803
|
+
return event ? [eventEvidence(event, path19)] : runEvidence(context.selectedRun);
|
|
7799
7804
|
}
|
|
7800
7805
|
function baselineDiffFinding(message, evidence, expected, actual) {
|
|
7801
7806
|
return failFinding("baseline.regression", message, evidence, expected, actual);
|
|
@@ -8145,13 +8150,13 @@ function createStructureCycleRule() {
|
|
|
8145
8150
|
const seenCycles = /* @__PURE__ */ new Set();
|
|
8146
8151
|
const findings = [];
|
|
8147
8152
|
for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
|
|
8148
|
-
const
|
|
8153
|
+
const path19 = [];
|
|
8149
8154
|
const seenAt = /* @__PURE__ */ new Map();
|
|
8150
8155
|
let current = event;
|
|
8151
8156
|
while (current) {
|
|
8152
8157
|
const existing = seenAt.get(current.eventId);
|
|
8153
8158
|
if (existing !== void 0) {
|
|
8154
|
-
const cycle =
|
|
8159
|
+
const cycle = path19.slice(existing);
|
|
8155
8160
|
const key = cycle.map((item) => item.eventId).sort().join("\0");
|
|
8156
8161
|
if (!seenCycles.has(key)) {
|
|
8157
8162
|
seenCycles.add(key);
|
|
@@ -8167,8 +8172,8 @@ function createStructureCycleRule() {
|
|
|
8167
8172
|
}
|
|
8168
8173
|
break;
|
|
8169
8174
|
}
|
|
8170
|
-
seenAt.set(current.eventId,
|
|
8171
|
-
|
|
8175
|
+
seenAt.set(current.eventId, path19.length);
|
|
8176
|
+
path19.push(current);
|
|
8172
8177
|
current = current.parentId ? byId.get(current.parentId) : void 0;
|
|
8173
8178
|
}
|
|
8174
8179
|
}
|
|
@@ -8965,23 +8970,23 @@ function evaluatePromptInjection(text, options = {}) {
|
|
|
8965
8970
|
}
|
|
8966
8971
|
return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
|
|
8967
8972
|
}
|
|
8968
|
-
function validateSchemaField(value, field,
|
|
8973
|
+
function validateSchemaField(value, field, path19, evidence) {
|
|
8969
8974
|
const ruleId = "guardrail.structured-output";
|
|
8970
8975
|
if (field.type) {
|
|
8971
8976
|
const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
8972
8977
|
if (actual !== field.type) {
|
|
8973
|
-
evidence.push({ ruleId, path:
|
|
8978
|
+
evidence.push({ ruleId, path: path19, preview: `expected ${field.type}, got ${actual}` });
|
|
8974
8979
|
return;
|
|
8975
8980
|
}
|
|
8976
8981
|
}
|
|
8977
8982
|
if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
|
|
8978
|
-
evidence.push({ ruleId, path:
|
|
8983
|
+
evidence.push({ ruleId, path: path19, preview: "value not in enum" });
|
|
8979
8984
|
}
|
|
8980
8985
|
if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
|
|
8981
8986
|
const record = value;
|
|
8982
8987
|
for (const key of field.required) {
|
|
8983
8988
|
if (!(key in record)) {
|
|
8984
|
-
evidence.push({ ruleId, path: `${
|
|
8989
|
+
evidence.push({ ruleId, path: `${path19}.${key}`, preview: "missing required key" });
|
|
8985
8990
|
}
|
|
8986
8991
|
}
|
|
8987
8992
|
}
|
|
@@ -9308,7 +9313,7 @@ function asConfig(value) {
|
|
|
9308
9313
|
}
|
|
9309
9314
|
async function loadConfig(configPath) {
|
|
9310
9315
|
if (configPath === void 0) return {};
|
|
9311
|
-
const extension =
|
|
9316
|
+
const extension = path13.extname(configPath);
|
|
9312
9317
|
if (TS_CONFIG_EXTENSIONS.has(extension)) {
|
|
9313
9318
|
throw new Error(
|
|
9314
9319
|
"TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
|
|
@@ -9317,7 +9322,7 @@ async function loadConfig(configPath) {
|
|
|
9317
9322
|
if (!CONFIG_EXTENSIONS.has(extension)) {
|
|
9318
9323
|
throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
|
|
9319
9324
|
}
|
|
9320
|
-
const absolute =
|
|
9325
|
+
const absolute = path13.resolve(configPath);
|
|
9321
9326
|
if (extension === ".json") {
|
|
9322
9327
|
const raw = await readFile(absolute, "utf-8");
|
|
9323
9328
|
return asConfig(JSON.parse(raw));
|
|
@@ -9466,10 +9471,10 @@ function printHuman(result) {
|
|
|
9466
9471
|
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
9467
9472
|
}
|
|
9468
9473
|
for (const finding of result.findings) {
|
|
9469
|
-
const
|
|
9474
|
+
const path19 = finding.evidence[0]?.path;
|
|
9470
9475
|
const run = finding.evidence[0]?.runId;
|
|
9471
9476
|
const runPrefix = run ? `[${run}] ` : "";
|
|
9472
|
-
console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${
|
|
9477
|
+
console.log(`- ${runPrefix}${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
|
|
9473
9478
|
}
|
|
9474
9479
|
}
|
|
9475
9480
|
function readErrorResult(error) {
|
|
@@ -9669,7 +9674,7 @@ function createViewerServer(options = {}) {
|
|
|
9669
9674
|
return sendJson(res, 200, {
|
|
9670
9675
|
ok: true,
|
|
9671
9676
|
readOnly: true,
|
|
9672
|
-
traceDir:
|
|
9677
|
+
traceDir: path13.resolve(traceDir)
|
|
9673
9678
|
});
|
|
9674
9679
|
}
|
|
9675
9680
|
const td = new TraceDirectory({ dir: traceDir });
|
|
@@ -9687,7 +9692,7 @@ function createViewerServer(options = {}) {
|
|
|
9687
9692
|
runId: meta.runId,
|
|
9688
9693
|
name: meta.name,
|
|
9689
9694
|
status: meta.status,
|
|
9690
|
-
file:
|
|
9695
|
+
file: path13.basename(meta.filePath),
|
|
9691
9696
|
startedAt: meta.startedAt,
|
|
9692
9697
|
durationMs: meta.durationMs
|
|
9693
9698
|
}))
|
|
@@ -9802,7 +9807,7 @@ function startViewerServer(options = {}) {
|
|
|
9802
9807
|
resolve({
|
|
9803
9808
|
host,
|
|
9804
9809
|
port: resolvedPort,
|
|
9805
|
-
traceDir:
|
|
9810
|
+
traceDir: path13.resolve(traceDir),
|
|
9806
9811
|
url: `http://${host}:${resolvedPort}`
|
|
9807
9812
|
});
|
|
9808
9813
|
});
|
|
@@ -10007,10 +10012,10 @@ async function evalRun(input3, options = {}) {
|
|
|
10007
10012
|
diagnostics: []
|
|
10008
10013
|
};
|
|
10009
10014
|
}
|
|
10010
|
-
function evidenceForRun(run,
|
|
10011
|
-
return [{ runId: run.runId, ...
|
|
10015
|
+
function evidenceForRun(run, path19) {
|
|
10016
|
+
return [{ runId: run.runId, ...path19 !== void 0 ? { path: path19 } : {} }];
|
|
10012
10017
|
}
|
|
10013
|
-
function evidenceForEvent(event,
|
|
10018
|
+
function evidenceForEvent(event, path19) {
|
|
10014
10019
|
return [
|
|
10015
10020
|
{
|
|
10016
10021
|
runId: event.runId,
|
|
@@ -10018,7 +10023,7 @@ function evidenceForEvent(event, path18) {
|
|
|
10018
10023
|
...event.parentId !== void 0 ? { parentId: event.parentId } : {},
|
|
10019
10024
|
kind: event.kind,
|
|
10020
10025
|
name: event.name,
|
|
10021
|
-
...
|
|
10026
|
+
...path19 !== void 0 ? { path: path19 } : {}
|
|
10022
10027
|
}
|
|
10023
10028
|
];
|
|
10024
10029
|
}
|
|
@@ -10176,9 +10181,9 @@ function collectTextFields(nodes, keys, preferredKinds = []) {
|
|
|
10176
10181
|
function tokenize(text) {
|
|
10177
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));
|
|
10178
10183
|
}
|
|
10179
|
-
function firstEvidence(fields, run,
|
|
10184
|
+
function firstEvidence(fields, run, path19) {
|
|
10180
10185
|
const first = fields[0];
|
|
10181
|
-
return first === void 0 ? evidenceForRun(run,
|
|
10186
|
+
return first === void 0 ? evidenceForRun(run, path19) : evidenceForEvent(first.node.event, first.path);
|
|
10182
10187
|
}
|
|
10183
10188
|
function collectSourceIds(nodes, keys) {
|
|
10184
10189
|
const wanted = keySet(keys);
|
|
@@ -10555,8 +10560,8 @@ function renderEvalMarkdown(result) {
|
|
|
10555
10560
|
if (result.findings.length > 0) {
|
|
10556
10561
|
lines.push("", "## Findings");
|
|
10557
10562
|
for (const finding of result.findings) {
|
|
10558
|
-
const
|
|
10559
|
-
lines.push(`- ${finding.ruleId}: ${finding.message}${
|
|
10563
|
+
const path19 = finding.evidence[0]?.path;
|
|
10564
|
+
lines.push(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
|
|
10560
10565
|
}
|
|
10561
10566
|
}
|
|
10562
10567
|
return `${lines.join("\n")}
|
|
@@ -10603,7 +10608,7 @@ function asConfig2(value) {
|
|
|
10603
10608
|
}
|
|
10604
10609
|
async function loadConfig2(configPath) {
|
|
10605
10610
|
if (configPath === void 0) return {};
|
|
10606
|
-
const extension =
|
|
10611
|
+
const extension = path13.extname(configPath);
|
|
10607
10612
|
if (TS_CONFIG_EXTENSIONS2.has(extension)) {
|
|
10608
10613
|
throw new Error(
|
|
10609
10614
|
"TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
|
|
@@ -10612,7 +10617,7 @@ async function loadConfig2(configPath) {
|
|
|
10612
10617
|
if (!CONFIG_EXTENSIONS2.has(extension)) {
|
|
10613
10618
|
throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
|
|
10614
10619
|
}
|
|
10615
|
-
const absolute =
|
|
10620
|
+
const absolute = path13.resolve(configPath);
|
|
10616
10621
|
if (extension === ".json") {
|
|
10617
10622
|
const raw = await readFile(absolute, "utf-8");
|
|
10618
10623
|
return asConfig2(JSON.parse(raw));
|
|
@@ -10749,8 +10754,8 @@ function printHuman2(result) {
|
|
|
10749
10754
|
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
10750
10755
|
}
|
|
10751
10756
|
for (const finding of result.findings) {
|
|
10752
|
-
const
|
|
10753
|
-
console.log(`- ${finding.ruleId}: ${finding.message}${
|
|
10757
|
+
const path19 = finding.evidence[0]?.path;
|
|
10758
|
+
console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
|
|
10754
10759
|
}
|
|
10755
10760
|
}
|
|
10756
10761
|
function readErrorResult2(error) {
|
|
@@ -10988,8 +10993,8 @@ function printHuman3(result) {
|
|
|
10988
10993
|
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
10989
10994
|
}
|
|
10990
10995
|
for (const finding of result.findings) {
|
|
10991
|
-
const
|
|
10992
|
-
console.log(`- ${finding.ruleId}: ${finding.message}${
|
|
10996
|
+
const path19 = finding.evidence[0]?.path;
|
|
10997
|
+
console.log(`- ${finding.ruleId}: ${finding.message}${path19 ? ` (${path19})` : ""}`);
|
|
10993
10998
|
}
|
|
10994
10999
|
console.log(`Note: ${result.note}`);
|
|
10995
11000
|
}
|
|
@@ -11035,6 +11040,38 @@ function scanCommand(target, options = {}, stdin = process.stdin) {
|
|
|
11035
11040
|
function verifySafeCommand(target, options = {}, stdin = process.stdin) {
|
|
11036
11041
|
return safetyCommand("verify-safe", target, options, stdin);
|
|
11037
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
|
+
}
|
|
11038
11075
|
var NOTE = "Generated locally by AgentInspect. Artifacts are best-effort summaries, not compliance or security certification.";
|
|
11039
11076
|
var SAFETY_RULES = [
|
|
11040
11077
|
createSafetyRawContentRule(),
|
|
@@ -11108,8 +11145,8 @@ function renderCheckSection(result) {
|
|
|
11108
11145
|
`Diagnostics: ${result.diagnostics.length}`
|
|
11109
11146
|
];
|
|
11110
11147
|
for (const finding of result.findings.slice(0, 10)) {
|
|
11111
|
-
const
|
|
11112
|
-
lines.push(`- ${finding.ruleId}: ${finding.message} (${
|
|
11148
|
+
const path19 = finding.evidence[0]?.path ?? "(run)";
|
|
11149
|
+
lines.push(`- ${finding.ruleId}: ${finding.message} (${path19})`);
|
|
11113
11150
|
}
|
|
11114
11151
|
for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
|
|
11115
11152
|
lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
@@ -11187,8 +11224,8 @@ function renderHtml(trace, check, diff) {
|
|
|
11187
11224
|
`;
|
|
11188
11225
|
}
|
|
11189
11226
|
async function writeArtifact(outputDir, relativePath, content, files) {
|
|
11190
|
-
const outPath =
|
|
11191
|
-
await mkdir(
|
|
11227
|
+
const outPath = path13.join(outputDir, relativePath);
|
|
11228
|
+
await mkdir(path13.dirname(outPath), { recursive: true });
|
|
11192
11229
|
await writeFile(outPath, content, "utf-8");
|
|
11193
11230
|
files.push(relativePath);
|
|
11194
11231
|
}
|
|
@@ -11204,7 +11241,7 @@ function manifestStatus(check, diff) {
|
|
|
11204
11241
|
return "ok";
|
|
11205
11242
|
}
|
|
11206
11243
|
async function artifactsCommand(target, options = {}, stdin = process.stdin) {
|
|
11207
|
-
const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ?
|
|
11244
|
+
const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path13.resolve(options.outputDir.trim()) : "";
|
|
11208
11245
|
if (outputDir === "") {
|
|
11209
11246
|
console.error("--output-dir is required.");
|
|
11210
11247
|
process.exitCode = 1;
|
|
@@ -11270,8 +11307,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
|
|
|
11270
11307
|
await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
|
|
11271
11308
|
const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
|
|
11272
11309
|
if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
|
|
11273
|
-
await mkdir(
|
|
11274
|
-
await appendFile(
|
|
11310
|
+
await mkdir(path13.dirname(path13.resolve(summaryTarget)), { recursive: true });
|
|
11311
|
+
await appendFile(path13.resolve(summaryTarget), `
|
|
11275
11312
|
${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
11276
11313
|
}
|
|
11277
11314
|
const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
|
|
@@ -11290,10 +11327,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
|
11290
11327
|
findings: diff?.findings.length ?? 0,
|
|
11291
11328
|
diagnostics: diff?.diagnostics.length ?? 0
|
|
11292
11329
|
},
|
|
11293
|
-
...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary:
|
|
11330
|
+
...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path13.resolve(summaryTarget) } : {},
|
|
11294
11331
|
note: NOTE
|
|
11295
11332
|
};
|
|
11296
|
-
await writeFile(
|
|
11333
|
+
await writeFile(path13.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
|
|
11297
11334
|
if (options.json === true) {
|
|
11298
11335
|
console.log(writeJson3(manifest).trimEnd());
|
|
11299
11336
|
} else {
|
|
@@ -11304,1568 +11341,1893 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
|
11304
11341
|
}
|
|
11305
11342
|
}
|
|
11306
11343
|
}
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
}
|
|
11335
|
-
|
|
11336
|
-
}
|
|
11337
|
-
const normalized = path11.posix.normalize(rawPath.replace(/\\/g, "/"));
|
|
11338
|
-
const segments = normalized.split("/");
|
|
11339
|
-
if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
|
|
11340
|
-
diagnostics.push({
|
|
11341
|
-
code: "artifact_path_escape",
|
|
11342
|
-
severity: "error",
|
|
11343
|
-
message: "Reporter artifact path must stay under the output directory.",
|
|
11344
|
-
target: rawPath
|
|
11345
|
-
});
|
|
11346
|
-
return { ok: false, outputDir, diagnostics };
|
|
11347
|
-
}
|
|
11348
|
-
const absolutePath = path11.resolve(outputDir, normalized);
|
|
11349
|
-
const relFromOutput = path11.relative(outputDir, absolutePath);
|
|
11350
|
-
if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path11.isAbsolute(relFromOutput)) {
|
|
11351
|
-
diagnostics.push({
|
|
11352
|
-
code: "artifact_path_escape",
|
|
11353
|
-
severity: "error",
|
|
11354
|
-
message: "Reporter artifact path resolved outside the output directory.",
|
|
11355
|
-
target: rawPath
|
|
11356
|
-
});
|
|
11357
|
-
return { ok: false, outputDir, diagnostics };
|
|
11358
|
-
}
|
|
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
|
+
};
|
|
11359
11373
|
return {
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
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
|
|
11365
11384
|
};
|
|
11366
11385
|
}
|
|
11367
|
-
|
|
11368
|
-
// packages/cli/src/ci-summary.ts
|
|
11369
|
-
var NOTE2 = "Generated locally by AgentInspect from reporter artifact manifests. Trace contents are not embedded.";
|
|
11370
|
-
var MAX_TEXT = 180;
|
|
11371
|
-
var FRAMEWORKS = /* @__PURE__ */ new Set(["vitest", "jest", "manual"]);
|
|
11372
|
-
var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
|
|
11373
|
-
var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
|
|
11374
|
-
var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
|
|
11375
|
-
var REDACTION_PROFILES = /* @__PURE__ */ new Set(["local", "share", "strict"]);
|
|
11376
|
-
function stable6(value) {
|
|
11377
|
-
if (Array.isArray(value)) return value.map(stable6);
|
|
11378
|
-
if (value === null || typeof value !== "object") return value;
|
|
11379
|
-
const record = value;
|
|
11380
|
-
return Object.fromEntries(
|
|
11381
|
-
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
|
|
11382
|
-
);
|
|
11383
|
-
}
|
|
11384
|
-
function writeJson4(value) {
|
|
11385
|
-
return `${JSON.stringify(stable6(value), null, 2)}
|
|
11386
|
-
`;
|
|
11387
|
-
}
|
|
11388
|
-
function isObject(value) {
|
|
11386
|
+
function isPlainObject(value) {
|
|
11389
11387
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11390
11388
|
}
|
|
11391
|
-
function
|
|
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) {
|
|
11392
11399
|
if (typeof value !== "string" || value.trim() === "") {
|
|
11393
|
-
|
|
11400
|
+
errors.push(`${field} must be a non-empty string`);
|
|
11401
|
+
return;
|
|
11394
11402
|
}
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
}
|
|
11400
|
-
function readFramework(value) {
|
|
11401
|
-
const framework = readString(value, "manifest.framework");
|
|
11402
|
-
if (!FRAMEWORKS.has(framework)) {
|
|
11403
|
-
throw new Error(`Unsupported reporter framework: ${framework}.`);
|
|
11403
|
+
if (!isSafeRelativeWorkspacePath(value)) {
|
|
11404
|
+
errors.push(
|
|
11405
|
+
`${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
|
|
11406
|
+
);
|
|
11404
11407
|
}
|
|
11405
|
-
return framework;
|
|
11406
11408
|
}
|
|
11407
|
-
function
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11409
|
+
function validateIndex(value, errors) {
|
|
11410
|
+
if (!isPlainObject(value)) {
|
|
11411
|
+
errors.push("index must be an object");
|
|
11412
|
+
return void 0;
|
|
11411
11413
|
}
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
function readArtifact(value, index) {
|
|
11415
|
-
if (!isObject(value)) throw new Error(`manifest.artifacts[${index}] must be an object.`);
|
|
11416
|
-
const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
|
|
11417
|
-
const format = readString(value.format, `manifest.artifacts[${index}].format`);
|
|
11418
|
-
const redactionProfile = readString(
|
|
11419
|
-
value.redactionProfile,
|
|
11420
|
-
`manifest.artifacts[${index}].redactionProfile`
|
|
11421
|
-
);
|
|
11422
|
-
if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
|
|
11423
|
-
if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
|
|
11424
|
-
if (!REDACTION_PROFILES.has(redactionProfile)) {
|
|
11425
|
-
throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
|
|
11414
|
+
if (typeof value.enabled !== "boolean") {
|
|
11415
|
+
errors.push("index.enabled must be a boolean");
|
|
11426
11416
|
}
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
outputDir: process.cwd(),
|
|
11430
|
-
relativePath: artifactPath
|
|
11431
|
-
});
|
|
11432
|
-
if (!pathCheck.ok || pathCheck.relativePath === void 0) {
|
|
11433
|
-
throw new Error(`Unsafe reporter artifact path: ${artifactPath}.`);
|
|
11417
|
+
if (!INDEX_TYPES.includes(value.type)) {
|
|
11418
|
+
errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
|
|
11434
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;
|
|
11435
11426
|
return {
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
redactionProfile
|
|
11440
|
-
};
|
|
11441
|
-
}
|
|
11442
|
-
function readArtifacts(value, label) {
|
|
11443
|
-
if (value === void 0) return [];
|
|
11444
|
-
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
11445
|
-
return value.map((item, index) => readArtifact(item, index));
|
|
11446
|
-
}
|
|
11447
|
-
function readDiagnosticsCount(value, label) {
|
|
11448
|
-
if (value === void 0) return 0;
|
|
11449
|
-
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
11450
|
-
return value.length;
|
|
11451
|
-
}
|
|
11452
|
-
function readResult(value, index) {
|
|
11453
|
-
if (!isObject(value)) throw new Error(`manifest.results[${index}] must be an object.`);
|
|
11454
|
-
const file = readOptionalString(value.file);
|
|
11455
|
-
const tracePath = readOptionalString(value.tracePath);
|
|
11456
|
-
return {
|
|
11457
|
-
testId: readString(value.testId, `manifest.results[${index}].testId`),
|
|
11458
|
-
name: readString(value.name, `manifest.results[${index}].name`),
|
|
11459
|
-
...file === void 0 ? {} : { file },
|
|
11460
|
-
status: readStatus(value.status),
|
|
11461
|
-
...tracePath === void 0 ? {} : { tracePath },
|
|
11462
|
-
artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
|
|
11463
|
-
diagnostics: readDiagnosticsCount(
|
|
11464
|
-
value.diagnostics,
|
|
11465
|
-
`manifest.results[${index}].diagnostics`
|
|
11466
|
-
)
|
|
11427
|
+
enabled: value.enabled,
|
|
11428
|
+
type: value.type,
|
|
11429
|
+
...value.path !== void 0 ? { path: value.path } : {}
|
|
11467
11430
|
};
|
|
11468
11431
|
}
|
|
11469
|
-
function
|
|
11470
|
-
|
|
11471
|
-
const
|
|
11472
|
-
if (!
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
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 };
|
|
11437
|
+
}
|
|
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
|
+
);
|
|
11444
|
+
}
|
|
11445
|
+
if (typeof input3.project !== "string" || input3.project.trim() === "") {
|
|
11446
|
+
errors.push("project must be a non-empty string");
|
|
11447
|
+
}
|
|
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");
|
|
11452
|
+
}
|
|
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
|
+
});
|
|
11465
|
+
}
|
|
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 };
|
|
11479
11481
|
}
|
|
11480
|
-
const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
|
|
11481
|
-
const results = candidate.results.map((item, index) => readResult(item, index));
|
|
11482
|
-
const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
|
|
11483
11482
|
const manifest = {
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
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
|
|
11493
11493
|
};
|
|
11494
|
+
return { ok: true, manifest, errors, warnings };
|
|
11494
11495
|
}
|
|
11495
|
-
function
|
|
11496
|
-
const
|
|
11497
|
-
if (
|
|
11498
|
-
return
|
|
11496
|
+
function parseWorkspaceManifest(json) {
|
|
11497
|
+
const warnings = [];
|
|
11498
|
+
if (typeof json !== "string") {
|
|
11499
|
+
return { ok: false, errors: ["manifest input must be a string"], warnings };
|
|
11499
11500
|
}
|
|
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);
|
|
11501
11517
|
}
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
const document = readManifestDocument(JSON.parse(raw));
|
|
11506
|
-
const manifest = document.manifest;
|
|
11507
|
-
const results = manifest.results.map((result) => ({
|
|
11508
|
-
testId: safeText(result.testId),
|
|
11509
|
-
name: safeText(result.name),
|
|
11510
|
-
...result.file === void 0 ? {} : { file: safeText(path11.basename(result.file)) },
|
|
11511
|
-
status: result.status,
|
|
11512
|
-
...result.tracePath === void 0 ? {} : { tracePath: safeText(path11.basename(result.tracePath)) },
|
|
11513
|
-
artifacts: result.artifacts,
|
|
11514
|
-
diagnostics: result.diagnostics
|
|
11515
|
-
}));
|
|
11516
|
-
return {
|
|
11517
|
-
...document.packageName === void 0 ? {} : { packageName: document.packageName },
|
|
11518
|
-
manifestFile: cwdRelative(absolute),
|
|
11519
|
-
framework: manifest.framework,
|
|
11520
|
-
generatedAt: manifest.generatedAt,
|
|
11521
|
-
results,
|
|
11522
|
-
artifacts: manifest.artifacts,
|
|
11523
|
-
diagnostics: document.diagnostics
|
|
11524
|
-
};
|
|
11518
|
+
function serializeWorkspaceManifest(manifest) {
|
|
11519
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
11520
|
+
`;
|
|
11525
11521
|
}
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
failed: 0,
|
|
11531
|
-
passed: 0,
|
|
11532
|
-
skipped: 0,
|
|
11533
|
-
todo: 0,
|
|
11534
|
-
artifacts: 0,
|
|
11535
|
-
diagnostics: 0
|
|
11536
|
-
};
|
|
11537
|
-
for (const manifest of manifests) {
|
|
11538
|
-
summary.artifacts += manifest.artifacts.length;
|
|
11539
|
-
summary.diagnostics += manifest.diagnostics;
|
|
11540
|
-
for (const result of manifest.results) {
|
|
11541
|
-
summary.tests += 1;
|
|
11542
|
-
if (result.status === "failed") summary.failed += 1;
|
|
11543
|
-
else if (result.status === "passed") summary.passed += 1;
|
|
11544
|
-
else if (result.status === "skipped") summary.skipped += 1;
|
|
11545
|
-
else summary.todo += 1;
|
|
11546
|
-
summary.artifacts += result.artifacts.length;
|
|
11547
|
-
summary.diagnostics += result.diagnostics;
|
|
11548
|
-
}
|
|
11549
|
-
}
|
|
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);
|
|
11550
11526
|
return {
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
note: NOTE2
|
|
11527
|
+
projectRoot,
|
|
11528
|
+
workspaceDir,
|
|
11529
|
+
manifestPath: path13.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
|
|
11555
11530
|
};
|
|
11556
11531
|
}
|
|
11557
|
-
function
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
"",
|
|
11564
|
-
NOTE2,
|
|
11565
|
-
"",
|
|
11566
|
-
"| Field | Value |",
|
|
11567
|
-
"| --- | --- |",
|
|
11568
|
-
`| Status | ${result.status} |`,
|
|
11569
|
-
`| Manifests | ${result.summary.manifests} |`,
|
|
11570
|
-
`| Tests | ${result.summary.tests} |`,
|
|
11571
|
-
`| Failed | ${result.summary.failed} |`,
|
|
11572
|
-
`| Passed | ${result.summary.passed} |`,
|
|
11573
|
-
`| Skipped | ${result.summary.skipped} |`,
|
|
11574
|
-
`| Todo | ${result.summary.todo} |`,
|
|
11575
|
-
`| Artifacts | ${result.summary.artifacts} |`,
|
|
11576
|
-
`| Diagnostics | ${result.summary.diagnostics} |`,
|
|
11577
|
-
"",
|
|
11578
|
-
"## Tests",
|
|
11579
|
-
"",
|
|
11580
|
-
"| Framework | Status | Test | File | Trace | Artifacts |",
|
|
11581
|
-
"| --- | --- | --- | --- | --- | --- |"
|
|
11582
|
-
];
|
|
11583
|
-
const rows = result.manifests.flatMap(
|
|
11584
|
-
(manifest) => manifest.results.map((test) => ({
|
|
11585
|
-
framework: manifest.framework,
|
|
11586
|
-
test
|
|
11587
|
-
}))
|
|
11588
|
-
);
|
|
11589
|
-
if (rows.length === 0) {
|
|
11590
|
-
lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
|
|
11591
|
-
} else {
|
|
11592
|
-
for (const row of rows) {
|
|
11593
|
-
lines.push(
|
|
11594
|
-
`| ${markdownCell(row.framework)} | ${markdownCell(row.test.status)} | ${markdownCell(row.test.name)} | ${markdownCell(row.test.file)} | ${markdownCell(row.test.tracePath)} | ${row.test.artifacts.length} |`
|
|
11595
|
-
);
|
|
11596
|
-
}
|
|
11597
|
-
}
|
|
11598
|
-
lines.push("", "## Manifests", "", "| Framework | File | Generated | Artifacts |", "| --- | --- | --- | --- |");
|
|
11599
|
-
for (const manifest of result.manifests) {
|
|
11600
|
-
lines.push(
|
|
11601
|
-
`| ${markdownCell(manifest.framework)} | ${markdownCell(manifest.manifestFile)} | ${markdownCell(manifest.generatedAt)} | ${manifest.artifacts.length} |`
|
|
11602
|
-
);
|
|
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;
|
|
11603
11538
|
}
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
(manifest) => manifest.artifacts.map((artifact) => ({ framework: manifest.framework, artifact }))
|
|
11539
|
+
throw new Error(
|
|
11540
|
+
`Workspace path "${relative}" resolves outside the workspace directory`
|
|
11607
11541
|
);
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
}
|
|
11542
|
+
}
|
|
11543
|
+
async function pathExists(p) {
|
|
11544
|
+
try {
|
|
11545
|
+
await access(p);
|
|
11546
|
+
return true;
|
|
11547
|
+
} catch {
|
|
11548
|
+
return false;
|
|
11616
11549
|
}
|
|
11617
|
-
lines.push("");
|
|
11618
|
-
return `${lines.join("\n")}
|
|
11619
|
-
`;
|
|
11620
11550
|
}
|
|
11621
|
-
function
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
return `${redacted.slice(0, MAX_TEXT - 12)}...[truncated]`;
|
|
11551
|
+
async function isWritable(p) {
|
|
11552
|
+
try {
|
|
11553
|
+
await access(p, constants$1.W_OK);
|
|
11554
|
+
return true;
|
|
11555
|
+
} catch {
|
|
11556
|
+
return false;
|
|
11557
|
+
}
|
|
11629
11558
|
}
|
|
11630
|
-
async function
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
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 [];
|
|
11635
11565
|
}
|
|
11636
|
-
|
|
11566
|
+
}
|
|
11567
|
+
async function countFiles(dir) {
|
|
11637
11568
|
try {
|
|
11638
|
-
const
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
manifests.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
|
|
11643
|
-
result = summarize3(manifests);
|
|
11644
|
-
} catch (error) {
|
|
11645
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
11646
|
-
console.error(`[AgentInspect] ci-summary failed: ${message}`);
|
|
11647
|
-
process.exitCode = 1;
|
|
11648
|
-
return;
|
|
11569
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
11570
|
+
return entries.filter((e) => e.isFile()).length;
|
|
11571
|
+
} catch {
|
|
11572
|
+
return 0;
|
|
11649
11573
|
}
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
await
|
|
11655
|
-
}
|
|
11656
|
-
|
|
11657
|
-
if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
|
|
11658
|
-
const summaryPath = path11.resolve(summaryTarget);
|
|
11659
|
-
await mkdir(path11.dirname(summaryPath), { recursive: true });
|
|
11660
|
-
await appendFile(summaryPath, `
|
|
11661
|
-
${markdown}`, "utf-8");
|
|
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: [] };
|
|
11662
11581
|
}
|
|
11663
|
-
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
11667
|
-
|
|
11582
|
+
const parsed = parseWorkspaceManifest(raw);
|
|
11583
|
+
return {
|
|
11584
|
+
exists: true,
|
|
11585
|
+
ok: parsed.ok,
|
|
11586
|
+
...parsed.manifest ? { manifest: parsed.manifest } : {},
|
|
11587
|
+
errors: parsed.errors,
|
|
11588
|
+
warnings: parsed.warnings
|
|
11589
|
+
};
|
|
11590
|
+
}
|
|
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;
|
|
11668
11604
|
} else {
|
|
11669
|
-
|
|
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;
|
|
11670
11614
|
}
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
|
|
11678
|
-
|
|
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 });
|
|
11679
11629
|
}
|
|
11680
|
-
|
|
11681
|
-
|
|
11682
|
-
|
|
11683
|
-
}
|
|
11684
|
-
|
|
11685
|
-
|
|
11686
|
-
|
|
11687
|
-
|
|
11688
|
-
|
|
11689
|
-
|
|
11690
|
-
|
|
11691
|
-
|
|
11692
|
-
|
|
11693
|
-
};
|
|
11694
|
-
`;
|
|
11695
|
-
if (framework === "custom") return base;
|
|
11696
|
-
return `${base}
|
|
11697
|
-
export const framework = "${framework}" as const;
|
|
11698
|
-
`;
|
|
11630
|
+
if (!dryRun && created) {
|
|
11631
|
+
await mkdir(location.workspaceDir, { recursive: true });
|
|
11632
|
+
await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
|
|
11633
|
+
}
|
|
11634
|
+
return {
|
|
11635
|
+
location,
|
|
11636
|
+
manifest,
|
|
11637
|
+
created,
|
|
11638
|
+
adopted,
|
|
11639
|
+
createdDirs,
|
|
11640
|
+
detectedExistingTraces,
|
|
11641
|
+
dryRun
|
|
11642
|
+
};
|
|
11699
11643
|
}
|
|
11700
|
-
function
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
await inspectRun("ai-sdk-demo", async () => {
|
|
11711
|
-
await step.tool("mock-generate", async () => ({ text: "ok" }));
|
|
11712
|
-
}, { traceDir: ".agent-inspect", silent: true });
|
|
11713
|
-
console.log("Trace written to .agent-inspect/");
|
|
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;
|
|
11714
11654
|
}
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
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;
|
|
11660
|
+
}
|
|
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)
|
|
11685
|
+
}
|
|
11686
|
+
};
|
|
11733
11687
|
}
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
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 };
|
|
11698
|
+
}
|
|
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 };
|
|
11706
|
+
}
|
|
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 });
|
|
11711
|
+
}
|
|
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 };
|
|
11752
11770
|
}
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
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
|
+
}
|
|
11765
11795
|
}
|
|
11796
|
+
return { dryRun, removed };
|
|
11766
11797
|
}
|
|
11767
11798
|
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
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
|
+
);
|
|
11808
|
+
}
|
|
11809
|
+
function writeJson4(value) {
|
|
11810
|
+
return `${JSON.stringify(stable6(value), null, 2)}
|
|
11775
11811
|
`;
|
|
11812
|
+
}
|
|
11813
|
+
function parseBundleProfile(value) {
|
|
11814
|
+
if (value === void 0 || value === "local" || value === "share" || value === "strict") {
|
|
11815
|
+
return value ?? "share";
|
|
11776
11816
|
}
|
|
11817
|
+
throw new Error(`Unsupported --profile "${value}". Use local, share, or strict.`);
|
|
11777
11818
|
}
|
|
11778
|
-
function
|
|
11779
|
-
return
|
|
11780
|
-
|
|
11781
|
-
on:
|
|
11782
|
-
workflow_dispatch:
|
|
11783
|
-
push:
|
|
11784
|
-
branches: [main]
|
|
11785
|
-
|
|
11786
|
-
jobs:
|
|
11787
|
-
trace-artifacts:
|
|
11788
|
-
runs-on: ubuntu-latest
|
|
11789
|
-
steps:
|
|
11790
|
-
- uses: actions/checkout@v4
|
|
11791
|
-
- uses: actions/setup-node@v4
|
|
11792
|
-
with:
|
|
11793
|
-
node-version: "22"
|
|
11794
|
-
- run: npm ci
|
|
11795
|
-
- run: npm test
|
|
11796
|
-
- name: Upload AgentInspect traces
|
|
11797
|
-
if: always()
|
|
11798
|
-
uses: actions/upload-artifact@v4
|
|
11799
|
-
with:
|
|
11800
|
-
name: agent-inspect-traces
|
|
11801
|
-
path: .agent-inspect/**/*.jsonl
|
|
11802
|
-
if-no-files-found: ignore
|
|
11803
|
-
`;
|
|
11819
|
+
function toReportProfile(profile) {
|
|
11820
|
+
return profile;
|
|
11804
11821
|
}
|
|
11805
|
-
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
{ rel: GITKEEP, content: "" },
|
|
11812
|
-
{ rel: demoPath, content: demoTemplate(framework) }
|
|
11813
|
-
];
|
|
11814
|
-
if (options.ci === "github") {
|
|
11815
|
-
candidates.push({
|
|
11816
|
-
rel: ".github/workflows/agent-inspect-artifacts.yml",
|
|
11817
|
-
content: githubWorkflowTemplate()
|
|
11818
|
-
});
|
|
11822
|
+
function escapeHtml3(value) {
|
|
11823
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
11824
|
+
}
|
|
11825
|
+
function safetyStatusFromAssess(status) {
|
|
11826
|
+
if (status === "SAFE" || status === "SAFE WITH WARNINGS" || status === "UNSAFE" || status === "UNKNOWN") {
|
|
11827
|
+
return status;
|
|
11819
11828
|
}
|
|
11820
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
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);
|
|
11823
11834
|
try {
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
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 {
|
|
11833
11844
|
}
|
|
11845
|
+
return normalized;
|
|
11834
11846
|
}
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
written.push(entry.path);
|
|
11846
|
-
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
|
+
);
|
|
11847
11857
|
}
|
|
11848
|
-
|
|
11849
|
-
const content = entry.path === CONFIG_FILE ? configTemplate(plan.framework) : entry.path === GITKEEP ? "" : entry.path.endsWith(".yml") ? githubWorkflowTemplate() : demoTemplate(plan.framework);
|
|
11850
|
-
await writeFile(abs, content, "utf-8");
|
|
11851
|
-
written.push(entry.path);
|
|
11858
|
+
} catch {
|
|
11852
11859
|
}
|
|
11853
|
-
return
|
|
11860
|
+
return defaultBundleOutputPath(runIds);
|
|
11854
11861
|
}
|
|
11855
|
-
async function
|
|
11856
|
-
const
|
|
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;
|
|
11857
11907
|
try {
|
|
11858
|
-
const
|
|
11859
|
-
const
|
|
11860
|
-
const
|
|
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);
|
|
11861
11921
|
if (options.json) {
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
framework: plan.framework,
|
|
11866
|
-
ci: plan.ci ?? null,
|
|
11867
|
-
dryRun: options.dryRun === true,
|
|
11868
|
-
planned: plan.files,
|
|
11869
|
-
wouldWrite: options.dryRun ? toWrite : void 0
|
|
11870
|
-
};
|
|
11871
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
11872
|
-
if (options.dryRun) return;
|
|
11922
|
+
console.log(writeJson4({ ok: false, error: message }).trimEnd());
|
|
11923
|
+
} else {
|
|
11924
|
+
console.error(`[AgentInspect] bundle failed: ${message}`);
|
|
11873
11925
|
}
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
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}`);
|
|
11886
11949
|
}
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
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}`);
|
|
11891
11965
|
}
|
|
11892
|
-
|
|
11893
|
-
|
|
11966
|
+
process.exitCode = 1;
|
|
11967
|
+
return;
|
|
11894
11968
|
}
|
|
11895
|
-
|
|
11896
|
-
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)) {
|
|
11897
12014
|
if (options.json) {
|
|
11898
|
-
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
|
+
);
|
|
11899
12022
|
} else {
|
|
11900
|
-
console.error(
|
|
12023
|
+
console.error(
|
|
12024
|
+
`[AgentInspect] bundle refused: safety status is ${checks2.aggregateStatus}. Pass --allow-unsafe to override.`
|
|
12025
|
+
);
|
|
11901
12026
|
}
|
|
11902
12027
|
process.exitCode = 1;
|
|
12028
|
+
return;
|
|
12029
|
+
}
|
|
12030
|
+
const redactionReport = {
|
|
12031
|
+
profile,
|
|
12032
|
+
totalFindings: redactionRuns.reduce((sum, run) => sum + run.findings, 0),
|
|
12033
|
+
runs: redactionRuns
|
|
12034
|
+
};
|
|
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
|
+
);
|
|
12046
|
+
}
|
|
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;
|
|
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}`);
|
|
12107
|
+
}
|
|
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
|
+
`;
|
|
12122
|
+
}
|
|
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."
|
|
12132
|
+
});
|
|
12133
|
+
return { ok: false, outputDir, diagnostics };
|
|
12134
|
+
}
|
|
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
|
|
12141
|
+
});
|
|
12142
|
+
return { ok: false, outputDir, diagnostics };
|
|
11903
12143
|
}
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11909
|
-
|
|
11910
|
-
};
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
|
|
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
|
|
12150
|
+
});
|
|
12151
|
+
return { ok: false, outputDir, diagnostics };
|
|
12152
|
+
}
|
|
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 };
|
|
12163
|
+
}
|
|
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 };
|
|
11921
12174
|
}
|
|
11922
12175
|
return {
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
12176
|
+
ok: true,
|
|
12177
|
+
outputDir,
|
|
12178
|
+
relativePath: normalized,
|
|
12179
|
+
absolutePath,
|
|
12180
|
+
diagnostics
|
|
11927
12181
|
};
|
|
11928
12182
|
}
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11937
|
-
|
|
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
|
+
);
|
|
12199
|
+
}
|
|
12200
|
+
function writeJson5(value) {
|
|
12201
|
+
return `${JSON.stringify(stable7(value), null, 2)}
|
|
12202
|
+
`;
|
|
12203
|
+
}
|
|
12204
|
+
function isObject(value) {
|
|
12205
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12206
|
+
}
|
|
12207
|
+
function readString(value, label) {
|
|
12208
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
12209
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
11938
12210
|
}
|
|
11939
|
-
return
|
|
11940
|
-
id: `env-${name.toLowerCase()}`,
|
|
11941
|
-
status: "pass",
|
|
11942
|
-
message: `${name} is set.`,
|
|
11943
|
-
evidence: value
|
|
11944
|
-
};
|
|
12211
|
+
return safeText(value);
|
|
11945
12212
|
}
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
status: "pass",
|
|
11954
|
-
message: `Trace directory is writable: ${resolved}`,
|
|
11955
|
-
evidence: resolved
|
|
11956
|
-
};
|
|
11957
|
-
} catch (error) {
|
|
11958
|
-
return {
|
|
11959
|
-
id: "trace-dir-writable",
|
|
11960
|
-
status: "fail",
|
|
11961
|
-
message: `Trace directory is not writable: ${resolved}`,
|
|
11962
|
-
remediation: "Create the directory or set AGENT_INSPECT_TRACE_DIR to a writable path.",
|
|
11963
|
-
evidence: error instanceof Error ? error.message : String(error)
|
|
11964
|
-
};
|
|
12213
|
+
function readOptionalString(value) {
|
|
12214
|
+
return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
|
|
12215
|
+
}
|
|
12216
|
+
function readFramework(value) {
|
|
12217
|
+
const framework = readString(value, "manifest.framework");
|
|
12218
|
+
if (!FRAMEWORKS.has(framework)) {
|
|
12219
|
+
throw new Error(`Unsupported reporter framework: ${framework}.`);
|
|
11965
12220
|
}
|
|
12221
|
+
return framework;
|
|
11966
12222
|
}
|
|
11967
|
-
function
|
|
11968
|
-
const
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
const pkg = require2(pkgPath);
|
|
11972
|
-
return { ok: true, version: pkg.version };
|
|
11973
|
-
} catch {
|
|
11974
|
-
return { ok: false };
|
|
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}.`);
|
|
11975
12227
|
}
|
|
12228
|
+
return status;
|
|
11976
12229
|
}
|
|
11977
|
-
function
|
|
11978
|
-
|
|
11979
|
-
const
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
id: "import-agent-inspect",
|
|
11990
|
-
status: "warn",
|
|
11991
|
-
message: "agent-inspect is not installed in the current project.",
|
|
11992
|
-
remediation: "Run npm install agent-inspect (or pnpm add agent-inspect)."
|
|
11993
|
-
});
|
|
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}.`);
|
|
11994
12242
|
}
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
});
|
|
12002
|
-
} catch {
|
|
12003
|
-
results.push({
|
|
12004
|
-
id: "import-agent-inspect-cjs",
|
|
12005
|
-
status: "skipped",
|
|
12006
|
-
message: "CJS resolution check skipped (package not installed locally)."
|
|
12007
|
-
});
|
|
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}.`);
|
|
12008
12250
|
}
|
|
12009
|
-
return
|
|
12251
|
+
return {
|
|
12252
|
+
kind,
|
|
12253
|
+
path: pathCheck.relativePath,
|
|
12254
|
+
format,
|
|
12255
|
+
redactionProfile
|
|
12256
|
+
};
|
|
12010
12257
|
}
|
|
12011
|
-
function
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
return
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
}
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
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
|
+
};
|
|
12036
12310
|
}
|
|
12037
|
-
function
|
|
12038
|
-
const
|
|
12039
|
-
if (
|
|
12040
|
-
return
|
|
12041
|
-
id: "version-alignment",
|
|
12042
|
-
status: "skipped",
|
|
12043
|
-
message: "Skipped version alignment \u2014 agent-inspect not installed locally."
|
|
12044
|
-
};
|
|
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);
|
|
12045
12315
|
}
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
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;
|
|
12364
|
+
}
|
|
12054
12365
|
}
|
|
12055
12366
|
return {
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12367
|
+
status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
|
|
12368
|
+
manifests,
|
|
12369
|
+
summary,
|
|
12370
|
+
note: NOTE2
|
|
12059
12371
|
};
|
|
12060
12372
|
}
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
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
|
+
"| --- | --- | --- | --- | --- | --- |"
|
|
12076
12398
|
];
|
|
12077
|
-
|
|
12078
|
-
|
|
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
|
+
);
|
|
12412
|
+
}
|
|
12079
12413
|
}
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
const checks2 = await runDoctorChecks(options);
|
|
12085
|
-
const failed = checks2.filter((check) => check.status === "fail").length;
|
|
12086
|
-
const warned = checks2.filter((check) => check.status === "warn").length;
|
|
12087
|
-
if (options.json) {
|
|
12088
|
-
console.log(
|
|
12089
|
-
JSON.stringify(
|
|
12090
|
-
{
|
|
12091
|
-
ok: failed === 0,
|
|
12092
|
-
version,
|
|
12093
|
-
summary: { pass: checks2.filter((c) => c.status === "pass").length, warn: warned, fail: failed },
|
|
12094
|
-
checks: checks2
|
|
12095
|
-
},
|
|
12096
|
-
null,
|
|
12097
|
-
2
|
|
12098
|
-
)
|
|
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} |`
|
|
12099
12418
|
);
|
|
12100
|
-
|
|
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
|
+
);
|
|
12431
|
+
}
|
|
12432
|
+
}
|
|
12433
|
+
lines.push("");
|
|
12434
|
+
return `${lines.join("\n")}
|
|
12435
|
+
`;
|
|
12436
|
+
}
|
|
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;
|
|
12101
12450
|
return;
|
|
12102
12451
|
}
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
const
|
|
12106
|
-
|
|
12107
|
-
|
|
12452
|
+
let result;
|
|
12453
|
+
try {
|
|
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;
|
|
12108
12465
|
}
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
if (
|
|
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
|
+
`;
|
|
12112
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";
|
|
12113
12524
|
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
async function indexIsStale(snapshot, traceDir) {
|
|
12120
|
-
const builtMs = Date.parse(snapshot.builtAt);
|
|
12121
|
-
if (Number.isNaN(builtMs)) return true;
|
|
12122
|
-
const td = new TraceDirectory({ dir: traceDir });
|
|
12123
|
-
const files = await td.list();
|
|
12124
|
-
for (const file of files) {
|
|
12125
|
-
const stats = await td.getFileStats(file);
|
|
12126
|
-
if (stats.mtimeMs > builtMs) return true;
|
|
12127
|
-
}
|
|
12128
|
-
return false;
|
|
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/");
|
|
12129
12530
|
}
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12135
|
-
|
|
12136
|
-
|
|
12137
|
-
|
|
12138
|
-
|
|
12139
|
-
|
|
12140
|
-
|
|
12141
|
-
|
|
12142
|
-
|
|
12143
|
-
|
|
12144
|
-
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
);
|
|
12149
|
-
const entries = metas.map((meta) => ({
|
|
12150
|
-
runId: meta.runId,
|
|
12151
|
-
path: meta.filePath,
|
|
12152
|
-
name: meta.name,
|
|
12153
|
-
startedAt: meta.startedAt,
|
|
12154
|
-
status: meta.status
|
|
12155
|
-
})).sort((a, b) => a.runId.localeCompare(b.runId));
|
|
12156
|
-
if (entries.length < slice.length) {
|
|
12157
|
-
warnings.push(
|
|
12158
|
-
`indexer.partial: indexed ${entries.length} of ${slice.length} trace files`
|
|
12159
|
-
);
|
|
12160
|
-
}
|
|
12161
|
-
return {
|
|
12162
|
-
traceDir,
|
|
12163
|
-
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12164
|
-
entries,
|
|
12165
|
-
warnings
|
|
12166
|
-
};
|
|
12167
|
-
}
|
|
12168
|
-
});
|
|
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/");
|
|
12169
12549
|
}
|
|
12170
12550
|
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
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/");
|
|
12175
12568
|
}
|
|
12176
|
-
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
|
|
12180
|
-
|
|
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}\` };
|
|
12181
12581
|
}
|
|
12182
|
-
return parsed;
|
|
12183
12582
|
}
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
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
|
+
`;
|
|
12190
12592
|
}
|
|
12191
12593
|
}
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
}
|
|
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
|
+
`;
|
|
12219
12620
|
}
|
|
12220
|
-
async function
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
12230
|
-
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
}
|
|
12235
|
-
const stale = await indexIsStale(snapshot, traceDir);
|
|
12236
|
-
const payload = {
|
|
12237
|
-
ok: true,
|
|
12238
|
-
exists: true,
|
|
12239
|
-
indexPath: indexPath2,
|
|
12240
|
-
traceDir,
|
|
12241
|
-
stale,
|
|
12242
|
-
builtAt: snapshot.builtAt,
|
|
12243
|
-
entries: snapshot.entries.length,
|
|
12244
|
-
warnings: snapshot.warnings
|
|
12245
|
-
};
|
|
12246
|
-
if (options.json) {
|
|
12247
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
12248
|
-
return;
|
|
12249
|
-
}
|
|
12250
|
-
console.log(`Index: ${indexPath2}`);
|
|
12251
|
-
console.log(`Built: ${snapshot.builtAt}`);
|
|
12252
|
-
console.log(`Entries: ${snapshot.entries.length}`);
|
|
12253
|
-
console.log(`Stale: ${stale ? "yes" : "no"}`);
|
|
12254
|
-
if (snapshot.warnings.length > 0) {
|
|
12255
|
-
for (const warning of snapshot.warnings) {
|
|
12256
|
-
console.log(`warning: ${warning}`);
|
|
12257
|
-
}
|
|
12258
|
-
}
|
|
12259
|
-
} catch (e) {
|
|
12260
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
12261
|
-
console.error(`[AgentInspect] index status failed: ${msg}`);
|
|
12262
|
-
process.exitCode = 1;
|
|
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
|
+
});
|
|
12263
12635
|
}
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
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" });
|
|
12273
12649
|
}
|
|
12274
|
-
console.log(`Removed index: ${indexPath2}`);
|
|
12275
|
-
} catch (e) {
|
|
12276
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
12277
|
-
console.error(`[AgentInspect] index clean failed: ${msg}`);
|
|
12278
|
-
process.exitCode = 1;
|
|
12279
12650
|
}
|
|
12651
|
+
return { framework, ...options.ci ? { ci: options.ci } : {}, files };
|
|
12280
12652
|
}
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
try {
|
|
12287
|
-
return await import('./src-VQT7QLGV.mjs');
|
|
12288
|
-
} catch (e) {
|
|
12289
|
-
if (isModuleNotFound3(e)) {
|
|
12290
|
-
console.error(
|
|
12291
|
-
`The optional SQLite index is not installed. Run: npm install ${PACKAGE}`
|
|
12292
|
-
);
|
|
12293
|
-
process.exitCode = 1;
|
|
12294
|
-
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;
|
|
12295
12658
|
}
|
|
12296
|
-
const
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
}
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
12306
|
-
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);
|
|
12307
12668
|
}
|
|
12308
|
-
return
|
|
12669
|
+
return written;
|
|
12309
12670
|
}
|
|
12310
|
-
async function
|
|
12311
|
-
|
|
12671
|
+
async function initCommand(options = {}) {
|
|
12672
|
+
const cwd = path13.resolve(options.cwd ?? process.cwd());
|
|
12312
12673
|
try {
|
|
12313
|
-
const
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
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"})`);
|
|
12320
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.");
|
|
12321
12710
|
}
|
|
12322
|
-
} catch {
|
|
12323
|
-
|
|
12324
|
-
return newest;
|
|
12325
|
-
}
|
|
12326
|
-
async function indexSqliteBuildCommand(options = {}) {
|
|
12327
|
-
const mod = await loadIndexSqlite();
|
|
12328
|
-
if (!mod) return;
|
|
12329
|
-
const result = await mod.buildIndex({
|
|
12330
|
-
traceDir: options.dir,
|
|
12331
|
-
maxRuns: parsePositiveInt(options.maxRuns, "--max-runs")
|
|
12332
|
-
});
|
|
12333
|
-
if (options.json) {
|
|
12334
|
-
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
12335
|
-
return;
|
|
12336
|
-
}
|
|
12337
|
-
console.log(`Built SQLite index: ${result.dbPath}`);
|
|
12338
|
-
console.log(`Runs: ${result.runs} Steps: ${result.steps} Errors: ${result.errors}`);
|
|
12339
|
-
for (const warning of result.warnings) console.log(`warning: ${warning}`);
|
|
12340
|
-
}
|
|
12341
|
-
async function indexSqliteStatusCommand(options = {}) {
|
|
12342
|
-
const mod = await loadIndexSqlite();
|
|
12343
|
-
if (!mod) return;
|
|
12344
|
-
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
12345
|
-
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
12346
|
-
const status = mod.indexStatus(dbPath);
|
|
12347
|
-
const stale = mod.isIndexStale(dbPath, await newestTraceMtimeMs2(traceDir));
|
|
12348
|
-
if (options.json) {
|
|
12349
|
-
console.log(JSON.stringify({ ok: true, traceDir, stale, ...status }, null, 2));
|
|
12350
|
-
return;
|
|
12351
|
-
}
|
|
12352
|
-
if (!status.exists) {
|
|
12353
|
-
console.log(`No SQLite index at ${dbPath}`);
|
|
12354
|
-
console.log("Run: agent-inspect index sqlite build");
|
|
12355
|
-
return;
|
|
12356
|
-
}
|
|
12357
|
-
console.log(`Index: ${status.dbPath}`);
|
|
12358
|
-
console.log(`Healthy: ${status.healthy ? "yes" : "no"}`);
|
|
12359
|
-
console.log(`Built: ${status.builtAt ?? "unknown"}`);
|
|
12360
|
-
console.log(`Runs: ${status.runs} Steps: ${status.steps}`);
|
|
12361
|
-
console.log(`Stale: ${stale ? "yes" : "no"}`);
|
|
12362
|
-
}
|
|
12363
|
-
async function indexSqliteQueryCommand(options = {}) {
|
|
12364
|
-
const mod = await loadIndexSqlite();
|
|
12365
|
-
if (!mod) return;
|
|
12366
|
-
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
12367
|
-
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
12368
|
-
const status = mod.indexStatus(dbPath);
|
|
12369
|
-
if (!status.exists || !status.healthy) {
|
|
12711
|
+
} catch (error) {
|
|
12712
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
12370
12713
|
if (options.json) {
|
|
12371
|
-
console.log(JSON.stringify({ ok: false,
|
|
12714
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
12372
12715
|
} else {
|
|
12373
|
-
console.
|
|
12716
|
+
console.error(`[AgentInspect] init failed: ${msg}`);
|
|
12374
12717
|
}
|
|
12375
12718
|
process.exitCode = 1;
|
|
12376
|
-
return;
|
|
12377
|
-
}
|
|
12378
|
-
const rows = mod.queryRuns(dbPath, {
|
|
12379
|
-
status: options.status,
|
|
12380
|
-
sessionId: options.session,
|
|
12381
|
-
name: options.name,
|
|
12382
|
-
kind: options.kind,
|
|
12383
|
-
tool: options.tool,
|
|
12384
|
-
limit: parsePositiveInt(options.limit, "--limit")
|
|
12385
|
-
});
|
|
12386
|
-
if (options.json) {
|
|
12387
|
-
console.log(JSON.stringify({ ok: true, count: rows.length, runs: rows }, null, 2));
|
|
12388
|
-
return;
|
|
12389
|
-
}
|
|
12390
|
-
if (rows.length === 0) {
|
|
12391
|
-
console.log("No matching runs.");
|
|
12392
|
-
return;
|
|
12393
|
-
}
|
|
12394
|
-
for (const run of rows) {
|
|
12395
|
-
const parts = [
|
|
12396
|
-
run.runId,
|
|
12397
|
-
run.status ?? "unknown",
|
|
12398
|
-
run.name ?? "",
|
|
12399
|
-
run.durationMs != null ? `${run.durationMs}ms` : ""
|
|
12400
|
-
].filter((p) => p !== "");
|
|
12401
|
-
console.log(parts.join(" "));
|
|
12402
|
-
}
|
|
12403
|
-
}
|
|
12404
|
-
async function indexSqliteCleanCommand(options = {}) {
|
|
12405
|
-
const mod = await loadIndexSqlite();
|
|
12406
|
-
if (!mod) return;
|
|
12407
|
-
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
12408
|
-
const dbPath = mod.resolveIndexDbPath(traceDir);
|
|
12409
|
-
await mod.cleanIndex(dbPath);
|
|
12410
|
-
if (options.json) {
|
|
12411
|
-
console.log(JSON.stringify({ ok: true, removed: dbPath }, null, 2));
|
|
12412
|
-
return;
|
|
12413
12719
|
}
|
|
12414
|
-
console.log(`Removed SQLite index: ${dbPath}`);
|
|
12415
12720
|
}
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
// packages/core/src/workspace/manifest.ts
|
|
12423
|
-
var DEFAULT_WORKSPACE_LAYOUT = {
|
|
12424
|
-
traceDirs: ["runs"],
|
|
12425
|
-
reportsDir: "reports",
|
|
12426
|
-
artifactsDir: "artifacts",
|
|
12427
|
-
bundlesDir: "bundles",
|
|
12428
|
-
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"]
|
|
12429
12726
|
};
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
const index = {
|
|
12441
|
-
enabled: options.index?.enabled ?? false,
|
|
12442
|
-
type: options.index?.type ?? "none",
|
|
12443
|
-
...options.index?.path !== void 0 ? { path: options.index.path } : {}
|
|
12444
|
-
};
|
|
12445
|
-
return {
|
|
12446
|
-
schemaVersion: WORKSPACE_SCHEMA_VERSION,
|
|
12447
|
-
project,
|
|
12448
|
-
createdAt: options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
12449
|
-
traceDirs: options.traceDirs ? [...options.traceDirs] : [...DEFAULT_WORKSPACE_LAYOUT.traceDirs],
|
|
12450
|
-
reportsDir: options.reportsDir ?? DEFAULT_WORKSPACE_LAYOUT.reportsDir,
|
|
12451
|
-
artifactsDir: options.artifactsDir ?? DEFAULT_WORKSPACE_LAYOUT.artifactsDir,
|
|
12452
|
-
bundlesDir: options.bundlesDir ?? DEFAULT_WORKSPACE_LAYOUT.bundlesDir,
|
|
12453
|
-
notesDir: options.notesDir ?? DEFAULT_WORKSPACE_LAYOUT.notesDir,
|
|
12454
|
-
redactionProfile: options.redactionProfile ?? DEFAULT_REDACTION_PROFILE,
|
|
12455
|
-
index
|
|
12456
|
-
};
|
|
12457
|
-
}
|
|
12458
|
-
function isPlainObject(value) {
|
|
12459
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12460
|
-
}
|
|
12461
|
-
function isSafeRelativeWorkspacePath(p) {
|
|
12462
|
-
if (typeof p !== "string") return false;
|
|
12463
|
-
const trimmed = p.trim();
|
|
12464
|
-
if (trimmed === "") return false;
|
|
12465
|
-
if (trimmed.startsWith("/") || trimmed.startsWith("\\")) return false;
|
|
12466
|
-
if (/^[a-zA-Z]:/.test(trimmed)) return false;
|
|
12467
|
-
const segments = trimmed.split(/[/\\]+/);
|
|
12468
|
-
return !segments.some((seg) => seg === "..");
|
|
12469
|
-
}
|
|
12470
|
-
function validateDirField(value, field, errors) {
|
|
12471
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
12472
|
-
errors.push(`${field} must be a non-empty string`);
|
|
12473
|
-
return;
|
|
12474
|
-
}
|
|
12475
|
-
if (!isSafeRelativeWorkspacePath(value)) {
|
|
12476
|
-
errors.push(
|
|
12477
|
-
`${field} must be a relative path inside the workspace (no absolute paths or ".." traversal)`
|
|
12478
|
-
);
|
|
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
|
+
};
|
|
12479
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
|
+
};
|
|
12480
12744
|
}
|
|
12481
|
-
function
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
return
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
errors.push(`index.type must be one of: ${INDEX_TYPES.join(", ")}`);
|
|
12491
|
-
}
|
|
12492
|
-
if (value.path !== void 0 && !isSafeRelativeWorkspacePath(value.path)) {
|
|
12493
|
-
errors.push(
|
|
12494
|
-
'index.path must be a relative path inside the workspace (no absolute paths or ".." traversal)'
|
|
12495
|
-
);
|
|
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
|
+
};
|
|
12496
12754
|
}
|
|
12497
|
-
if (errors.length > 0) return void 0;
|
|
12498
12755
|
return {
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12756
|
+
id: `env-${name.toLowerCase()}`,
|
|
12757
|
+
status: "pass",
|
|
12758
|
+
message: `${name} is set.`,
|
|
12759
|
+
evidence: value
|
|
12502
12760
|
};
|
|
12503
12761
|
}
|
|
12504
|
-
function
|
|
12505
|
-
const
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
`
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
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
|
+
};
|
|
12519
12781
|
}
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
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 };
|
|
12524
12791
|
}
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
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)."
|
|
12536
12809
|
});
|
|
12537
12810
|
}
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
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
|
+
});
|
|
12544
12824
|
}
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
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
|
+
};
|
|
12550
12861
|
}
|
|
12551
|
-
if (
|
|
12552
|
-
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
|
+
};
|
|
12553
12870
|
}
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
traceDirs: input3.traceDirs.map((d) => d.trim()),
|
|
12559
|
-
reportsDir: input3.reportsDir.trim(),
|
|
12560
|
-
artifactsDir: input3.artifactsDir.trim(),
|
|
12561
|
-
bundlesDir: input3.bundlesDir.trim(),
|
|
12562
|
-
notesDir: input3.notesDir.trim(),
|
|
12563
|
-
redactionProfile: input3.redactionProfile,
|
|
12564
|
-
index
|
|
12871
|
+
return {
|
|
12872
|
+
id: "version-alignment",
|
|
12873
|
+
status: "pass",
|
|
12874
|
+
message: `CLI and local agent-inspect are aligned at ${version}.`
|
|
12565
12875
|
};
|
|
12566
|
-
return { ok: true, manifest, errors, warnings };
|
|
12567
12876
|
}
|
|
12568
|
-
function
|
|
12569
|
-
const
|
|
12570
|
-
|
|
12571
|
-
|
|
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));
|
|
12572
12895
|
}
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
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;
|
|
12581
12918
|
}
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
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}`);
|
|
12587
12924
|
}
|
|
12588
|
-
|
|
12589
|
-
}
|
|
12590
|
-
|
|
12591
|
-
return `${JSON.stringify(manifest, null, 2)}
|
|
12592
|
-
`;
|
|
12925
|
+
console.log(`
|
|
12926
|
+
Summary: ${failed} failed, ${warned} warnings`);
|
|
12927
|
+
if (failed > 0) process2.exitCode = 1;
|
|
12593
12928
|
}
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12598
|
-
return
|
|
12599
|
-
projectRoot,
|
|
12600
|
-
workspaceDir,
|
|
12601
|
-
manifestPath: path11.join(workspaceDir, WORKSPACE_MANIFEST_FILENAME)
|
|
12602
|
-
};
|
|
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;
|
|
12603
12934
|
}
|
|
12604
|
-
function
|
|
12605
|
-
const
|
|
12606
|
-
|
|
12607
|
-
const
|
|
12608
|
-
|
|
12609
|
-
|
|
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;
|
|
12610
12943
|
}
|
|
12611
|
-
|
|
12612
|
-
`Workspace path "${relative}" resolves outside the workspace directory`
|
|
12613
|
-
);
|
|
12944
|
+
return false;
|
|
12614
12945
|
}
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
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
|
+
});
|
|
12622
12985
|
}
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
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.");
|
|
12629
12997
|
}
|
|
12998
|
+
return parsed;
|
|
12630
12999
|
}
|
|
12631
|
-
async function
|
|
13000
|
+
async function readSnapshot(indexPath2) {
|
|
12632
13001
|
try {
|
|
12633
|
-
const
|
|
12634
|
-
return
|
|
13002
|
+
const raw = await readFile(indexPath2, "utf8");
|
|
13003
|
+
return JSON.parse(raw);
|
|
12635
13004
|
} catch {
|
|
12636
|
-
return
|
|
13005
|
+
return void 0;
|
|
12637
13006
|
}
|
|
12638
13007
|
}
|
|
12639
|
-
async function
|
|
13008
|
+
async function indexBuildCommand(options = {}) {
|
|
12640
13009
|
try {
|
|
12641
|
-
const
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
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;
|
|
12645
13034
|
}
|
|
12646
13035
|
}
|
|
12647
|
-
async function
|
|
12648
|
-
let raw;
|
|
13036
|
+
async function indexStatusCommand(options = {}) {
|
|
12649
13037
|
try {
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
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;
|
|
12653
13079
|
}
|
|
12654
|
-
const parsed = parseWorkspaceManifest(raw);
|
|
12655
|
-
return {
|
|
12656
|
-
exists: true,
|
|
12657
|
-
ok: parsed.ok,
|
|
12658
|
-
...parsed.manifest ? { manifest: parsed.manifest } : {},
|
|
12659
|
-
errors: parsed.errors,
|
|
12660
|
-
warnings: parsed.warnings
|
|
12661
|
-
};
|
|
12662
13080
|
}
|
|
12663
|
-
async function
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
const traceDirs = detectedExistingTraces ? ["runs", "."] : ["runs"];
|
|
12679
|
-
manifest = createDefaultWorkspaceManifest({
|
|
12680
|
-
project,
|
|
12681
|
-
traceDirs,
|
|
12682
|
-
...options.redactionProfile ? { redactionProfile: options.redactionProfile } : {}
|
|
12683
|
-
});
|
|
12684
|
-
created = true;
|
|
12685
|
-
adopted = detectedExistingTraces || existing.exists && !existing.ok;
|
|
12686
|
-
}
|
|
12687
|
-
const relDirs = uniqueDirs([
|
|
12688
|
-
...manifest.traceDirs.filter((d) => d !== "."),
|
|
12689
|
-
manifest.reportsDir,
|
|
12690
|
-
manifest.artifactsDir,
|
|
12691
|
-
manifest.bundlesDir,
|
|
12692
|
-
manifest.notesDir,
|
|
12693
|
-
INDEX_DIR_NAME
|
|
12694
|
-
]);
|
|
12695
|
-
const createdDirs = [];
|
|
12696
|
-
for (const rel of relDirs) {
|
|
12697
|
-
const abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
12698
|
-
if (await pathExists(abs)) continue;
|
|
12699
|
-
createdDirs.push(rel);
|
|
12700
|
-
if (!dryRun) await mkdir(abs, { recursive: true });
|
|
12701
|
-
}
|
|
12702
|
-
if (!dryRun && created) {
|
|
12703
|
-
await mkdir(location.workspaceDir, { recursive: true });
|
|
12704
|
-
await writeFile(location.manifestPath, serializeWorkspaceManifest(manifest), "utf-8");
|
|
12705
|
-
}
|
|
12706
|
-
return {
|
|
12707
|
-
location,
|
|
12708
|
-
manifest,
|
|
12709
|
-
created,
|
|
12710
|
-
adopted,
|
|
12711
|
-
createdDirs,
|
|
12712
|
-
detectedExistingTraces,
|
|
12713
|
-
dryRun
|
|
12714
|
-
};
|
|
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;
|
|
13095
|
+
}
|
|
12715
13096
|
}
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
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;
|
|
12724
13116
|
}
|
|
12725
|
-
return out;
|
|
12726
13117
|
}
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
|
|
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.`);
|
|
12732
13123
|
}
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
return {
|
|
12747
|
-
project: manifest.project,
|
|
12748
|
-
traceFiles,
|
|
12749
|
-
reports,
|
|
12750
|
-
artifacts,
|
|
12751
|
-
bundles,
|
|
12752
|
-
notes,
|
|
12753
|
-
index: {
|
|
12754
|
-
enabled: manifest.index.enabled,
|
|
12755
|
-
type: manifest.index.type,
|
|
12756
|
-
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
|
+
}
|
|
12757
13137
|
}
|
|
12758
|
-
}
|
|
13138
|
+
} catch {
|
|
13139
|
+
}
|
|
13140
|
+
return newest;
|
|
12759
13141
|
}
|
|
12760
|
-
async function
|
|
12761
|
-
const
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
});
|
|
12769
|
-
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;
|
|
12770
13152
|
}
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
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;
|
|
12778
13167
|
}
|
|
12779
|
-
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
|
|
13168
|
+
if (!status.exists) {
|
|
13169
|
+
console.log(`No SQLite index at ${dbPath}`);
|
|
13170
|
+
console.log("Run: agent-inspect index sqlite build");
|
|
13171
|
+
return;
|
|
12783
13172
|
}
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
message: error instanceof Error ? error.message : String(error)
|
|
12800
|
-
});
|
|
12801
|
-
continue;
|
|
12802
|
-
}
|
|
12803
|
-
if (!await pathExists(abs)) {
|
|
12804
|
-
checks2.push({ id, status: "warn", message: `${rel}/ does not exist yet` });
|
|
12805
|
-
} else if (!await isWritable(abs)) {
|
|
12806
|
-
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));
|
|
12807
13188
|
} else {
|
|
12808
|
-
|
|
13189
|
+
console.log("No usable SQLite index. Run: agent-inspect index sqlite build");
|
|
12809
13190
|
}
|
|
13191
|
+
process.exitCode = 1;
|
|
13192
|
+
return;
|
|
12810
13193
|
}
|
|
12811
|
-
|
|
12812
|
-
|
|
12813
|
-
|
|
12814
|
-
|
|
12815
|
-
|
|
12816
|
-
|
|
12817
|
-
|
|
12818
|
-
|
|
12819
|
-
|
|
12820
|
-
|
|
12821
|
-
|
|
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;
|
|
12822
13205
|
}
|
|
12823
|
-
if (
|
|
12824
|
-
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
} catch {
|
|
12836
|
-
checks2.push({ id: "index", status: "warn", message: "cannot stat index" });
|
|
12837
|
-
}
|
|
12838
|
-
}
|
|
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(" "));
|
|
12839
13218
|
}
|
|
12840
|
-
const ok = !checks2.some((c) => c.status === "fail");
|
|
12841
|
-
return { ok, checks: checks2 };
|
|
12842
13219
|
}
|
|
12843
|
-
async function
|
|
12844
|
-
const
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
for (const rel of targets) {
|
|
12853
|
-
const abs = resolveInsideWorkspace(location.workspaceDir, rel);
|
|
12854
|
-
let entries;
|
|
12855
|
-
try {
|
|
12856
|
-
entries = await readdir(abs);
|
|
12857
|
-
} catch {
|
|
12858
|
-
continue;
|
|
12859
|
-
}
|
|
12860
|
-
for (const entry of entries) {
|
|
12861
|
-
const relPath = `${rel}/${entry}`;
|
|
12862
|
-
removed.push(relPath);
|
|
12863
|
-
if (!dryRun) {
|
|
12864
|
-
await rm(path11.join(abs, entry), { recursive: true, force: true });
|
|
12865
|
-
}
|
|
12866
|
-
}
|
|
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;
|
|
12867
13229
|
}
|
|
12868
|
-
|
|
13230
|
+
console.log(`Removed SQLite index: ${dbPath}`);
|
|
12869
13231
|
}
|
|
12870
13232
|
|
|
12871
13233
|
// packages/cli/src/workspace.ts
|
|
@@ -13217,6 +13579,11 @@ function createCliProgram() {
|
|
|
13217
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) => {
|
|
13218
13580
|
runCommand(() => artifactsCommand(target, opts));
|
|
13219
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
|
+
});
|
|
13220
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) => {
|
|
13221
13588
|
runCommand(() => ciSummaryCommand(manifest, opts));
|
|
13222
13589
|
});
|
|
@@ -13421,9 +13788,9 @@ function isPrimaryModule() {
|
|
|
13421
13788
|
if (!entry) return false;
|
|
13422
13789
|
const selfPath = fileURLToPath(import.meta.url);
|
|
13423
13790
|
try {
|
|
13424
|
-
return realpathSync(
|
|
13791
|
+
return realpathSync(path13.resolve(entry)) === realpathSync(path13.resolve(selfPath));
|
|
13425
13792
|
} catch {
|
|
13426
|
-
return
|
|
13793
|
+
return path13.resolve(entry) === path13.resolve(selfPath);
|
|
13427
13794
|
}
|
|
13428
13795
|
}
|
|
13429
13796
|
if (isPrimaryModule()) {
|