agent-inspect 6.31.9 → 6.31.11
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 +12 -0
- package/README.md +1 -1
- package/docs/NESTJS.md +56 -18
- package/package.json +1 -1
- package/packages/cli/dist/{chunk-SWUZRRDT.mjs → chunk-MQYHJL3W.mjs} +2216 -1938
- package/packages/cli/dist/chunk-MQYHJL3W.mjs.map +1 -0
- package/packages/cli/dist/index.cjs +975 -671
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +32 -9
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/cli/dist/{src-XFIPYODW.mjs → src-76R6E2LQ.mjs} +3 -3
- package/packages/cli/dist/{src-XFIPYODW.mjs.map → src-76R6E2LQ.mjs.map} +1 -1
- package/packages/core/dist/advanced.cjs +2079 -1879
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +56 -4
- package/packages/core/dist/advanced.d.ts +56 -4
- package/packages/core/dist/advanced.mjs +188 -23
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/core/dist/checks.cjs +126 -9
- package/packages/core/dist/checks.cjs.map +1 -1
- package/packages/core/dist/checks.d.cts +3 -3
- package/packages/core/dist/checks.d.ts +3 -3
- package/packages/core/dist/checks.mjs +3 -2
- package/packages/core/dist/{chunk-L5WLVBW4.mjs → chunk-AHUZNHFI.mjs} +4 -55
- package/packages/core/dist/chunk-AHUZNHFI.mjs.map +1 -0
- package/packages/core/dist/{chunk-2CCRUI42.mjs → chunk-CCYKNAOD.mjs} +119 -12
- package/packages/core/dist/chunk-CCYKNAOD.mjs.map +1 -0
- package/packages/core/dist/{chunk-7YYS742B.mjs → chunk-M3RJD3BP.mjs} +5 -5
- package/packages/core/dist/{chunk-7YYS742B.mjs.map → chunk-M3RJD3BP.mjs.map} +1 -1
- package/packages/core/dist/{chunk-2IRRUNIN.mjs → chunk-OAUNS5CS.mjs} +3 -3
- package/packages/core/dist/{chunk-2IRRUNIN.mjs.map → chunk-OAUNS5CS.mjs.map} +1 -1
- package/packages/core/dist/{chunk-IJJSPJXV.mjs → chunk-QOC3PJO3.mjs} +4 -4
- package/packages/core/dist/{chunk-IJJSPJXV.mjs.map → chunk-QOC3PJO3.mjs.map} +1 -1
- package/packages/core/dist/chunk-S6LQBLX5.mjs +67 -0
- package/packages/core/dist/chunk-S6LQBLX5.mjs.map +1 -0
- package/packages/core/dist/{chunk-3SPBELJB.mjs → chunk-SSG3ICI4.mjs} +16 -2
- package/packages/core/dist/chunk-SSG3ICI4.mjs.map +1 -0
- package/packages/core/dist/exporters.cjs +3 -3
- package/packages/core/dist/exporters.cjs.map +1 -1
- package/packages/core/dist/exporters.mjs +1 -1
- package/packages/core/dist/{index-EQihlEei.d.cts → index-DACU4Dy1.d.cts} +12 -0
- package/packages/core/dist/{index-CgOAGuM5.d.ts → index-b4tIGrzq.d.ts} +12 -0
- package/packages/core/dist/index.cjs +77 -0
- package/packages/core/dist/index.cjs.map +1 -1
- package/packages/core/dist/index.mjs +3 -2
- package/packages/core/dist/index.mjs.map +1 -1
- package/packages/core/dist/persisted.cjs +11 -0
- package/packages/core/dist/persisted.cjs.map +1 -1
- package/packages/core/dist/persisted.mjs +4 -3
- package/packages/core/dist/persisted.mjs.map +1 -1
- package/packages/core/dist/{programmatic-gZTWLObB.d.cts → programmatic-BLlC7Ytq.d.cts} +1 -1
- package/packages/core/dist/{programmatic-gZTWLObB.d.ts → programmatic-BLlC7Ytq.d.ts} +1 -1
- package/packages/core/dist/readers.cjs +11 -0
- package/packages/core/dist/readers.cjs.map +1 -1
- package/packages/core/dist/readers.d.cts +1 -1
- package/packages/core/dist/readers.d.ts +1 -1
- package/packages/core/dist/readers.mjs +4 -3
- package/packages/cli/dist/chunk-SWUZRRDT.mjs.map +0 -1
- package/packages/core/dist/chunk-2CCRUI42.mjs.map +0 -1
- package/packages/core/dist/chunk-3SPBELJB.mjs.map +0 -1
- package/packages/core/dist/chunk-L5WLVBW4.mjs.map +0 -1
|
@@ -4306,7 +4306,7 @@ function stepMixLine(summary) {
|
|
|
4306
4306
|
if (summary.logicSteps > 0) parts.push(`${summary.logicSteps} logic`);
|
|
4307
4307
|
return parts.length > 0 ? parts.join(", ") : "none";
|
|
4308
4308
|
}
|
|
4309
|
-
function
|
|
4309
|
+
function executionLine(summary) {
|
|
4310
4310
|
if (summary.status === "success") {
|
|
4311
4311
|
return summary.errorSteps > 0 ? "Completed with step errors recorded." : "Completed successfully.";
|
|
4312
4312
|
}
|
|
@@ -4324,7 +4324,7 @@ function outcomeLine(summary) {
|
|
|
4324
4324
|
if (summary.status === "running") {
|
|
4325
4325
|
return "Run is still in progress (no run_completed).";
|
|
4326
4326
|
}
|
|
4327
|
-
return "
|
|
4327
|
+
return "Execution status unknown \u2014 inspect events may be incomplete.";
|
|
4328
4328
|
}
|
|
4329
4329
|
function buildRunWhatSummary(events) {
|
|
4330
4330
|
const base = buildRunSummary(events);
|
|
@@ -4415,7 +4415,7 @@ function renderRunWhat(summary, options = {}) {
|
|
|
4415
4415
|
lines.push(`Correlation: ${parts.join(", ")}`);
|
|
4416
4416
|
}
|
|
4417
4417
|
}
|
|
4418
|
-
lines.push(`
|
|
4418
|
+
lines.push(`Execution: ${executionLine(summary)}`);
|
|
4419
4419
|
if (summary.longestStep && summary.totalSteps > 0) {
|
|
4420
4420
|
lines.push(
|
|
4421
4421
|
`Slowest: ${summary.longestStep.name} (${formatDuration2(summary.longestStep.durationMs)}, ${summary.longestStep.type})`
|
|
@@ -4569,2061 +4569,2120 @@ function findFirstCausalFailure(events, options = {}) {
|
|
|
4569
4569
|
);
|
|
4570
4570
|
}
|
|
4571
4571
|
|
|
4572
|
-
// packages/core/src/
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4572
|
+
// packages/core/src/diagnostics/programmatic.ts
|
|
4573
|
+
var PROGRAMMATIC_DIAGNOSTIC_SPECS = Object.freeze({
|
|
4574
|
+
AI_TRACE_INPUT_INVALID: {
|
|
4575
|
+
code: "AI_TRACE_INPUT_INVALID",
|
|
4576
|
+
summary: 'Expected { type: "file", path }, { type: "directory", path }, { type: "string", content }, { type: "buffer", content }, or { type: "stdin" }.',
|
|
4577
|
+
remediation: "For a file path, use openTraceFile(path).",
|
|
4578
|
+
relatedCodes: ["invalid_input"]
|
|
4579
|
+
},
|
|
4580
|
+
AI_TRACE_FORMAT_UNSUPPORTED: {
|
|
4581
|
+
code: "AI_TRACE_FORMAT_UNSUPPORTED",
|
|
4582
|
+
summary: "No trace reader could detect the input format.",
|
|
4583
|
+
remediation: "Pass an AgentInspect JSONL file via openTraceFile, or set options.format to a registered reader.",
|
|
4584
|
+
relatedCodes: ["unsupported_format"]
|
|
4585
|
+
},
|
|
4586
|
+
AI_TRACE_FORMAT_AMBIGUOUS: {
|
|
4587
|
+
code: "AI_TRACE_FORMAT_AMBIGUOUS",
|
|
4588
|
+
summary: "Multiple trace readers matched the input with equal confidence.",
|
|
4589
|
+
remediation: "Set options.format explicitly to disambiguate the reader.",
|
|
4590
|
+
relatedCodes: ["ambiguous_format"]
|
|
4591
|
+
},
|
|
4592
|
+
AI_TRACE_FACTS_INPUT_NOT_NORMALIZED: {
|
|
4593
|
+
code: "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED",
|
|
4594
|
+
summary: "TraceFacts requires TraceReadResult or PersistedInspectEvent[].",
|
|
4595
|
+
remediation: "Use openTraceFile() to normalize a JSONL trace first."
|
|
4596
|
+
},
|
|
4597
|
+
AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED: {
|
|
4598
|
+
code: "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
|
|
4599
|
+
summary: "Multiple runs are available; select a run before executing checks.",
|
|
4600
|
+
remediation: "Pass options.runId or TraceCheckInput.selectedRun.",
|
|
4601
|
+
relatedCodes: ["AI_CHECK_RUN_SELECTION_REQUIRED"]
|
|
4602
|
+
},
|
|
4603
|
+
AI_TRACE_RELATIONSHIP_SELF_PARENT: {
|
|
4604
|
+
code: "AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
4605
|
+
summary: "A parentId equals its own event/step id.",
|
|
4606
|
+
remediation: "Reject at capture (AI_LANGGRAPH_SELF_PARENT_REJECTED) or drop via logical projection; do not invent replacement parents.",
|
|
4607
|
+
relatedCodes: [
|
|
4608
|
+
"AI_LANGGRAPH_SELF_PARENT_REJECTED",
|
|
4609
|
+
"AI_LOGICAL_SELF_PARENT_REMOVED"
|
|
4610
|
+
]
|
|
4611
|
+
},
|
|
4612
|
+
AI_TRACE_RELATIONSHIP_CYCLE: {
|
|
4613
|
+
code: "AI_TRACE_RELATIONSHIP_CYCLE",
|
|
4614
|
+
summary: "Trace contains a parentId cycle.",
|
|
4615
|
+
remediation: "Use visibility-first tree linking for legacy fixtures; prefer acyclic capture for new adapter output.",
|
|
4616
|
+
relatedCodes: ["structure.cycle"]
|
|
4617
|
+
},
|
|
4618
|
+
AI_TRACE_CONTRACT_INPUT_INVALID: {
|
|
4619
|
+
code: "AI_TRACE_CONTRACT_INPUT_INVALID",
|
|
4620
|
+
summary: "evaluateTraceContract expects ({ read }, contract) or use evaluateTraceContractRead(read, contract).",
|
|
4621
|
+
remediation: "Pass TraceCheckInput as the first argument: evaluateTraceContract({ read }, contract).",
|
|
4622
|
+
relatedCodes: ["AI_CHECK_INVALID_ARGUMENTS"]
|
|
4623
|
+
},
|
|
4624
|
+
AI_INSPECT_RUN_UNSUPPORTED_OPTION: {
|
|
4625
|
+
code: "AI_INSPECT_RUN_UNSUPPORTED_OPTION",
|
|
4626
|
+
summary: 'inspectRun does not accept a "writer" option.',
|
|
4627
|
+
remediation: "Use createInspector({ writer }) then inspector.run(...). inspectRun persists via the global JSONL path (schema 0.1)."
|
|
4583
4628
|
}
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
}
|
|
4589
|
-
function fact(id, label, value, redactor) {
|
|
4590
|
-
return {
|
|
4591
|
-
id,
|
|
4592
|
-
label,
|
|
4593
|
-
value: redactValue(redactor, id.split(".").at(-1) ?? id, value),
|
|
4594
|
-
source: "trace",
|
|
4595
|
-
confidence: "observed"
|
|
4596
|
-
};
|
|
4629
|
+
});
|
|
4630
|
+
function formatProgrammaticDiagnostic(code, detail) {
|
|
4631
|
+
const spec = PROGRAMMATIC_DIAGNOSTIC_SPECS[code];
|
|
4632
|
+
const summary = detail?.trim() ? detail.trim() : spec.summary;
|
|
4633
|
+
return `${code}: ${summary} Remediation: ${spec.remediation}`;
|
|
4597
4634
|
}
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
}).slice(0, 5).map(([kind, count]) => `${kind}:${count}`);
|
|
4635
|
+
|
|
4636
|
+
// packages/core/src/checks/logical-events.ts
|
|
4637
|
+
function isRecord7(value) {
|
|
4638
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4603
4639
|
}
|
|
4604
|
-
function
|
|
4605
|
-
|
|
4640
|
+
function legacyEvent(event) {
|
|
4641
|
+
const value = event.attributes?.legacyEvent;
|
|
4642
|
+
return typeof value === "string" ? value : void 0;
|
|
4606
4643
|
}
|
|
4607
|
-
function
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
// stats slowest-step ranking, which never counts the run envelope.
|
|
4612
|
-
entry.node.event.kind !== "RUN"
|
|
4613
|
-
).sort((a, b) => {
|
|
4614
|
-
const delta = (b.node.event.durationMs ?? 0) - (a.node.event.durationMs ?? 0);
|
|
4615
|
-
return delta !== 0 ? delta : a.index - b.index;
|
|
4616
|
-
})[0];
|
|
4644
|
+
function stepIdOf(event) {
|
|
4645
|
+
const value = event.attributes?.stepId;
|
|
4646
|
+
if (typeof value === "string" && value.trim() !== "") return value;
|
|
4647
|
+
return void 0;
|
|
4617
4648
|
}
|
|
4618
|
-
function
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
label: `${entry.node.event.name} attribute ${key}`,
|
|
4627
|
-
value: redactValue(redactor, key, attrs[key]),
|
|
4628
|
-
source: "trace",
|
|
4629
|
-
confidence: "observed"
|
|
4630
|
-
});
|
|
4631
|
-
if (facts.length >= 8) return facts;
|
|
4632
|
-
}
|
|
4633
|
-
}
|
|
4634
|
-
return facts;
|
|
4649
|
+
function cloneEvent(event) {
|
|
4650
|
+
return {
|
|
4651
|
+
...event,
|
|
4652
|
+
...event.attributes !== void 0 ? { attributes: { ...event.attributes } } : {},
|
|
4653
|
+
...event.error !== void 0 ? { error: { ...event.error } } : {},
|
|
4654
|
+
...event.tokenUsage !== void 0 ? { tokenUsage: { ...event.tokenUsage } } : {},
|
|
4655
|
+
...event.source !== void 0 ? { source: { ...event.source } } : {}
|
|
4656
|
+
};
|
|
4635
4657
|
}
|
|
4636
|
-
function
|
|
4637
|
-
const
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
fact("run.errorNodeCount", "Error node count", countErrorNodes(nodes), redactor),
|
|
4646
|
-
fact("run.kinds", "Observed kind mix", topKinds(run), redactor)
|
|
4647
|
-
];
|
|
4648
|
-
if (run.durationMs !== void 0) {
|
|
4649
|
-
facts.push(fact("run.durationMs", "Run duration milliseconds", run.durationMs, redactor));
|
|
4650
|
-
}
|
|
4651
|
-
const slowest = slowestNode(nodes);
|
|
4652
|
-
if (slowest !== void 0) {
|
|
4653
|
-
facts.push(
|
|
4654
|
-
fact("run.slowestNode", "Slowest observed node", {
|
|
4655
|
-
name: slowest.node.event.name,
|
|
4656
|
-
kind: slowest.node.event.kind,
|
|
4657
|
-
durationMs: slowest.node.event.durationMs
|
|
4658
|
-
}, redactor)
|
|
4659
|
-
);
|
|
4658
|
+
function mergeAttributes(start, complete) {
|
|
4659
|
+
const merged = {
|
|
4660
|
+
...isRecord7(complete.attributes) ? complete.attributes : {},
|
|
4661
|
+
...isRecord7(start.attributes) ? start.attributes : {}
|
|
4662
|
+
};
|
|
4663
|
+
merged.legacyEvent = start.attributes?.legacyEvent ?? complete.attributes?.legacyEvent;
|
|
4664
|
+
merged.legacyCompleteEvent = complete.attributes?.legacyEvent;
|
|
4665
|
+
if (complete.attributes?.errorStack !== void 0) {
|
|
4666
|
+
merged.errorStack = complete.attributes.errorStack;
|
|
4660
4667
|
}
|
|
4661
|
-
|
|
4662
|
-
return facts;
|
|
4668
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
4663
4669
|
}
|
|
4664
|
-
function
|
|
4665
|
-
const
|
|
4666
|
-
const
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
}
|
|
4687
|
-
if (kindFact !== void 0) {
|
|
4688
|
-
inferences.push({
|
|
4689
|
-
id: "shape.kind-mix",
|
|
4690
|
-
label: "Trace shape",
|
|
4691
|
-
text: "The explanation is based on the observed event kind mix, not generated content.",
|
|
4692
|
-
evidence: [kindFact.id],
|
|
4693
|
-
confidence: "deterministic"
|
|
4694
|
-
});
|
|
4695
|
-
}
|
|
4696
|
-
if (durationFact !== void 0) {
|
|
4697
|
-
inferences.push({
|
|
4698
|
-
id: "timing.duration",
|
|
4699
|
-
label: "Timing",
|
|
4700
|
-
text: "Timing claims are limited to persisted duration fields in the trace.",
|
|
4701
|
-
evidence: [durationFact.id],
|
|
4702
|
-
confidence: "deterministic"
|
|
4703
|
-
});
|
|
4704
|
-
}
|
|
4705
|
-
return inferences;
|
|
4670
|
+
function pairStartComplete(start, complete) {
|
|
4671
|
+
const attributes = mergeAttributes(start, complete);
|
|
4672
|
+
const paired = {
|
|
4673
|
+
...cloneEvent(start),
|
|
4674
|
+
status: complete.status,
|
|
4675
|
+
timestamp: complete.timestamp ?? start.timestamp,
|
|
4676
|
+
...complete.endedAt !== void 0 ? { endedAt: complete.endedAt } : {},
|
|
4677
|
+
...complete.durationMs !== void 0 ? { durationMs: complete.durationMs } : {},
|
|
4678
|
+
...complete.error !== void 0 ? { error: { ...complete.error } } : {},
|
|
4679
|
+
...complete.tokenUsage !== void 0 && start.tokenUsage === void 0 ? { tokenUsage: { ...complete.tokenUsage } } : {},
|
|
4680
|
+
...attributes !== void 0 ? { attributes } : {}
|
|
4681
|
+
};
|
|
4682
|
+
return {
|
|
4683
|
+
...paired,
|
|
4684
|
+
sourceEventIds: Object.freeze([start.eventId, complete.eventId]),
|
|
4685
|
+
projection: {
|
|
4686
|
+
paired: true,
|
|
4687
|
+
absorbedEventIds: Object.freeze([complete.eventId]),
|
|
4688
|
+
parentNormalized: false,
|
|
4689
|
+
...start.parentId !== void 0 ? { originalParentId: start.parentId } : {}
|
|
4690
|
+
}
|
|
4691
|
+
};
|
|
4706
4692
|
}
|
|
4707
|
-
function
|
|
4708
|
-
const redactionProfile = options.redactionProfile ?? "local";
|
|
4709
|
-
const resolved = resolveRedactionProfile(redactionProfile);
|
|
4710
|
-
const redactor = new Redactor({ extraKeys: resolved.extraKeys });
|
|
4711
|
-
const mode = options.mode ?? "local";
|
|
4712
|
-
const facts = buildFacts(run, redactor);
|
|
4693
|
+
function asLogical(event, extras) {
|
|
4713
4694
|
return {
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
"Generated locally without provider or network calls.",
|
|
4723
|
-
"Facts are observed from normalized trace data; inferences are deterministic labels."
|
|
4724
|
-
]
|
|
4695
|
+
...cloneEvent(event),
|
|
4696
|
+
sourceEventIds: Object.freeze([event.eventId]),
|
|
4697
|
+
projection: {
|
|
4698
|
+
paired: false,
|
|
4699
|
+
absorbedEventIds: Object.freeze([]),
|
|
4700
|
+
parentNormalized: extras?.parentNormalized === true,
|
|
4701
|
+
...{}
|
|
4702
|
+
}
|
|
4725
4703
|
};
|
|
4726
4704
|
}
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
const
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
`Invalid --duration "${expr}". Use forms like >5s, >=500ms, <2m.`
|
|
4735
|
-
);
|
|
4736
|
-
}
|
|
4737
|
-
const op = m[1];
|
|
4738
|
-
const ms = parseDuration(m[2].trim());
|
|
4739
|
-
return { op, ms };
|
|
4740
|
-
}
|
|
4741
|
-
function durationMatches(valueMs, filter) {
|
|
4742
|
-
if (valueMs === void 0 || !Number.isFinite(valueMs)) return false;
|
|
4743
|
-
switch (filter.op) {
|
|
4744
|
-
case ">":
|
|
4745
|
-
return valueMs > filter.ms;
|
|
4746
|
-
case ">=":
|
|
4747
|
-
return valueMs >= filter.ms;
|
|
4748
|
-
case "<":
|
|
4749
|
-
return valueMs < filter.ms;
|
|
4750
|
-
case "<=":
|
|
4751
|
-
return valueMs <= filter.ms;
|
|
4752
|
-
default:
|
|
4753
|
-
return false;
|
|
4754
|
-
}
|
|
4755
|
-
}
|
|
4756
|
-
function normalizeStepTypeFilter(kind, type) {
|
|
4757
|
-
const v = (kind ?? type)?.trim().toLowerCase();
|
|
4758
|
-
return v && v !== "" ? v : void 0;
|
|
4759
|
-
}
|
|
4760
|
-
function nameMatches(hay, needle) {
|
|
4761
|
-
return hay.toLowerCase().includes(needle.toLowerCase());
|
|
4762
|
-
}
|
|
4763
|
-
async function searchTraces(metas, options) {
|
|
4764
|
-
let filtered = filterTraces(metas, { since: options.since });
|
|
4765
|
-
const stepTypeFilter = normalizeStepTypeFilter(options.kind, options.type);
|
|
4766
|
-
const nameQuery = options.name?.trim();
|
|
4767
|
-
const toolQuery = options.tool?.trim();
|
|
4768
|
-
let durationFilter;
|
|
4769
|
-
if (options.duration) {
|
|
4770
|
-
durationFilter = parseDurationFilter(options.duration);
|
|
4705
|
+
function projectLogicalEvents(events) {
|
|
4706
|
+
const diagnostics = [];
|
|
4707
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
4708
|
+
for (const event of events) {
|
|
4709
|
+
const list = byRun.get(event.runId) ?? [];
|
|
4710
|
+
list.push(event);
|
|
4711
|
+
byRun.set(event.runId, list);
|
|
4771
4712
|
}
|
|
4772
|
-
const
|
|
4773
|
-
const
|
|
4774
|
-
const
|
|
4775
|
-
const
|
|
4776
|
-
|
|
4777
|
-
)
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4713
|
+
const absorbedIds = /* @__PURE__ */ new Set();
|
|
4714
|
+
const logicalByRawId = /* @__PURE__ */ new Map();
|
|
4715
|
+
const stepIdToLogicalId = /* @__PURE__ */ new Map();
|
|
4716
|
+
const logical = [];
|
|
4717
|
+
const orderedRuns = [...byRun.keys()].sort((a, b) => a.localeCompare(b));
|
|
4718
|
+
for (const runId of orderedRuns) {
|
|
4719
|
+
const runEvents = byRun.get(runId) ?? [];
|
|
4720
|
+
const starts = [];
|
|
4721
|
+
const completes = [];
|
|
4722
|
+
const others = [];
|
|
4723
|
+
for (const event of runEvents) {
|
|
4724
|
+
const legacy = legacyEvent(event);
|
|
4725
|
+
if (legacy === "step_started" || legacy === "run_started" && event.status === "running") {
|
|
4726
|
+
starts.push(event);
|
|
4727
|
+
} else if (legacy === "step_completed" || legacy === "run_completed") {
|
|
4728
|
+
completes.push(event);
|
|
4729
|
+
} else {
|
|
4730
|
+
others.push(event);
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
const usedCompletes = /* @__PURE__ */ new Set();
|
|
4734
|
+
for (const start of starts) {
|
|
4735
|
+
const stepId = stepIdOf(start);
|
|
4736
|
+
let match;
|
|
4737
|
+
if (legacyEvent(start) === "run_started") {
|
|
4738
|
+
const candidates = completes.filter(
|
|
4739
|
+
(c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "run_completed"
|
|
4740
|
+
);
|
|
4741
|
+
if (candidates.length > 1) {
|
|
4742
|
+
diagnostics.push({
|
|
4743
|
+
code: "AI_LOGICAL_PAIR_AMBIGUOUS",
|
|
4744
|
+
message: `Multiple run_completed rows for run ${runId}; using first by eventId.`,
|
|
4745
|
+
eventIds: candidates.map((c) => c.eventId)
|
|
4746
|
+
});
|
|
4747
|
+
candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
|
|
4748
|
+
}
|
|
4749
|
+
match = candidates[0];
|
|
4750
|
+
} else if (stepId) {
|
|
4751
|
+
const candidates = completes.filter(
|
|
4752
|
+
(c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "step_completed" && stepIdOf(c) === stepId
|
|
4753
|
+
);
|
|
4754
|
+
if (candidates.length > 1) {
|
|
4755
|
+
diagnostics.push({
|
|
4756
|
+
code: "AI_LOGICAL_PAIR_AMBIGUOUS",
|
|
4757
|
+
message: `Multiple step_completed rows for stepId ${stepId}; using first by eventId.`,
|
|
4758
|
+
eventIds: candidates.map((c) => c.eventId)
|
|
4759
|
+
});
|
|
4760
|
+
candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
|
|
4761
|
+
}
|
|
4762
|
+
match = candidates[0];
|
|
4763
|
+
}
|
|
4764
|
+
if (match) {
|
|
4765
|
+
usedCompletes.add(match.eventId);
|
|
4766
|
+
absorbedIds.add(match.eventId);
|
|
4767
|
+
const paired = pairStartComplete(start, match);
|
|
4768
|
+
logical.push(paired);
|
|
4769
|
+
logicalByRawId.set(start.eventId, paired);
|
|
4770
|
+
logicalByRawId.set(match.eventId, paired);
|
|
4771
|
+
if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, paired.eventId);
|
|
4772
|
+
} else {
|
|
4773
|
+
diagnostics.push({
|
|
4774
|
+
code: "AI_LOGICAL_PAIR_UNMATCHED_START",
|
|
4775
|
+
message: `No matching complete for start ${start.eventId}.`,
|
|
4776
|
+
eventIds: [start.eventId]
|
|
4777
|
+
});
|
|
4778
|
+
const alone = asLogical(start);
|
|
4779
|
+
logical.push(alone);
|
|
4780
|
+
logicalByRawId.set(start.eventId, alone);
|
|
4781
|
+
if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
for (const complete of completes) {
|
|
4785
|
+
if (usedCompletes.has(complete.eventId)) continue;
|
|
4786
|
+
diagnostics.push({
|
|
4787
|
+
code: "AI_LOGICAL_PAIR_UNMATCHED_COMPLETE",
|
|
4788
|
+
message: `No matching start for complete ${complete.eventId}.`,
|
|
4789
|
+
eventIds: [complete.eventId]
|
|
4792
4790
|
});
|
|
4791
|
+
const alone = asLogical(complete);
|
|
4792
|
+
logical.push(alone);
|
|
4793
|
+
logicalByRawId.set(complete.eventId, alone);
|
|
4794
|
+
const stepId = stepIdOf(complete);
|
|
4795
|
+
if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
|
|
4796
|
+
stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
for (const event of others) {
|
|
4800
|
+
const alone = asLogical(event);
|
|
4801
|
+
logical.push(alone);
|
|
4802
|
+
logicalByRawId.set(event.eventId, alone);
|
|
4803
|
+
const stepId = stepIdOf(event);
|
|
4804
|
+
if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
|
|
4805
|
+
stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
4806
|
+
}
|
|
4793
4807
|
}
|
|
4794
|
-
results.sort((a, b) => {
|
|
4795
|
-
const ta = a.timestamp ?? 0;
|
|
4796
|
-
const tb = b.timestamp ?? 0;
|
|
4797
|
-
if (ta !== tb) return tb - ta;
|
|
4798
|
-
return a.runId.localeCompare(b.runId);
|
|
4799
|
-
});
|
|
4800
|
-
return results.slice(0, limit);
|
|
4801
4808
|
}
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4809
|
+
const logicalById = new Map(logical.map((e) => [e.eventId, e]));
|
|
4810
|
+
const normalized = [];
|
|
4811
|
+
for (const event of logical) {
|
|
4812
|
+
const originalParentId = event.parentId;
|
|
4813
|
+
if (!originalParentId) {
|
|
4814
|
+
normalized.push(event);
|
|
4808
4815
|
continue;
|
|
4809
4816
|
}
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4817
|
+
let nextParent = originalParentId;
|
|
4818
|
+
let remapped = false;
|
|
4819
|
+
const viaAbsorbed = logicalByRawId.get(originalParentId);
|
|
4820
|
+
if (viaAbsorbed && viaAbsorbed.eventId !== originalParentId) {
|
|
4821
|
+
nextParent = viaAbsorbed.eventId;
|
|
4822
|
+
remapped = true;
|
|
4823
|
+
} else if (!logicalById.has(originalParentId)) {
|
|
4824
|
+
const viaStep = stepIdToLogicalId.get(`${event.runId}:${originalParentId}`);
|
|
4825
|
+
if (viaStep) {
|
|
4826
|
+
nextParent = viaStep;
|
|
4827
|
+
remapped = true;
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
if (!remapped) {
|
|
4831
|
+
if (originalParentId === event.eventId) {
|
|
4832
|
+
diagnostics.push({
|
|
4833
|
+
code: "AI_LOGICAL_SELF_PARENT_REMOVED",
|
|
4834
|
+
message: formatProgrammaticDiagnostic(
|
|
4835
|
+
"AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
4836
|
+
`Removed self-parent edge on ${event.eventId}.`
|
|
4837
|
+
),
|
|
4838
|
+
eventIds: [event.eventId]
|
|
4839
|
+
});
|
|
4840
|
+
const { parentId: _drop, ...rest } = event;
|
|
4841
|
+
normalized.push({
|
|
4842
|
+
...rest,
|
|
4843
|
+
projection: {
|
|
4844
|
+
...event.projection,
|
|
4845
|
+
parentNormalized: true,
|
|
4846
|
+
originalParentId
|
|
4847
|
+
}
|
|
4840
4848
|
});
|
|
4849
|
+
continue;
|
|
4841
4850
|
}
|
|
4851
|
+
if (!logicalById.has(originalParentId) && !logicalByRawId.has(originalParentId)) {
|
|
4852
|
+
const mapping = event.attributes?.parentMapping;
|
|
4853
|
+
const unresolved = mapping === "unresolved" || event.attributes?.parentUnresolved === true || event.attributes?.unresolvedParent === true || // LangGraph/framework scaffolding sentinel labels are not event ids.
|
|
4854
|
+
/^LangGraph$/i.test(originalParentId) || originalParentId.startsWith("unresolved:");
|
|
4855
|
+
if (!unresolved) {
|
|
4856
|
+
diagnostics.push({
|
|
4857
|
+
code: "AI_LOGICAL_PARENT_UNRESOLVED",
|
|
4858
|
+
message: `Parent ${originalParentId} unresolved for ${event.eventId}.`,
|
|
4859
|
+
eventIds: [event.eventId]
|
|
4860
|
+
});
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
normalized.push(event);
|
|
4864
|
+
continue;
|
|
4842
4865
|
}
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
return [];
|
|
4862
|
-
}
|
|
4863
|
-
const fields = [];
|
|
4864
|
-
if (opts.statusFilter) fields.push("run.status");
|
|
4865
|
-
if (opts.nameQuery) fields.push("run.name");
|
|
4866
|
-
if (opts.durationFilter) fields.push("run.durationMs");
|
|
4867
|
-
if (fields.length === 0) return [];
|
|
4868
|
-
return [
|
|
4869
|
-
{
|
|
4870
|
-
runId: m.runId,
|
|
4871
|
-
runName: m.name,
|
|
4872
|
-
runStatus: m.status,
|
|
4873
|
-
timestamp: m.startedAt,
|
|
4874
|
-
durationMs: m.durationMs,
|
|
4875
|
-
matchReason: `run match: ${fields.join(", ")}`,
|
|
4876
|
-
matchedFields: fields,
|
|
4877
|
-
filePath: m.filePath
|
|
4878
|
-
}
|
|
4879
|
-
];
|
|
4880
|
-
}
|
|
4881
|
-
function matchStepLevel(m, events, opts) {
|
|
4882
|
-
const out = [];
|
|
4883
|
-
const started = /* @__PURE__ */ new Map();
|
|
4884
|
-
for (const e of events) {
|
|
4885
|
-
if (e.event === "step_started") {
|
|
4886
|
-
started.set(e.stepId, e);
|
|
4887
|
-
}
|
|
4888
|
-
}
|
|
4889
|
-
for (const e of events) {
|
|
4890
|
-
if (e.event !== "step_completed") continue;
|
|
4891
|
-
const c = e;
|
|
4892
|
-
const s = started.get(c.stepId);
|
|
4893
|
-
if (!s) continue;
|
|
4894
|
-
const fields = [];
|
|
4895
|
-
const stepType = s.type;
|
|
4896
|
-
if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
|
|
4897
|
-
continue;
|
|
4898
|
-
}
|
|
4899
|
-
const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
|
|
4900
|
-
if (!hasStepFilters) continue;
|
|
4901
|
-
if (opts.statusFilter === "error" && c.status === "error") {
|
|
4902
|
-
fields.push("step.status");
|
|
4903
|
-
} else if (opts.statusFilter === "success" && c.status === "success") {
|
|
4904
|
-
fields.push("step.status");
|
|
4905
|
-
} else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
|
|
4866
|
+
if (nextParent === event.eventId) {
|
|
4867
|
+
diagnostics.push({
|
|
4868
|
+
code: "AI_LOGICAL_SELF_PARENT_REMOVED",
|
|
4869
|
+
message: formatProgrammaticDiagnostic(
|
|
4870
|
+
"AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
4871
|
+
`Removed self-parent edge on ${event.eventId} (was ${originalParentId}).`
|
|
4872
|
+
),
|
|
4873
|
+
eventIds: [event.eventId]
|
|
4874
|
+
});
|
|
4875
|
+
const { parentId: _drop, ...rest } = event;
|
|
4876
|
+
normalized.push({
|
|
4877
|
+
...rest,
|
|
4878
|
+
projection: {
|
|
4879
|
+
...event.projection,
|
|
4880
|
+
parentNormalized: true,
|
|
4881
|
+
originalParentId
|
|
4882
|
+
}
|
|
4883
|
+
});
|
|
4906
4884
|
continue;
|
|
4907
4885
|
}
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
if (opts.stepTypeFilter) {
|
|
4922
|
-
fields.push("step.type");
|
|
4923
|
-
}
|
|
4924
|
-
if (fields.length === 0) continue;
|
|
4925
|
-
out.push({
|
|
4926
|
-
runId: m.runId,
|
|
4927
|
-
runName: m.name,
|
|
4928
|
-
runStatus: m.status,
|
|
4929
|
-
stepId: c.stepId,
|
|
4930
|
-
stepName: s.name,
|
|
4931
|
-
stepType,
|
|
4932
|
-
timestamp: s.startTime ?? s.timestamp,
|
|
4933
|
-
durationMs: c.durationMs,
|
|
4934
|
-
matchReason: `step match: ${fields.join(", ")}`,
|
|
4935
|
-
matchedFields: fields,
|
|
4936
|
-
filePath: m.filePath
|
|
4886
|
+
diagnostics.push({
|
|
4887
|
+
code: "AI_LOGICAL_PARENT_REMAPPED",
|
|
4888
|
+
message: `Remapped parent ${originalParentId} \u2192 ${nextParent} for ${event.eventId}.`,
|
|
4889
|
+
eventIds: [event.eventId]
|
|
4890
|
+
});
|
|
4891
|
+
normalized.push({
|
|
4892
|
+
...event,
|
|
4893
|
+
parentId: nextParent,
|
|
4894
|
+
projection: {
|
|
4895
|
+
...event.projection,
|
|
4896
|
+
parentNormalized: true,
|
|
4897
|
+
originalParentId
|
|
4898
|
+
}
|
|
4937
4899
|
});
|
|
4938
4900
|
}
|
|
4939
|
-
|
|
4901
|
+
const rawIndex = new Map(events.map((e, i) => [e.eventId, i]));
|
|
4902
|
+
normalized.sort((a, b) => {
|
|
4903
|
+
const ai = rawIndex.get(a.sourceEventIds[0]) ?? 0;
|
|
4904
|
+
const bi = rawIndex.get(b.sourceEventIds[0]) ?? 0;
|
|
4905
|
+
return ai - bi || a.eventId.localeCompare(b.eventId);
|
|
4906
|
+
});
|
|
4907
|
+
return {
|
|
4908
|
+
logicalEvents: Object.freeze(normalized),
|
|
4909
|
+
diagnostics: Object.freeze(diagnostics)
|
|
4910
|
+
};
|
|
4940
4911
|
}
|
|
4941
|
-
|
|
4942
|
-
const
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
}
|
|
4912
|
+
function resolveCanonicalToolName(event) {
|
|
4913
|
+
const attrs = event.attributes;
|
|
4914
|
+
const direct = pickString(attrs, ["toolName", "tool"]);
|
|
4915
|
+
if (direct) return direct;
|
|
4916
|
+
const metadata = attrs?.metadata;
|
|
4917
|
+
if (isRecord7(metadata)) {
|
|
4918
|
+
const nested = pickString(metadata, ["toolName", "tool"]);
|
|
4919
|
+
if (nested) return nested;
|
|
4950
4920
|
}
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
// packages/core/src/sessions/metadata.ts
|
|
4955
|
-
function isNonEmptyString3(value) {
|
|
4956
|
-
return typeof value === "string" && value.trim() !== "";
|
|
4957
|
-
}
|
|
4958
|
-
function finitePositiveInt(value) {
|
|
4959
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
4960
|
-
return void 0;
|
|
4921
|
+
for (const prefix of ["tool:", "function:", "mcp-tools:"]) {
|
|
4922
|
+
if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
|
|
4961
4923
|
}
|
|
4962
|
-
return
|
|
4924
|
+
return event.name;
|
|
4963
4925
|
}
|
|
4964
|
-
function
|
|
4926
|
+
function pickString(record, keys) {
|
|
4965
4927
|
if (!record) return void 0;
|
|
4966
|
-
const
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
if (isNonEmptyString3(value)) {
|
|
4970
|
-
out[key] = value.trim();
|
|
4971
|
-
found = true;
|
|
4972
|
-
}
|
|
4973
|
-
};
|
|
4974
|
-
assignString("sessionId", record.sessionId);
|
|
4975
|
-
assignString("conversationId", record.conversationId);
|
|
4976
|
-
assignString("groupId", record.groupId);
|
|
4977
|
-
assignString("parentGroupId", record.parentGroupId);
|
|
4978
|
-
assignString("retryOf", record.retryOf);
|
|
4979
|
-
assignString("retryReason", record.retryReason);
|
|
4980
|
-
assignString("handoffFrom", record.handoffFrom);
|
|
4981
|
-
assignString("handoffTo", record.handoffTo);
|
|
4982
|
-
assignString("subAgentId", record.subAgentId);
|
|
4983
|
-
assignString("subAgentName", record.subAgentName);
|
|
4984
|
-
assignString("jobId", record.jobId);
|
|
4985
|
-
assignString("queueName", record.queueName);
|
|
4986
|
-
assignString("workflowName", record.workflowName);
|
|
4987
|
-
assignString("workflowStep", record.workflowStep);
|
|
4988
|
-
assignString("toolCallId", record.toolCallId);
|
|
4989
|
-
assignString("mcpToolCallId", record.mcpToolCallId);
|
|
4990
|
-
assignString("linkedStepId", record.linkedStepId);
|
|
4991
|
-
assignString("operationId", record.operationId);
|
|
4992
|
-
assignString("attemptId", record.attemptId);
|
|
4993
|
-
assignString("fallbackOf", record.fallbackOf);
|
|
4994
|
-
assignString("idempotencyKey", record.idempotencyKey);
|
|
4995
|
-
assignString("correlationId", record.correlationId);
|
|
4996
|
-
assignString("requestId", record.requestId);
|
|
4997
|
-
assignString("decisionId", record.decisionId);
|
|
4998
|
-
const attempt = finitePositiveInt(record.attempt);
|
|
4999
|
-
if (attempt !== void 0) {
|
|
5000
|
-
out.attempt = attempt;
|
|
5001
|
-
found = true;
|
|
5002
|
-
}
|
|
5003
|
-
const attemptNumber = finitePositiveInt(record.attemptNumber);
|
|
5004
|
-
if (attemptNumber !== void 0) {
|
|
5005
|
-
out.attemptNumber = attemptNumber;
|
|
5006
|
-
found = true;
|
|
5007
|
-
}
|
|
5008
|
-
return found ? out : void 0;
|
|
5009
|
-
}
|
|
5010
|
-
function sessionKeyForRun(meta, options) {
|
|
5011
|
-
if (meta?.sessionId) return meta.sessionId;
|
|
5012
|
-
if (options?.correlateByGroupId && meta?.groupId) {
|
|
5013
|
-
return `group:${meta.groupId}`;
|
|
4928
|
+
for (const key of keys) {
|
|
4929
|
+
const value = record[key];
|
|
4930
|
+
if (typeof value === "string" && value.trim() !== "") return value.trim();
|
|
5014
4931
|
}
|
|
5015
4932
|
return void 0;
|
|
5016
4933
|
}
|
|
5017
4934
|
|
|
5018
|
-
// packages/core/src/
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
error
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
stale: 2,
|
|
5025
|
-
completed: 1
|
|
5026
|
-
};
|
|
5027
|
-
var EXPLICIT_SESSION_STATUSES = /* @__PURE__ */ new Set([
|
|
5028
|
-
"running",
|
|
5029
|
-
"waiting_input",
|
|
5030
|
-
"idle",
|
|
5031
|
-
"completed",
|
|
5032
|
-
"error",
|
|
5033
|
-
"stale",
|
|
5034
|
-
"unknown"
|
|
5035
|
-
]);
|
|
5036
|
-
function isExplicitSessionStatus(value) {
|
|
5037
|
-
return typeof value === "string" && EXPLICIT_SESSION_STATUSES.has(value);
|
|
5038
|
-
}
|
|
5039
|
-
function activityMs(run) {
|
|
5040
|
-
return run.endedAt ?? run.startedAt ?? 0;
|
|
4935
|
+
// packages/core/src/explain.ts
|
|
4936
|
+
function toDisplayStatus(status) {
|
|
4937
|
+
if (status === "ok") return "success";
|
|
4938
|
+
if (status === "error") return "error";
|
|
4939
|
+
if (status === "running") return "running";
|
|
4940
|
+
return "unknown";
|
|
5041
4941
|
}
|
|
5042
|
-
function
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
if (ms > latest) latest = ms;
|
|
4942
|
+
function flatten(nodes, out = []) {
|
|
4943
|
+
for (const node of nodes) {
|
|
4944
|
+
out.push({ node, index: out.length + 1 });
|
|
4945
|
+
flatten(node.children, out);
|
|
5047
4946
|
}
|
|
5048
|
-
return
|
|
4947
|
+
return out;
|
|
5049
4948
|
}
|
|
5050
|
-
function
|
|
5051
|
-
|
|
5052
|
-
for (const run of runs) {
|
|
5053
|
-
if (run.startedAt === void 0) continue;
|
|
5054
|
-
if (earliest === void 0 || run.startedAt < earliest) {
|
|
5055
|
-
earliest = run.startedAt;
|
|
5056
|
-
}
|
|
5057
|
-
}
|
|
5058
|
-
return earliest;
|
|
4949
|
+
function redactValue(redactor, key, value) {
|
|
4950
|
+
return redactor.redactValue(key, value);
|
|
5059
4951
|
}
|
|
5060
|
-
function
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
4952
|
+
function fact(id, label, value, redactor) {
|
|
4953
|
+
return {
|
|
4954
|
+
id,
|
|
4955
|
+
label,
|
|
4956
|
+
value: redactValue(redactor, id.split(".").at(-1) ?? id, value),
|
|
4957
|
+
source: "trace",
|
|
4958
|
+
confidence: "observed"
|
|
4959
|
+
};
|
|
5068
4960
|
}
|
|
5069
|
-
function
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
if (!isExplicitSessionStatus(raw)) continue;
|
|
5075
|
-
const priority = EXPLICIT_STATUS_PRIORITY[raw] ?? 0;
|
|
5076
|
-
if (priority > bestPriority) {
|
|
5077
|
-
bestPriority = priority;
|
|
5078
|
-
best = raw;
|
|
5079
|
-
}
|
|
5080
|
-
}
|
|
5081
|
-
return best;
|
|
4961
|
+
function topKinds(run) {
|
|
4962
|
+
return Object.entries(run.metadata.kinds).filter(([, count]) => count > 0).sort((a, b) => {
|
|
4963
|
+
if (b[1] !== a[1]) return b[1] - a[1];
|
|
4964
|
+
return a[0].localeCompare(b[0]);
|
|
4965
|
+
}).slice(0, 5).map(([kind, count]) => `${kind}:${count}`);
|
|
5082
4966
|
}
|
|
5083
|
-
function
|
|
5084
|
-
|
|
5085
|
-
const latest = errorRuns[0];
|
|
5086
|
-
if (!latest) return void 0;
|
|
5087
|
-
const meta = latest.metadata ?? {};
|
|
5088
|
-
const message = typeof meta.errorMessage === "string" && meta.errorMessage.trim() !== "" ? meta.errorMessage.trim() : latest.name ?? latest.runId;
|
|
5089
|
-
const code = typeof meta.errorCode === "string" && meta.errorCode.trim() !== "" ? meta.errorCode.trim() : void 0;
|
|
5090
|
-
return { runId: latest.runId, message, code };
|
|
4967
|
+
function countErrorNodes(nodes) {
|
|
4968
|
+
return nodes.filter((entry) => entry.node.event.status === "error").length;
|
|
5091
4969
|
}
|
|
5092
|
-
function
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
4970
|
+
function slowestNode(nodes) {
|
|
4971
|
+
return nodes.filter(
|
|
4972
|
+
(entry) => entry.node.event.durationMs !== void 0 && // The RUN boundary spans the whole run, so its duration always dominates
|
|
4973
|
+
// and shadows every real step. Rank actual work nodes only, matching the
|
|
4974
|
+
// stats slowest-step ranking, which never counts the run envelope.
|
|
4975
|
+
entry.node.event.kind !== "RUN"
|
|
4976
|
+
).sort((a, b) => {
|
|
4977
|
+
const delta = (b.node.event.durationMs ?? 0) - (a.node.event.durationMs ?? 0);
|
|
4978
|
+
return delta !== 0 ? delta : a.index - b.index;
|
|
4979
|
+
})[0];
|
|
4980
|
+
}
|
|
4981
|
+
function attributeFacts(nodes, redactor) {
|
|
4982
|
+
const facts = [];
|
|
4983
|
+
for (const entry of nodes) {
|
|
4984
|
+
const attrs = entry.node.event.attributes;
|
|
4985
|
+
if (attrs === void 0) continue;
|
|
4986
|
+
for (const key of Object.keys(attrs).sort()) {
|
|
4987
|
+
facts.push({
|
|
4988
|
+
id: `node.${entry.index}.attributes.${key}`,
|
|
4989
|
+
label: `${entry.node.event.name} attribute ${key}`,
|
|
4990
|
+
value: redactValue(redactor, key, attrs[key]),
|
|
4991
|
+
source: "trace",
|
|
4992
|
+
confidence: "observed"
|
|
4993
|
+
});
|
|
4994
|
+
if (facts.length >= 8) return facts;
|
|
5112
4995
|
}
|
|
5113
4996
|
}
|
|
5114
|
-
return
|
|
4997
|
+
return facts;
|
|
5115
4998
|
}
|
|
5116
|
-
function
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
4999
|
+
function buildFacts(run, redactor, events) {
|
|
5000
|
+
const nodes = flatten(run.children);
|
|
5001
|
+
const facts = [
|
|
5002
|
+
fact("run.id", "Run id", run.runId, redactor),
|
|
5003
|
+
fact("run.name", "Run name", run.name ?? run.runId, redactor),
|
|
5004
|
+
fact("run.status", "Run status", toDisplayStatus(run.status), redactor),
|
|
5005
|
+
fact("run.totalEvents", "Total events", run.metadata.totalEvents, redactor),
|
|
5006
|
+
fact("run.stepCount", "Top-level step count", run.children.length, redactor),
|
|
5007
|
+
fact("run.nodeCount", "Total node count", nodes.length, redactor),
|
|
5008
|
+
fact("run.errorNodeCount", "Error node count", countErrorNodes(nodes), redactor),
|
|
5009
|
+
fact("run.kinds", "Observed kind mix", topKinds(run), redactor)
|
|
5010
|
+
];
|
|
5011
|
+
if (run.durationMs !== void 0) {
|
|
5012
|
+
facts.push(fact("run.durationMs", "Run duration milliseconds", run.durationMs, redactor));
|
|
5013
|
+
}
|
|
5014
|
+
const slowest = slowestNode(nodes);
|
|
5015
|
+
if (slowest !== void 0) {
|
|
5016
|
+
facts.push(
|
|
5017
|
+
fact("run.slowestNode", "Slowest observed node", {
|
|
5018
|
+
name: slowest.node.event.name,
|
|
5019
|
+
kind: slowest.node.event.kind,
|
|
5020
|
+
durationMs: slowest.node.event.durationMs
|
|
5021
|
+
}, redactor)
|
|
5022
|
+
);
|
|
5023
|
+
}
|
|
5024
|
+
if (events !== void 0) {
|
|
5025
|
+
const scoped = events.filter((event) => event.runId === run.runId);
|
|
5026
|
+
const { logicalEvents: logical } = projectLogicalEvents(scoped);
|
|
5027
|
+
const logicalSteps = logical.filter((event) => event.kind !== "RUN");
|
|
5028
|
+
facts.push(
|
|
5029
|
+
fact("run.rawEventCount", "Raw persisted event count", scoped.length, redactor),
|
|
5030
|
+
fact(
|
|
5031
|
+
"run.logicalStepCount",
|
|
5032
|
+
"Logical lifecycle step count",
|
|
5033
|
+
logicalSteps.length,
|
|
5034
|
+
redactor
|
|
5035
|
+
),
|
|
5036
|
+
fact(
|
|
5037
|
+
"run.logicalEventCount",
|
|
5038
|
+
"Logical lifecycle event count (including RUN)",
|
|
5039
|
+
logical.length,
|
|
5040
|
+
redactor
|
|
5041
|
+
)
|
|
5042
|
+
);
|
|
5043
|
+
const slowestLogical = [...logicalSteps].filter((event) => typeof event.durationMs === "number").sort((a, b) => {
|
|
5044
|
+
const delta = (b.durationMs ?? 0) - (a.durationMs ?? 0);
|
|
5045
|
+
return delta !== 0 ? delta : a.eventId.localeCompare(b.eventId);
|
|
5046
|
+
})[0];
|
|
5047
|
+
if (slowestLogical !== void 0) {
|
|
5048
|
+
facts.push(
|
|
5049
|
+
fact("run.slowestLogicalStep", "Slowest logical lifecycle step", {
|
|
5050
|
+
name: slowestLogical.name,
|
|
5051
|
+
kind: slowestLogical.kind,
|
|
5052
|
+
durationMs: slowestLogical.durationMs
|
|
5053
|
+
}, redactor)
|
|
5054
|
+
);
|
|
5121
5055
|
}
|
|
5122
5056
|
}
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
function deriveSessionStatus(runs, options = {}) {
|
|
5126
|
-
if (runs.length === 0) return "unknown";
|
|
5127
|
-
if (runs.some((run) => run.status === "running")) return "running";
|
|
5128
|
-
const explicit = pickExplicitStatus(runs);
|
|
5129
|
-
if (explicit && explicit !== "running") return explicit;
|
|
5130
|
-
if (runs.some((run) => run.status === "error")) return "error";
|
|
5131
|
-
if (runs.every((run) => run.status === "success")) return "completed";
|
|
5132
|
-
const nowMs = options.nowMs ?? Date.now();
|
|
5133
|
-
const staleThresholdMs = options.staleThresholdMs ?? DEFAULT_STALE_THRESHOLD_MS;
|
|
5134
|
-
const lastMs = latestActivityMs(runs);
|
|
5135
|
-
if (lastMs > 0 && nowMs - lastMs > staleThresholdMs) return "stale";
|
|
5136
|
-
return "unknown";
|
|
5057
|
+
facts.push(...attributeFacts(nodes, redactor));
|
|
5058
|
+
return facts;
|
|
5137
5059
|
}
|
|
5138
|
-
function
|
|
5139
|
-
const
|
|
5140
|
-
const
|
|
5141
|
-
const
|
|
5142
|
-
const
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5060
|
+
function buildInferences(run, facts) {
|
|
5061
|
+
const inferences = [];
|
|
5062
|
+
const errorFact = facts.find((item) => item.id === "run.errorNodeCount");
|
|
5063
|
+
const kindFact = facts.find((item) => item.id === "run.kinds");
|
|
5064
|
+
const durationFact = facts.find((item) => item.id === "run.durationMs");
|
|
5065
|
+
const errorNodeCount = typeof errorFact?.value === "number" ? errorFact.value : 0;
|
|
5066
|
+
if (run.status === "error" || errorNodeCount > 0) {
|
|
5067
|
+
inferences.push({
|
|
5068
|
+
id: "outcome.error",
|
|
5069
|
+
label: "Outcome",
|
|
5070
|
+
text: "The run recorded an error status or at least one error node.",
|
|
5071
|
+
evidence: ["run.status", "run.errorNodeCount"],
|
|
5072
|
+
confidence: "deterministic"
|
|
5073
|
+
});
|
|
5074
|
+
} else if (run.status === "ok") {
|
|
5075
|
+
inferences.push({
|
|
5076
|
+
id: "outcome.success",
|
|
5077
|
+
label: "Outcome",
|
|
5078
|
+
text: "The run completed without observed error nodes.",
|
|
5079
|
+
evidence: ["run.status", "run.errorNodeCount"],
|
|
5080
|
+
confidence: "deterministic"
|
|
5081
|
+
});
|
|
5152
5082
|
}
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5083
|
+
if (kindFact !== void 0) {
|
|
5084
|
+
inferences.push({
|
|
5085
|
+
id: "shape.kind-mix",
|
|
5086
|
+
label: "Trace shape",
|
|
5087
|
+
text: "The explanation is based on the observed event kind mix, not generated content.",
|
|
5088
|
+
evidence: [kindFact.id],
|
|
5089
|
+
confidence: "deterministic"
|
|
5090
|
+
});
|
|
5091
|
+
}
|
|
5092
|
+
if (durationFact !== void 0) {
|
|
5093
|
+
inferences.push({
|
|
5094
|
+
id: "timing.duration",
|
|
5095
|
+
label: "Timing",
|
|
5096
|
+
text: "Timing claims are limited to persisted duration fields in the trace.",
|
|
5097
|
+
evidence: [durationFact.id],
|
|
5098
|
+
confidence: "deterministic"
|
|
5099
|
+
});
|
|
5100
|
+
}
|
|
5101
|
+
return inferences;
|
|
5102
|
+
}
|
|
5103
|
+
function buildLocalExplanation(run, options = {}) {
|
|
5104
|
+
const redactionProfile = options.redactionProfile ?? "local";
|
|
5105
|
+
const resolved = resolveRedactionProfile(redactionProfile);
|
|
5106
|
+
const redactor = new Redactor({ extraKeys: resolved.extraKeys });
|
|
5107
|
+
const mode = options.mode ?? "local";
|
|
5108
|
+
const facts = buildFacts(run, redactor, options.events);
|
|
5158
5109
|
return {
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
observationSummary: deriveObservationSummary(sessionRuns),
|
|
5171
|
-
checkSummary: deriveCheckSummary(sessionRuns)
|
|
5110
|
+
mode,
|
|
5111
|
+
runId: String(redactValue(redactor, "runId", run.runId)),
|
|
5112
|
+
...run.name !== void 0 ? { name: String(redactValue(redactor, "name", run.name)) } : {},
|
|
5113
|
+
...run.status !== void 0 ? { status: toDisplayStatus(run.status) } : {},
|
|
5114
|
+
redactionProfile,
|
|
5115
|
+
facts,
|
|
5116
|
+
inferences: mode === "dry-run" ? [] : buildInferences(run, facts),
|
|
5117
|
+
notes: [
|
|
5118
|
+
"Generated locally without provider or network calls.",
|
|
5119
|
+
"Facts are observed from normalized trace data; inferences are deterministic labels."
|
|
5120
|
+
]
|
|
5172
5121
|
};
|
|
5173
5122
|
}
|
|
5174
5123
|
|
|
5175
|
-
// packages/core/src/
|
|
5176
|
-
function
|
|
5177
|
-
const
|
|
5178
|
-
const
|
|
5179
|
-
if (
|
|
5180
|
-
|
|
5124
|
+
// packages/core/src/search.ts
|
|
5125
|
+
function parseDurationFilter(expr) {
|
|
5126
|
+
const raw = expr.trim();
|
|
5127
|
+
const m = raw.match(/^(>=|<=|>|<)\s*(.+)$/);
|
|
5128
|
+
if (!m) {
|
|
5129
|
+
throw new Error(
|
|
5130
|
+
`Invalid --duration "${expr}". Use forms like >5s, >=500ms, <2m.`
|
|
5131
|
+
);
|
|
5181
5132
|
}
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
}
|
|
5185
|
-
return `${name} session ${session.sessionId} ${status}`;
|
|
5186
|
-
}
|
|
5187
|
-
function parseSinceMs(since, nowMs) {
|
|
5188
|
-
if (!since || since.trim() === "") return nowMs - 7 * 864e5;
|
|
5189
|
-
const trimmed = since.trim().toLowerCase();
|
|
5190
|
-
const match = /^(\d+)([smhd])$/.exec(trimmed);
|
|
5191
|
-
if (!match) return nowMs - 7 * 864e5;
|
|
5192
|
-
const amount = Number.parseInt(match[1], 10);
|
|
5193
|
-
const unit = match[2];
|
|
5194
|
-
const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
5195
|
-
return nowMs - amount * mult;
|
|
5133
|
+
const op = m[1];
|
|
5134
|
+
const ms = parseDuration(m[2].trim());
|
|
5135
|
+
return { op, ms };
|
|
5196
5136
|
}
|
|
5197
|
-
function
|
|
5198
|
-
|
|
5137
|
+
function durationMatches(valueMs, filter) {
|
|
5138
|
+
if (valueMs === void 0 || !Number.isFinite(valueMs)) return false;
|
|
5139
|
+
switch (filter.op) {
|
|
5140
|
+
case ">":
|
|
5141
|
+
return valueMs > filter.ms;
|
|
5142
|
+
case ">=":
|
|
5143
|
+
return valueMs >= filter.ms;
|
|
5144
|
+
case "<":
|
|
5145
|
+
return valueMs < filter.ms;
|
|
5146
|
+
case "<=":
|
|
5147
|
+
return valueMs <= filter.ms;
|
|
5148
|
+
default:
|
|
5149
|
+
return false;
|
|
5150
|
+
}
|
|
5199
5151
|
}
|
|
5200
|
-
function
|
|
5201
|
-
|
|
5152
|
+
function normalizeStepTypeFilter(kind, type) {
|
|
5153
|
+
const v = (kind ?? type)?.trim().toLowerCase();
|
|
5154
|
+
return v && v !== "" ? v : void 0;
|
|
5202
5155
|
}
|
|
5203
|
-
function
|
|
5204
|
-
|
|
5205
|
-
if (!summary) return 0;
|
|
5206
|
-
return summary.warn;
|
|
5156
|
+
function nameMatches(hay, needle) {
|
|
5157
|
+
return hay.toLowerCase().includes(needle.toLowerCase());
|
|
5207
5158
|
}
|
|
5208
|
-
function
|
|
5209
|
-
|
|
5210
|
-
const
|
|
5211
|
-
const
|
|
5212
|
-
const
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
});
|
|
5217
|
-
const entries = [...inWindow].sort((a, b) => Date.parse(b.lastActivity) - Date.parse(a.lastActivity)).slice(0, limit).map((session) => ({
|
|
5218
|
-
sessionId: session.sessionId,
|
|
5219
|
-
status: session.status,
|
|
5220
|
-
summary: statusLine(session),
|
|
5221
|
-
lastActivity: session.lastActivity,
|
|
5222
|
-
runCount: session.runIds.length
|
|
5223
|
-
}));
|
|
5224
|
-
let failed = 0;
|
|
5225
|
-
let stale = 0;
|
|
5226
|
-
let guardrailWarningTotal = 0;
|
|
5227
|
-
for (const session of inWindow) {
|
|
5228
|
-
if (isFailed(session.status)) failed += 1;
|
|
5229
|
-
if (isStale(session.status)) stale += 1;
|
|
5230
|
-
guardrailWarningTotal += guardrailWarnings(session);
|
|
5159
|
+
async function searchTraces(metas, options) {
|
|
5160
|
+
let filtered = filterTraces(metas, { since: options.since });
|
|
5161
|
+
const stepTypeFilter = normalizeStepTypeFilter(options.kind, options.type);
|
|
5162
|
+
const nameQuery = options.name?.trim();
|
|
5163
|
+
const toolQuery = options.tool?.trim();
|
|
5164
|
+
let durationFilter;
|
|
5165
|
+
if (options.duration) {
|
|
5166
|
+
durationFilter = parseDurationFilter(options.duration);
|
|
5231
5167
|
}
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
guardrailWarnings: guardrailWarningTotal,
|
|
5238
|
-
entries
|
|
5239
|
-
};
|
|
5240
|
-
}
|
|
5241
|
-
function renderActivitySummaryHuman(summary, options) {
|
|
5242
|
-
const lines = [];
|
|
5243
|
-
const nowMs = options?.nowMs ?? Date.now();
|
|
5244
|
-
const todayStart = new Date(nowMs);
|
|
5245
|
-
todayStart.setHours(0, 0, 0, 0);
|
|
5246
|
-
const todayMs = todayStart.getTime();
|
|
5247
|
-
const today = summary.entries.filter(
|
|
5248
|
-
(entry) => Date.parse(entry.lastActivity) >= todayMs
|
|
5168
|
+
const limit = options.limit ?? 50;
|
|
5169
|
+
const sessionId = options.session?.trim();
|
|
5170
|
+
const observationStatus2 = parseObservationFilter(options.observation);
|
|
5171
|
+
const hasContentFilter = Boolean(
|
|
5172
|
+
options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter || observationStatus2
|
|
5249
5173
|
);
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5174
|
+
const results = [];
|
|
5175
|
+
const sessionLabel = sessionId && sessionId !== "" ? sessionId : void 0;
|
|
5176
|
+
if (!hasContentFilter) {
|
|
5177
|
+
for (const m of filtered) {
|
|
5178
|
+
results.push({
|
|
5179
|
+
runId: m.runId,
|
|
5180
|
+
runName: m.name,
|
|
5181
|
+
runStatus: m.status,
|
|
5182
|
+
timestamp: m.startedAt,
|
|
5183
|
+
durationMs: m.durationMs,
|
|
5184
|
+
matchReason: sessionLabel ? `trace in session ${sessionLabel}` : "trace in directory",
|
|
5185
|
+
matchedFields: sessionLabel ? ["run", "session"] : ["run"],
|
|
5186
|
+
filePath: m.filePath,
|
|
5187
|
+
...sessionLabel ? { sessionId: sessionLabel } : {}
|
|
5188
|
+
});
|
|
5254
5189
|
}
|
|
5255
|
-
|
|
5190
|
+
results.sort((a, b) => {
|
|
5191
|
+
const ta = a.timestamp ?? 0;
|
|
5192
|
+
const tb = b.timestamp ?? 0;
|
|
5193
|
+
if (ta !== tb) return tb - ta;
|
|
5194
|
+
return a.runId.localeCompare(b.runId);
|
|
5195
|
+
});
|
|
5196
|
+
return results.slice(0, limit);
|
|
5256
5197
|
}
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
]
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
async function enrichSessionRunRecord(meta) {
|
|
5297
|
-
let metadata;
|
|
5298
|
-
try {
|
|
5299
|
-
const events = await readTraceEventsFromFile(meta.filePath);
|
|
5300
|
-
for (const event of events) {
|
|
5301
|
-
if (event.event !== "run_started") continue;
|
|
5302
|
-
if (event.metadata && typeof event.metadata === "object") {
|
|
5303
|
-
metadata = event.metadata;
|
|
5198
|
+
for (const m of filtered) {
|
|
5199
|
+
if (options.status && m.status !== options.status) continue;
|
|
5200
|
+
let events = [];
|
|
5201
|
+
try {
|
|
5202
|
+
events = await readTraceEventsFromFile(m.filePath);
|
|
5203
|
+
} catch {
|
|
5204
|
+
continue;
|
|
5205
|
+
}
|
|
5206
|
+
if (events.length === 0) continue;
|
|
5207
|
+
const runMatches = matchRunLevel(m, {
|
|
5208
|
+
stepTypeFilter,
|
|
5209
|
+
nameQuery,
|
|
5210
|
+
toolQuery,
|
|
5211
|
+
durationFilter,
|
|
5212
|
+
statusFilter: options.status
|
|
5213
|
+
});
|
|
5214
|
+
results.push(...runMatches);
|
|
5215
|
+
const stepMatches = matchStepLevel(m, events, {
|
|
5216
|
+
stepTypeFilter,
|
|
5217
|
+
nameQuery,
|
|
5218
|
+
toolQuery,
|
|
5219
|
+
durationFilter,
|
|
5220
|
+
statusFilter: options.status
|
|
5221
|
+
});
|
|
5222
|
+
results.push(...stepMatches);
|
|
5223
|
+
if (observationStatus2) {
|
|
5224
|
+
const outcomes = extractOutcomesFromTraceEvents(events);
|
|
5225
|
+
const matched = outcomes.filter((outcome) => outcome.status === observationStatus2);
|
|
5226
|
+
for (const outcome of matched) {
|
|
5227
|
+
results.push({
|
|
5228
|
+
runId: m.runId,
|
|
5229
|
+
runName: m.name,
|
|
5230
|
+
runStatus: m.status,
|
|
5231
|
+
stepName: outcome.name,
|
|
5232
|
+
timestamp: outcome.observedAt,
|
|
5233
|
+
matchReason: `observation status=${outcome.status}`,
|
|
5234
|
+
matchedFields: ["outcome.status", "outcome.name"],
|
|
5235
|
+
filePath: m.filePath
|
|
5236
|
+
});
|
|
5304
5237
|
}
|
|
5305
|
-
break;
|
|
5306
5238
|
}
|
|
5307
|
-
} catch {
|
|
5308
5239
|
}
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
};
|
|
5240
|
+
results.sort((a, b) => {
|
|
5241
|
+
const ta = a.timestamp ?? 0;
|
|
5242
|
+
const tb = b.timestamp ?? 0;
|
|
5243
|
+
if (ta !== tb) return tb - ta;
|
|
5244
|
+
const runCmp = a.runId.localeCompare(b.runId);
|
|
5245
|
+
if (runCmp !== 0) return runCmp;
|
|
5246
|
+
return (a.stepName ?? "").localeCompare(b.stepName ?? "");
|
|
5247
|
+
});
|
|
5248
|
+
return results.slice(0, limit);
|
|
5319
5249
|
}
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5250
|
+
function matchRunLevel(m, opts) {
|
|
5251
|
+
if (opts.stepTypeFilter || opts.toolQuery) return [];
|
|
5252
|
+
if (opts.statusFilter && m.status !== opts.statusFilter) return [];
|
|
5253
|
+
if (opts.nameQuery && !nameMatches(m.name ?? m.runId, opts.nameQuery)) {
|
|
5254
|
+
return [];
|
|
5324
5255
|
}
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
// packages/core/src/sessions/scope.ts
|
|
5329
|
-
function metaToSessionRunRecord(meta) {
|
|
5330
|
-
return {
|
|
5331
|
-
runId: meta.runId,
|
|
5332
|
-
name: meta.name,
|
|
5333
|
-
status: meta.status,
|
|
5334
|
-
startedAt: meta.startedAt,
|
|
5335
|
-
endedAt: meta.endedAt,
|
|
5336
|
-
durationMs: meta.durationMs,
|
|
5337
|
-
filePath: meta.filePath,
|
|
5338
|
-
metadata: void 0
|
|
5339
|
-
};
|
|
5340
|
-
}
|
|
5341
|
-
function filterMetasBySessionScope(metas, records, options) {
|
|
5342
|
-
const sessionId = options.sessionId?.trim();
|
|
5343
|
-
const groupId = options.groupId?.trim();
|
|
5344
|
-
const warnings = [];
|
|
5345
|
-
if (sessionId) {
|
|
5346
|
-
const index = buildSessionIndex(records, {
|
|
5347
|
-
correlateByGroupId: options.correlateByGroupId === true
|
|
5348
|
-
});
|
|
5349
|
-
warnings.push(...index.warnings);
|
|
5350
|
-
const session = index.sessions.find((item) => item.sessionId === sessionId);
|
|
5351
|
-
if (!session) {
|
|
5352
|
-
return {
|
|
5353
|
-
metas: [],
|
|
5354
|
-
scopeLabel: sessionId,
|
|
5355
|
-
scopeKind: "session",
|
|
5356
|
-
runIds: [],
|
|
5357
|
-
warnings,
|
|
5358
|
-
notFound: true
|
|
5359
|
-
};
|
|
5360
|
-
}
|
|
5361
|
-
const runIdSet = new Set(session.runIds);
|
|
5362
|
-
const filtered = metas.filter((meta) => runIdSet.has(meta.runId));
|
|
5363
|
-
return {
|
|
5364
|
-
metas: filtered,
|
|
5365
|
-
scopeLabel: sessionId,
|
|
5366
|
-
scopeKind: "session",
|
|
5367
|
-
runIds: session.runIds,
|
|
5368
|
-
warnings,
|
|
5369
|
-
notFound: false
|
|
5370
|
-
};
|
|
5256
|
+
if (opts.durationFilter && !durationMatches(m.durationMs, opts.durationFilter)) {
|
|
5257
|
+
return [];
|
|
5371
5258
|
}
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5259
|
+
const fields = [];
|
|
5260
|
+
if (opts.statusFilter) fields.push("run.status");
|
|
5261
|
+
if (opts.nameQuery) fields.push("run.name");
|
|
5262
|
+
if (opts.durationFilter) fields.push("run.durationMs");
|
|
5263
|
+
if (fields.length === 0) return [];
|
|
5264
|
+
return [
|
|
5265
|
+
{
|
|
5266
|
+
runId: m.runId,
|
|
5267
|
+
runName: m.name,
|
|
5268
|
+
runStatus: m.status,
|
|
5269
|
+
timestamp: m.startedAt,
|
|
5270
|
+
durationMs: m.durationMs,
|
|
5271
|
+
matchReason: `run match: ${fields.join(", ")}`,
|
|
5272
|
+
matchedFields: fields,
|
|
5273
|
+
filePath: m.filePath
|
|
5383
5274
|
}
|
|
5384
|
-
|
|
5385
|
-
return {
|
|
5386
|
-
metas: metas.filter((meta) => runIdSet.has(meta.runId)),
|
|
5387
|
-
scopeLabel: groupId,
|
|
5388
|
-
scopeKind: "group",
|
|
5389
|
-
runIds,
|
|
5390
|
-
warnings,
|
|
5391
|
-
notFound: false
|
|
5392
|
-
};
|
|
5393
|
-
}
|
|
5394
|
-
return {
|
|
5395
|
-
metas: [...metas],
|
|
5396
|
-
scopeLabel: "",
|
|
5397
|
-
scopeKind: "session",
|
|
5398
|
-
runIds: [],
|
|
5399
|
-
warnings,
|
|
5400
|
-
notFound: false
|
|
5401
|
-
};
|
|
5402
|
-
}
|
|
5403
|
-
function traceMetasToSessionRunRecords(metas) {
|
|
5404
|
-
return metas.map(metaToSessionRunRecord);
|
|
5275
|
+
];
|
|
5405
5276
|
}
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
const
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
const index = buildSessionIndex(runs, options);
|
|
5413
|
-
for (const session of index.sessions) {
|
|
5414
|
-
cohorts.push({
|
|
5415
|
-
key: session.sessionId,
|
|
5416
|
-
kind: "session",
|
|
5417
|
-
runIds: [...session.runIds]
|
|
5418
|
-
});
|
|
5419
|
-
}
|
|
5420
|
-
if (index.unscopedRunIds.length > 0) {
|
|
5421
|
-
cohorts.push({
|
|
5422
|
-
key: "__unscoped__",
|
|
5423
|
-
kind: "session",
|
|
5424
|
-
runIds: [...index.unscopedRunIds]
|
|
5425
|
-
});
|
|
5277
|
+
function matchStepLevel(m, events, opts) {
|
|
5278
|
+
const out = [];
|
|
5279
|
+
const started = /* @__PURE__ */ new Map();
|
|
5280
|
+
for (const e of events) {
|
|
5281
|
+
if (e.event === "step_started") {
|
|
5282
|
+
started.set(e.stepId, e);
|
|
5426
5283
|
}
|
|
5427
|
-
return cohorts.sort((a, b) => a.key.localeCompare(b.key));
|
|
5428
5284
|
}
|
|
5429
|
-
const
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
const
|
|
5433
|
-
if (!
|
|
5434
|
-
|
|
5285
|
+
for (const e of events) {
|
|
5286
|
+
if (e.event !== "step_completed") continue;
|
|
5287
|
+
const c = e;
|
|
5288
|
+
const s = started.get(c.stepId);
|
|
5289
|
+
if (!s) continue;
|
|
5290
|
+
const fields = [];
|
|
5291
|
+
const stepType = s.type;
|
|
5292
|
+
if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
|
|
5435
5293
|
continue;
|
|
5436
5294
|
}
|
|
5437
|
-
const
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5295
|
+
const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
|
|
5296
|
+
if (!hasStepFilters) continue;
|
|
5297
|
+
if (opts.statusFilter === "error" && c.status === "error") {
|
|
5298
|
+
fields.push("step.status");
|
|
5299
|
+
} else if (opts.statusFilter === "success" && c.status === "success") {
|
|
5300
|
+
fields.push("step.status");
|
|
5301
|
+
} else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
|
|
5302
|
+
continue;
|
|
5303
|
+
}
|
|
5304
|
+
if (opts.nameQuery) {
|
|
5305
|
+
if (!nameMatches(s.name, opts.nameQuery)) continue;
|
|
5306
|
+
fields.push("step.name");
|
|
5307
|
+
}
|
|
5308
|
+
if (opts.toolQuery) {
|
|
5309
|
+
const toolName2 = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
|
|
5310
|
+
if (!nameMatches(toolName2, opts.toolQuery)) continue;
|
|
5311
|
+
fields.push("step.tool");
|
|
5312
|
+
}
|
|
5313
|
+
if (opts.durationFilter) {
|
|
5314
|
+
if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
|
|
5315
|
+
fields.push("step.durationMs");
|
|
5316
|
+
}
|
|
5317
|
+
if (opts.stepTypeFilter) {
|
|
5318
|
+
fields.push("step.type");
|
|
5319
|
+
}
|
|
5320
|
+
if (fields.length === 0) continue;
|
|
5321
|
+
out.push({
|
|
5322
|
+
runId: m.runId,
|
|
5323
|
+
runName: m.name,
|
|
5324
|
+
runStatus: m.status,
|
|
5325
|
+
stepId: c.stepId,
|
|
5326
|
+
stepName: s.name,
|
|
5327
|
+
stepType,
|
|
5328
|
+
timestamp: s.startTime ?? s.timestamp,
|
|
5329
|
+
durationMs: c.durationMs,
|
|
5330
|
+
matchReason: `step match: ${fields.join(", ")}`,
|
|
5331
|
+
matchedFields: fields,
|
|
5332
|
+
filePath: m.filePath
|
|
5448
5333
|
});
|
|
5449
5334
|
}
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5335
|
+
return out;
|
|
5336
|
+
}
|
|
5337
|
+
async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
|
|
5338
|
+
const metas = [];
|
|
5339
|
+
for (const fileName of fileNames) {
|
|
5340
|
+
try {
|
|
5341
|
+
const filePath = getPath(fileName);
|
|
5342
|
+
const meta = await extractMetadata(filePath);
|
|
5343
|
+
metas.push(meta);
|
|
5344
|
+
} catch {
|
|
5345
|
+
}
|
|
5456
5346
|
}
|
|
5457
|
-
return
|
|
5347
|
+
return metas;
|
|
5458
5348
|
}
|
|
5459
5349
|
|
|
5460
|
-
// packages/core/src/sessions/
|
|
5461
|
-
function
|
|
5462
|
-
return
|
|
5350
|
+
// packages/core/src/sessions/metadata.ts
|
|
5351
|
+
function isNonEmptyString3(value) {
|
|
5352
|
+
return typeof value === "string" && value.trim() !== "";
|
|
5463
5353
|
}
|
|
5464
|
-
function
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
errors: target.errors + source.errors,
|
|
5470
|
-
rulesEvaluated: target.rulesEvaluated + source.rulesEvaluated
|
|
5471
|
-
};
|
|
5354
|
+
function finitePositiveInt(value) {
|
|
5355
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
5356
|
+
return void 0;
|
|
5357
|
+
}
|
|
5358
|
+
return Math.trunc(value);
|
|
5472
5359
|
}
|
|
5473
|
-
function
|
|
5474
|
-
|
|
5360
|
+
function extractSessionWorkflowMetadata(record) {
|
|
5361
|
+
if (!record) return void 0;
|
|
5362
|
+
const out = {};
|
|
5363
|
+
let found = false;
|
|
5364
|
+
const assignString = (key, value) => {
|
|
5365
|
+
if (isNonEmptyString3(value)) {
|
|
5366
|
+
out[key] = value.trim();
|
|
5367
|
+
found = true;
|
|
5368
|
+
}
|
|
5369
|
+
};
|
|
5370
|
+
assignString("sessionId", record.sessionId);
|
|
5371
|
+
assignString("conversationId", record.conversationId);
|
|
5372
|
+
assignString("groupId", record.groupId);
|
|
5373
|
+
assignString("parentGroupId", record.parentGroupId);
|
|
5374
|
+
assignString("retryOf", record.retryOf);
|
|
5375
|
+
assignString("retryReason", record.retryReason);
|
|
5376
|
+
assignString("handoffFrom", record.handoffFrom);
|
|
5377
|
+
assignString("handoffTo", record.handoffTo);
|
|
5378
|
+
assignString("subAgentId", record.subAgentId);
|
|
5379
|
+
assignString("subAgentName", record.subAgentName);
|
|
5380
|
+
assignString("jobId", record.jobId);
|
|
5381
|
+
assignString("queueName", record.queueName);
|
|
5382
|
+
assignString("workflowName", record.workflowName);
|
|
5383
|
+
assignString("workflowStep", record.workflowStep);
|
|
5384
|
+
assignString("toolCallId", record.toolCallId);
|
|
5385
|
+
assignString("mcpToolCallId", record.mcpToolCallId);
|
|
5386
|
+
assignString("linkedStepId", record.linkedStepId);
|
|
5387
|
+
assignString("operationId", record.operationId);
|
|
5388
|
+
assignString("attemptId", record.attemptId);
|
|
5389
|
+
assignString("fallbackOf", record.fallbackOf);
|
|
5390
|
+
assignString("idempotencyKey", record.idempotencyKey);
|
|
5391
|
+
assignString("correlationId", record.correlationId);
|
|
5392
|
+
assignString("requestId", record.requestId);
|
|
5393
|
+
assignString("decisionId", record.decisionId);
|
|
5394
|
+
const attempt = finitePositiveInt(record.attempt);
|
|
5395
|
+
if (attempt !== void 0) {
|
|
5396
|
+
out.attempt = attempt;
|
|
5397
|
+
found = true;
|
|
5398
|
+
}
|
|
5399
|
+
const attemptNumber = finitePositiveInt(record.attemptNumber);
|
|
5400
|
+
if (attemptNumber !== void 0) {
|
|
5401
|
+
out.attemptNumber = attemptNumber;
|
|
5402
|
+
found = true;
|
|
5403
|
+
}
|
|
5404
|
+
return found ? out : void 0;
|
|
5475
5405
|
}
|
|
5476
|
-
function
|
|
5477
|
-
if (
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
status: "error",
|
|
5481
|
-
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
5482
|
-
scopeKind: scope.scopeKind,
|
|
5483
|
-
scopeLabel: scope.scopeLabel,
|
|
5484
|
-
runIds: [],
|
|
5485
|
-
runResults: [],
|
|
5486
|
-
summary: { ...emptySummary(), errors: 1 },
|
|
5487
|
-
findings: [],
|
|
5488
|
-
diagnostics: [
|
|
5489
|
-
sessionDiagnostic(
|
|
5490
|
-
"AI_CHECK_INVALID_ARGUMENTS",
|
|
5491
|
-
`${scope.scopeKind} not found: ${scope.scopeLabel}`
|
|
5492
|
-
)
|
|
5493
|
-
],
|
|
5494
|
-
ruleExecutions: [],
|
|
5495
|
-
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
5496
|
-
};
|
|
5406
|
+
function sessionKeyForRun(meta, options) {
|
|
5407
|
+
if (meta?.sessionId) return meta.sessionId;
|
|
5408
|
+
if (options?.correlateByGroupId && meta?.groupId) {
|
|
5409
|
+
return `group:${meta.groupId}`;
|
|
5497
5410
|
}
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5411
|
+
return void 0;
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
// packages/core/src/sessions/status.ts
|
|
5415
|
+
var DEFAULT_STALE_THRESHOLD_MS = 864e5;
|
|
5416
|
+
var EXPLICIT_STATUS_PRIORITY = {
|
|
5417
|
+
error: 5,
|
|
5418
|
+
waiting_input: 4,
|
|
5419
|
+
idle: 3,
|
|
5420
|
+
stale: 2,
|
|
5421
|
+
completed: 1
|
|
5422
|
+
};
|
|
5423
|
+
var EXPLICIT_SESSION_STATUSES = /* @__PURE__ */ new Set([
|
|
5424
|
+
"running",
|
|
5425
|
+
"waiting_input",
|
|
5426
|
+
"idle",
|
|
5427
|
+
"completed",
|
|
5428
|
+
"error",
|
|
5429
|
+
"stale",
|
|
5430
|
+
"unknown"
|
|
5431
|
+
]);
|
|
5432
|
+
function isExplicitSessionStatus(value) {
|
|
5433
|
+
return typeof value === "string" && EXPLICIT_SESSION_STATUSES.has(value);
|
|
5434
|
+
}
|
|
5435
|
+
function activityMs(run) {
|
|
5436
|
+
return run.endedAt ?? run.startedAt ?? 0;
|
|
5437
|
+
}
|
|
5438
|
+
function latestActivityMs(runs) {
|
|
5439
|
+
let latest = 0;
|
|
5440
|
+
for (const run of runs) {
|
|
5441
|
+
const ms = activityMs(run);
|
|
5442
|
+
if (ms > latest) latest = ms;
|
|
5518
5443
|
}
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
const
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
diagnostics.push(...result.diagnostics);
|
|
5528
|
-
ruleExecutions.push(...result.ruleExecutions ?? []);
|
|
5529
|
-
if (result.runId) {
|
|
5530
|
-
runResults.push({ runId: result.runId, status: result.status });
|
|
5444
|
+
return latest;
|
|
5445
|
+
}
|
|
5446
|
+
function earliestStart(runs) {
|
|
5447
|
+
let earliest;
|
|
5448
|
+
for (const run of runs) {
|
|
5449
|
+
if (run.startedAt === void 0) continue;
|
|
5450
|
+
if (earliest === void 0 || run.startedAt < earliest) {
|
|
5451
|
+
earliest = run.startedAt;
|
|
5531
5452
|
}
|
|
5532
5453
|
}
|
|
5533
|
-
|
|
5534
|
-
findings.sort((a, b) => {
|
|
5535
|
-
const runCmp = (a.evidence[0]?.runId ?? "").localeCompare(
|
|
5536
|
-
b.evidence[0]?.runId ?? ""
|
|
5537
|
-
);
|
|
5538
|
-
if (runCmp !== 0) return runCmp;
|
|
5539
|
-
return a.ruleId.localeCompare(b.ruleId);
|
|
5540
|
-
});
|
|
5541
|
-
ruleExecutions.sort((a, b) => {
|
|
5542
|
-
const runCmp = (a.runId ?? "").localeCompare(b.runId ?? "");
|
|
5543
|
-
if (runCmp !== 0) return runCmp;
|
|
5544
|
-
return a.ruleId.localeCompare(b.ruleId);
|
|
5545
|
-
});
|
|
5546
|
-
const hasErrors = diagnostics.some((item) => item.severity === "error");
|
|
5547
|
-
const status = hasErrors ? "error" : summary.failed > 0 ? "fail" : "pass";
|
|
5548
|
-
return {
|
|
5549
|
-
ok: status === "pass",
|
|
5550
|
-
status,
|
|
5551
|
-
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
5552
|
-
scopeKind: scope.scopeKind,
|
|
5553
|
-
scopeLabel: scope.scopeLabel,
|
|
5554
|
-
runIds: scope.runIds,
|
|
5555
|
-
runResults,
|
|
5556
|
-
summary,
|
|
5557
|
-
findings,
|
|
5558
|
-
diagnostics,
|
|
5559
|
-
ruleExecutions,
|
|
5560
|
-
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
5561
|
-
};
|
|
5454
|
+
return earliest;
|
|
5562
5455
|
}
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
const
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5456
|
+
function latestEndWhenAllEnded(runs) {
|
|
5457
|
+
if (runs.length === 0) return void 0;
|
|
5458
|
+
let latest;
|
|
5459
|
+
for (const run of runs) {
|
|
5460
|
+
if (run.endedAt === void 0) return void 0;
|
|
5461
|
+
if (latest === void 0 || run.endedAt > latest) latest = run.endedAt;
|
|
5462
|
+
}
|
|
5463
|
+
return latest;
|
|
5570
5464
|
}
|
|
5571
|
-
function
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
groupId,
|
|
5582
|
-
parentGroupId: metaByRunId.get(runId)?.parentGroupId,
|
|
5583
|
-
runIds: [runId]
|
|
5584
|
-
});
|
|
5465
|
+
function pickExplicitStatus(runs) {
|
|
5466
|
+
let best;
|
|
5467
|
+
let bestPriority = 0;
|
|
5468
|
+
for (const run of runs) {
|
|
5469
|
+
const raw = run.metadata?.sessionStatus;
|
|
5470
|
+
if (!isExplicitSessionStatus(raw)) continue;
|
|
5471
|
+
const priority = EXPLICIT_STATUS_PRIORITY[raw] ?? 0;
|
|
5472
|
+
if (priority > bestPriority) {
|
|
5473
|
+
bestPriority = priority;
|
|
5474
|
+
best = raw;
|
|
5585
5475
|
}
|
|
5586
5476
|
}
|
|
5587
|
-
return
|
|
5588
|
-
...group,
|
|
5589
|
-
runIds: [...group.runIds].sort((a, b) => a.localeCompare(b))
|
|
5590
|
-
}));
|
|
5477
|
+
return best;
|
|
5591
5478
|
}
|
|
5592
|
-
function
|
|
5593
|
-
const
|
|
5594
|
-
const
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5479
|
+
function deriveLastError(runs) {
|
|
5480
|
+
const errorRuns = runs.filter((run) => run.status === "error").sort((a, b) => activityMs(b) - activityMs(a));
|
|
5481
|
+
const latest = errorRuns[0];
|
|
5482
|
+
if (!latest) return void 0;
|
|
5483
|
+
const meta = latest.metadata ?? {};
|
|
5484
|
+
const message = typeof meta.errorMessage === "string" && meta.errorMessage.trim() !== "" ? meta.errorMessage.trim() : latest.name ?? latest.runId;
|
|
5485
|
+
const code = typeof meta.errorCode === "string" && meta.errorCode.trim() !== "" ? meta.errorCode.trim() : void 0;
|
|
5486
|
+
return { runId: latest.runId, message, code };
|
|
5487
|
+
}
|
|
5488
|
+
function deriveCheckSummary(runs) {
|
|
5489
|
+
let pass = 0;
|
|
5490
|
+
let fail = 0;
|
|
5491
|
+
let warn2 = 0;
|
|
5492
|
+
let found = false;
|
|
5493
|
+
for (const run of runs) {
|
|
5494
|
+
const summary = run.metadata?.checkSummary;
|
|
5495
|
+
if (!summary || typeof summary !== "object") continue;
|
|
5496
|
+
const record = summary;
|
|
5497
|
+
if (typeof record.pass === "number") {
|
|
5498
|
+
pass += record.pass;
|
|
5499
|
+
found = true;
|
|
5612
5500
|
}
|
|
5613
|
-
if (
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
to: runId,
|
|
5617
|
-
source: "manual",
|
|
5618
|
-
confidence: "explicit"
|
|
5619
|
-
});
|
|
5501
|
+
if (typeof record.fail === "number") {
|
|
5502
|
+
fail += record.fail;
|
|
5503
|
+
found = true;
|
|
5620
5504
|
}
|
|
5621
|
-
if (
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
to: meta.handoffTo,
|
|
5625
|
-
source: "manual",
|
|
5626
|
-
confidence: "explicit"
|
|
5627
|
-
});
|
|
5505
|
+
if (typeof record.warn === "number") {
|
|
5506
|
+
warn2 += record.warn;
|
|
5507
|
+
found = true;
|
|
5628
5508
|
}
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
code: "ambiguous-handoff-endpoints",
|
|
5638
|
-
message: "Handoff inferred from parentGroupId and subAgentId without explicit handoffFrom/handoffTo.",
|
|
5639
|
-
runId,
|
|
5640
|
-
sessionId
|
|
5641
|
-
});
|
|
5509
|
+
}
|
|
5510
|
+
return found ? { pass, fail, warn: warn2 } : void 0;
|
|
5511
|
+
}
|
|
5512
|
+
function deriveObservationSummary(runs) {
|
|
5513
|
+
for (const run of [...runs].sort((a, b) => activityMs(b) - activityMs(a))) {
|
|
5514
|
+
const value = run.metadata?.observationSummary;
|
|
5515
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
5516
|
+
return value.trim();
|
|
5642
5517
|
}
|
|
5643
5518
|
}
|
|
5644
|
-
return
|
|
5645
|
-
const from = a.from.localeCompare(b.from);
|
|
5646
|
-
if (from !== 0) return from;
|
|
5647
|
-
return a.to.localeCompare(b.to);
|
|
5648
|
-
});
|
|
5519
|
+
return void 0;
|
|
5649
5520
|
}
|
|
5650
|
-
function
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
});
|
|
5663
|
-
continue;
|
|
5664
|
-
}
|
|
5665
|
-
if (meta.attempt !== void 0 && meta.attempt > 1) {
|
|
5666
|
-
retries.push({
|
|
5667
|
-
runId,
|
|
5668
|
-
attempt: meta.attempt,
|
|
5669
|
-
source: "inferred",
|
|
5670
|
-
confidence: "correlated"
|
|
5671
|
-
});
|
|
5672
|
-
warnings.push({
|
|
5673
|
-
code: "ambiguous-retry-link",
|
|
5674
|
-
message: "attempt > 1 without retryOf; retry link is correlated only.",
|
|
5675
|
-
runId,
|
|
5676
|
-
sessionId
|
|
5677
|
-
});
|
|
5678
|
-
}
|
|
5679
|
-
}
|
|
5680
|
-
return retries.sort((a, b) => a.runId.localeCompare(b.runId));
|
|
5521
|
+
function deriveSessionStatus(runs, options = {}) {
|
|
5522
|
+
if (runs.length === 0) return "unknown";
|
|
5523
|
+
if (runs.some((run) => run.status === "running")) return "running";
|
|
5524
|
+
const explicit = pickExplicitStatus(runs);
|
|
5525
|
+
if (explicit && explicit !== "running") return explicit;
|
|
5526
|
+
if (runs.some((run) => run.status === "error")) return "error";
|
|
5527
|
+
if (runs.every((run) => run.status === "success")) return "completed";
|
|
5528
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
5529
|
+
const staleThresholdMs = options.staleThresholdMs ?? DEFAULT_STALE_THRESHOLD_MS;
|
|
5530
|
+
const lastMs = latestActivityMs(runs);
|
|
5531
|
+
if (lastMs > 0 && nowMs - lastMs > staleThresholdMs) return "stale";
|
|
5532
|
+
return "unknown";
|
|
5681
5533
|
}
|
|
5682
|
-
function
|
|
5683
|
-
const
|
|
5684
|
-
const
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
const
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
if (
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5534
|
+
function enrichSessionSummary(summary, runs, options = {}) {
|
|
5535
|
+
const sessionRuns = runs.filter((run) => summary.runIds.includes(run.runId)).sort((a, b) => a.runId.localeCompare(b.runId));
|
|
5536
|
+
const startedAt = earliestStart(sessionRuns);
|
|
5537
|
+
const endedAt = latestEndWhenAllEnded(sessionRuns);
|
|
5538
|
+
const durationMs2 = startedAt !== void 0 && endedAt !== void 0 ? endedAt - startedAt : void 0;
|
|
5539
|
+
let correlationId;
|
|
5540
|
+
let jobId;
|
|
5541
|
+
let workflowId;
|
|
5542
|
+
for (const run of sessionRuns) {
|
|
5543
|
+
const meta = extractSessionWorkflowMetadata(run.metadata);
|
|
5544
|
+
if (!correlationId && meta?.correlationId) correlationId = meta.correlationId;
|
|
5545
|
+
if (!jobId && meta?.jobId) jobId = meta.jobId;
|
|
5546
|
+
if (!workflowId && meta?.workflowName) workflowId = meta.workflowName;
|
|
5547
|
+
else if (!workflowId && meta?.workflowStep) workflowId = meta.workflowStep;
|
|
5548
|
+
}
|
|
5549
|
+
const lastMs = latestActivityMs(sessionRuns);
|
|
5550
|
+
const lastActivity = lastMs > 0 ? new Date(lastMs).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString();
|
|
5551
|
+
const retryCount2 = summary.retries.filter(
|
|
5552
|
+
(retry) => retry.retryOf !== void 0 || (retry.attempt ?? 0) > 1
|
|
5553
|
+
).length;
|
|
5554
|
+
return {
|
|
5555
|
+
...summary,
|
|
5556
|
+
status: deriveSessionStatus(sessionRuns, options),
|
|
5557
|
+
startedAt,
|
|
5558
|
+
endedAt,
|
|
5559
|
+
durationMs: durationMs2,
|
|
5560
|
+
correlationId,
|
|
5561
|
+
jobId,
|
|
5562
|
+
workflowId,
|
|
5563
|
+
lastError: deriveLastError(sessionRuns),
|
|
5564
|
+
lastActivity,
|
|
5565
|
+
retryCount: retryCount2,
|
|
5566
|
+
observationSummary: deriveObservationSummary(sessionRuns),
|
|
5567
|
+
checkSummary: deriveCheckSummary(sessionRuns)
|
|
5704
5568
|
};
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
if (fromRun) pushRun(fromRun, "explicit", "manual");
|
|
5714
|
-
if (toRun) pushRun(toRun, "explicit", "manual");
|
|
5569
|
+
}
|
|
5570
|
+
|
|
5571
|
+
// packages/core/src/sessions/activity.ts
|
|
5572
|
+
function statusLine(session) {
|
|
5573
|
+
const name = session.workflowId ?? session.correlationId ?? session.sessionId;
|
|
5574
|
+
const status = session.status;
|
|
5575
|
+
if (session.lastError) {
|
|
5576
|
+
return `${name} session ${session.sessionId} failed at ${session.lastError.message}`;
|
|
5715
5577
|
}
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
|
|
5719
|
-
pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
|
|
5578
|
+
if (session.observationSummary) {
|
|
5579
|
+
return `${name} session ${session.sessionId} ${status} with observation warning`;
|
|
5720
5580
|
}
|
|
5721
|
-
return
|
|
5581
|
+
return `${name} session ${session.sessionId} ${status}`;
|
|
5722
5582
|
}
|
|
5723
|
-
function
|
|
5724
|
-
|
|
5725
|
-
|
|
5583
|
+
function parseSinceMs(since, nowMs) {
|
|
5584
|
+
if (!since || since.trim() === "") return nowMs - 7 * 864e5;
|
|
5585
|
+
const trimmed = since.trim().toLowerCase();
|
|
5586
|
+
const match = /^(\d+)([smhd])$/.exec(trimmed);
|
|
5587
|
+
if (!match) return nowMs - 7 * 864e5;
|
|
5588
|
+
const amount = Number.parseInt(match[1], 10);
|
|
5589
|
+
const unit = match[2];
|
|
5590
|
+
const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
|
|
5591
|
+
return nowMs - amount * mult;
|
|
5726
5592
|
}
|
|
5727
|
-
function
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
const
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
bucket.push(run);
|
|
5747
|
-
sessionsByKey.set(key, bucket);
|
|
5748
|
-
}
|
|
5749
|
-
const sessions = [...sessionsByKey.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([sessionId, sessionRuns]) => {
|
|
5750
|
-
const runIds = sessionRuns.map((run) => run.runId).sort();
|
|
5751
|
-
const handoffs = buildHandoffs(runIds, metaByRunId, warnings, sessionId);
|
|
5752
|
-
const retries = buildRetries(runIds, metaByRunId, warnings, sessionId);
|
|
5753
|
-
const groups = buildGroups(runIds, metaByRunId);
|
|
5754
|
-
const criticalPath = buildCriticalPath(sessionRuns, handoffs);
|
|
5755
|
-
const confidences = new Set(handoffs.map((edge) => edge.confidence));
|
|
5756
|
-
if (confidences.has("explicit") && confidences.has("correlated")) {
|
|
5757
|
-
warnings.push({
|
|
5758
|
-
code: "mixed-confidence-group",
|
|
5759
|
-
message: "Session aggregates explicit and correlated handoff edges.",
|
|
5760
|
-
sessionId
|
|
5761
|
-
});
|
|
5762
|
-
}
|
|
5763
|
-
return enrichSessionSummary(
|
|
5764
|
-
{
|
|
5765
|
-
sessionId,
|
|
5766
|
-
runIds,
|
|
5767
|
-
groups,
|
|
5768
|
-
handoffs,
|
|
5769
|
-
retries,
|
|
5770
|
-
criticalPath
|
|
5771
|
-
},
|
|
5772
|
-
runs,
|
|
5773
|
-
{
|
|
5774
|
-
nowMs: options.nowMs,
|
|
5775
|
-
staleThresholdMs: options.staleThresholdMs
|
|
5776
|
-
}
|
|
5777
|
-
);
|
|
5593
|
+
function isFailed(status) {
|
|
5594
|
+
return status === "error";
|
|
5595
|
+
}
|
|
5596
|
+
function isStale(status) {
|
|
5597
|
+
return status === "stale";
|
|
5598
|
+
}
|
|
5599
|
+
function guardrailWarnings(session) {
|
|
5600
|
+
const summary = session.checkSummary;
|
|
5601
|
+
if (!summary) return 0;
|
|
5602
|
+
return summary.warn;
|
|
5603
|
+
}
|
|
5604
|
+
function buildActivitySummary(index, options = {}) {
|
|
5605
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
5606
|
+
const sinceMs = parseSinceMs(options.since, nowMs);
|
|
5607
|
+
const sinceIso = new Date(sinceMs).toISOString();
|
|
5608
|
+
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 20;
|
|
5609
|
+
const inWindow = index.sessions.filter((session) => {
|
|
5610
|
+
const activityMs2 = Date.parse(session.lastActivity);
|
|
5611
|
+
return Number.isFinite(activityMs2) && activityMs2 >= sinceMs;
|
|
5778
5612
|
});
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5613
|
+
const entries = [...inWindow].sort((a, b) => Date.parse(b.lastActivity) - Date.parse(a.lastActivity)).slice(0, limit).map((session) => ({
|
|
5614
|
+
sessionId: session.sessionId,
|
|
5615
|
+
status: session.status,
|
|
5616
|
+
summary: statusLine(session),
|
|
5617
|
+
lastActivity: session.lastActivity,
|
|
5618
|
+
runCount: session.runIds.length
|
|
5619
|
+
}));
|
|
5620
|
+
let failed = 0;
|
|
5621
|
+
let stale = 0;
|
|
5622
|
+
let guardrailWarningTotal = 0;
|
|
5623
|
+
for (const session of inWindow) {
|
|
5624
|
+
if (isFailed(session.status)) failed += 1;
|
|
5625
|
+
if (isStale(session.status)) stale += 1;
|
|
5626
|
+
guardrailWarningTotal += guardrailWarnings(session);
|
|
5784
5627
|
}
|
|
5785
|
-
warnings.sort((a, b) => {
|
|
5786
|
-
const code = a.code.localeCompare(b.code);
|
|
5787
|
-
if (code !== 0) return code;
|
|
5788
|
-
return (a.runId ?? "").localeCompare(b.runId ?? "");
|
|
5789
|
-
});
|
|
5790
5628
|
return {
|
|
5791
|
-
|
|
5792
|
-
sessions,
|
|
5793
|
-
|
|
5794
|
-
|
|
5629
|
+
since: sinceIso,
|
|
5630
|
+
sessions: inWindow.length,
|
|
5631
|
+
failed,
|
|
5632
|
+
stale,
|
|
5633
|
+
guardrailWarnings: guardrailWarningTotal,
|
|
5634
|
+
entries
|
|
5795
5635
|
};
|
|
5796
5636
|
}
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
}
|
|
5810
|
-
return void 0;
|
|
5811
|
-
}
|
|
5812
|
-
}
|
|
5813
|
-
async function isAgentInspectTrace(filePath) {
|
|
5814
|
-
try {
|
|
5815
|
-
const rl = readline.createInterface({
|
|
5816
|
-
input: fs.createReadStream(filePath, { encoding: "utf8" }),
|
|
5817
|
-
crlfDelay: Infinity
|
|
5818
|
-
});
|
|
5819
|
-
let checked = 0;
|
|
5820
|
-
for await (const line of rl) {
|
|
5821
|
-
const trimmed = line.trim();
|
|
5822
|
-
if (trimmed === "") continue;
|
|
5823
|
-
const parsed = safeParse(trimmed);
|
|
5824
|
-
if (!parsed) continue;
|
|
5825
|
-
if (!isRecord7(parsed)) continue;
|
|
5826
|
-
checked += 1;
|
|
5827
|
-
if (isTraceEvent(parsed)) return true;
|
|
5828
|
-
const ev = parsed.event;
|
|
5829
|
-
const runId = parsed.runId;
|
|
5830
|
-
if (typeof ev === "string" && KNOWN_EVENTS.has(ev) && typeof runId === "string") {
|
|
5831
|
-
return true;
|
|
5832
|
-
}
|
|
5833
|
-
if (checked >= 20) break;
|
|
5637
|
+
function renderActivitySummaryHuman(summary, options) {
|
|
5638
|
+
const lines = [];
|
|
5639
|
+
const nowMs = options?.nowMs ?? Date.now();
|
|
5640
|
+
const todayStart = new Date(nowMs);
|
|
5641
|
+
todayStart.setHours(0, 0, 0, 0);
|
|
5642
|
+
const todayMs = todayStart.getTime();
|
|
5643
|
+
const today = summary.entries.filter(
|
|
5644
|
+
(entry) => Date.parse(entry.lastActivity) >= todayMs
|
|
5645
|
+
);
|
|
5646
|
+
if (today.length > 0) {
|
|
5647
|
+
lines.push("Today");
|
|
5648
|
+
for (const entry of today) {
|
|
5649
|
+
lines.push(` ${entry.summary}`);
|
|
5834
5650
|
}
|
|
5835
|
-
|
|
5836
|
-
} catch {
|
|
5837
|
-
return false;
|
|
5651
|
+
lines.push("");
|
|
5838
5652
|
}
|
|
5653
|
+
lines.push(`Since ${summary.since}`);
|
|
5654
|
+
lines.push(` ${summary.sessions} sessions`);
|
|
5655
|
+
lines.push(` ${summary.failed} failed`);
|
|
5656
|
+
lines.push(` ${summary.stale} stale`);
|
|
5657
|
+
lines.push(` ${summary.guardrailWarnings} guardrail warnings`);
|
|
5658
|
+
return lines.join("\n");
|
|
5839
5659
|
}
|
|
5840
5660
|
|
|
5841
|
-
// packages/core/src/
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5661
|
+
// packages/core/src/sessions/types.ts
|
|
5662
|
+
var SESSION_WORKFLOW_KEYS = [
|
|
5663
|
+
"sessionId",
|
|
5664
|
+
"conversationId",
|
|
5665
|
+
"groupId",
|
|
5666
|
+
"parentGroupId",
|
|
5667
|
+
"attempt",
|
|
5668
|
+
"retryOf",
|
|
5669
|
+
"retryReason",
|
|
5670
|
+
"handoffFrom",
|
|
5671
|
+
"handoffTo",
|
|
5672
|
+
"subAgentId",
|
|
5673
|
+
"subAgentName",
|
|
5674
|
+
"jobId",
|
|
5675
|
+
"queueName",
|
|
5676
|
+
"workflowName",
|
|
5677
|
+
"workflowStep",
|
|
5678
|
+
"toolCallId",
|
|
5679
|
+
"mcpToolCallId",
|
|
5680
|
+
"linkedStepId",
|
|
5681
|
+
"operationId",
|
|
5682
|
+
"attemptId",
|
|
5683
|
+
"attemptNumber",
|
|
5684
|
+
"fallbackOf",
|
|
5685
|
+
"idempotencyKey",
|
|
5686
|
+
"correlationId",
|
|
5687
|
+
"requestId",
|
|
5688
|
+
"decisionId"
|
|
5689
|
+
];
|
|
5690
|
+
|
|
5691
|
+
// packages/core/src/sessions/load.ts
|
|
5692
|
+
async function enrichSessionRunRecord(meta) {
|
|
5693
|
+
let metadata;
|
|
5694
|
+
try {
|
|
5695
|
+
const events = await readTraceEventsFromFile(meta.filePath);
|
|
5696
|
+
for (const event of events) {
|
|
5697
|
+
if (event.event !== "run_started") continue;
|
|
5698
|
+
if (event.metadata && typeof event.metadata === "object") {
|
|
5699
|
+
metadata = event.metadata;
|
|
5700
|
+
}
|
|
5701
|
+
break;
|
|
5702
|
+
}
|
|
5703
|
+
} catch {
|
|
5846
5704
|
}
|
|
5847
|
-
return
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5705
|
+
return {
|
|
5706
|
+
runId: meta.runId,
|
|
5707
|
+
name: meta.name,
|
|
5708
|
+
status: meta.status,
|
|
5709
|
+
startedAt: meta.startedAt,
|
|
5710
|
+
endedAt: meta.endedAt,
|
|
5711
|
+
durationMs: meta.durationMs,
|
|
5712
|
+
filePath: meta.filePath,
|
|
5713
|
+
metadata
|
|
5714
|
+
};
|
|
5853
5715
|
}
|
|
5854
|
-
function
|
|
5855
|
-
const
|
|
5856
|
-
const
|
|
5857
|
-
|
|
5858
|
-
const activity = runActivityMs(run);
|
|
5859
|
-
if (activity !== void 0 && activity >= cutoff) {
|
|
5860
|
-
ids.push(run.runId);
|
|
5861
|
-
}
|
|
5716
|
+
async function loadSessionRunRecords(metas) {
|
|
5717
|
+
const out = [];
|
|
5718
|
+
for (const meta of metas) {
|
|
5719
|
+
out.push(await enrichSessionRunRecord(meta));
|
|
5862
5720
|
}
|
|
5863
|
-
return
|
|
5721
|
+
return out;
|
|
5864
5722
|
}
|
|
5865
|
-
|
|
5866
|
-
|
|
5723
|
+
|
|
5724
|
+
// packages/core/src/sessions/scope.ts
|
|
5725
|
+
function metaToSessionRunRecord(meta) {
|
|
5726
|
+
return {
|
|
5727
|
+
runId: meta.runId,
|
|
5728
|
+
name: meta.name,
|
|
5729
|
+
status: meta.status,
|
|
5730
|
+
startedAt: meta.startedAt,
|
|
5731
|
+
endedAt: meta.endedAt,
|
|
5732
|
+
durationMs: meta.durationMs,
|
|
5733
|
+
filePath: meta.filePath,
|
|
5734
|
+
metadata: void 0
|
|
5735
|
+
};
|
|
5867
5736
|
}
|
|
5868
|
-
function
|
|
5869
|
-
const runId = options.runId?.trim();
|
|
5737
|
+
function filterMetasBySessionScope(metas, records, options) {
|
|
5870
5738
|
const sessionId = options.sessionId?.trim();
|
|
5871
|
-
const
|
|
5872
|
-
const
|
|
5873
|
-
if (modes === 0) {
|
|
5874
|
-
throw new Error(
|
|
5875
|
-
"bundle requires a run id, --session <sessionId>, or --since <duration>."
|
|
5876
|
-
);
|
|
5877
|
-
}
|
|
5878
|
-
if (modes > 1) {
|
|
5879
|
-
throw new Error(
|
|
5880
|
-
"bundle accepts only one target: a run id, --session, or --since (not combined)."
|
|
5881
|
-
);
|
|
5882
|
-
}
|
|
5883
|
-
if (runId) {
|
|
5884
|
-
const known = runs.some((run) => run.runId === runId);
|
|
5885
|
-
if (!known) {
|
|
5886
|
-
throw new Error(`Run "${runId}" was not found in the trace directory.`);
|
|
5887
|
-
}
|
|
5888
|
-
return { runIds: [runId] };
|
|
5889
|
-
}
|
|
5739
|
+
const groupId = options.groupId?.trim();
|
|
5740
|
+
const warnings = [];
|
|
5890
5741
|
if (sessionId) {
|
|
5891
|
-
const
|
|
5742
|
+
const index = buildSessionIndex(records, {
|
|
5743
|
+
correlateByGroupId: options.correlateByGroupId === true
|
|
5744
|
+
});
|
|
5745
|
+
warnings.push(...index.warnings);
|
|
5746
|
+
const session = index.sessions.find((item) => item.sessionId === sessionId);
|
|
5892
5747
|
if (!session) {
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5748
|
+
return {
|
|
5749
|
+
metas: [],
|
|
5750
|
+
scopeLabel: sessionId,
|
|
5751
|
+
scopeKind: "session",
|
|
5752
|
+
runIds: [],
|
|
5753
|
+
warnings,
|
|
5754
|
+
notFound: true
|
|
5755
|
+
};
|
|
5897
5756
|
}
|
|
5757
|
+
const runIdSet = new Set(session.runIds);
|
|
5758
|
+
const filtered = metas.filter((meta) => runIdSet.has(meta.runId));
|
|
5898
5759
|
return {
|
|
5899
|
-
|
|
5900
|
-
sessionId
|
|
5760
|
+
metas: filtered,
|
|
5761
|
+
scopeLabel: sessionId,
|
|
5762
|
+
scopeKind: "session",
|
|
5763
|
+
runIds: session.runIds,
|
|
5764
|
+
warnings,
|
|
5765
|
+
notFound: false
|
|
5901
5766
|
};
|
|
5902
5767
|
}
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5768
|
+
if (groupId) {
|
|
5769
|
+
const runIds = records.filter((run) => extractSessionWorkflowMetadata(run.metadata)?.groupId === groupId).map((run) => run.runId).sort();
|
|
5770
|
+
if (runIds.length === 0) {
|
|
5771
|
+
return {
|
|
5772
|
+
metas: [],
|
|
5773
|
+
scopeLabel: groupId,
|
|
5774
|
+
scopeKind: "group",
|
|
5775
|
+
runIds: [],
|
|
5776
|
+
warnings,
|
|
5777
|
+
notFound: true
|
|
5778
|
+
};
|
|
5779
|
+
}
|
|
5780
|
+
const runIdSet = new Set(runIds);
|
|
5781
|
+
return {
|
|
5782
|
+
metas: metas.filter((meta) => runIdSet.has(meta.runId)),
|
|
5783
|
+
scopeLabel: groupId,
|
|
5784
|
+
scopeKind: "group",
|
|
5785
|
+
runIds,
|
|
5786
|
+
warnings,
|
|
5787
|
+
notFound: false
|
|
5788
|
+
};
|
|
5906
5789
|
}
|
|
5907
|
-
return { runIds, since };
|
|
5908
|
-
}
|
|
5909
|
-
|
|
5910
|
-
// packages/core/src/bundle/safety-status.ts
|
|
5911
|
-
function aggregateBundleSafeStatus(statuses) {
|
|
5912
|
-
if (statuses.length === 0) return "UNKNOWN";
|
|
5913
|
-
if (statuses.some((status) => status === "UNSAFE")) return "UNSAFE";
|
|
5914
|
-
if (statuses.some((status) => status === "UNKNOWN")) return "UNKNOWN";
|
|
5915
|
-
if (statuses.some((status) => status === "SAFE WITH WARNINGS")) return "SAFE WITH WARNINGS";
|
|
5916
|
-
return "SAFE";
|
|
5917
|
-
}
|
|
5918
|
-
function toMetadataSafeStatus(status) {
|
|
5919
|
-
if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
|
|
5920
|
-
return status;
|
|
5921
|
-
}
|
|
5922
|
-
function bundleFailsOnSafety(status, allowUnsafe) {
|
|
5923
|
-
if (allowUnsafe) return false;
|
|
5924
|
-
return status === "UNSAFE" || status === "UNKNOWN";
|
|
5925
|
-
}
|
|
5926
|
-
|
|
5927
|
-
// packages/core/src/bundle/manifest.ts
|
|
5928
|
-
var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
|
|
5929
|
-
var PLACEHOLDER_NOTE = "No eval or performance artifacts were requested for this bundle.";
|
|
5930
|
-
function buildBundleMetadata(parts) {
|
|
5931
|
-
const aggregate = aggregateBundleSafeStatus(
|
|
5932
|
-
parts.checks.runs.map((run) => run.status)
|
|
5933
|
-
);
|
|
5934
5790
|
return {
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
files: [...parts.files].sort((a, b) => a.localeCompare(b)),
|
|
5942
|
-
note: BUNDLE_NOTE,
|
|
5943
|
-
...parts.resolve.sessionId !== void 0 ? { sessionId: parts.resolve.sessionId } : {},
|
|
5944
|
-
...parts.resolve.since !== void 0 ? { since: parts.resolve.since } : {}
|
|
5791
|
+
metas: [...metas],
|
|
5792
|
+
scopeLabel: "",
|
|
5793
|
+
scopeKind: "session",
|
|
5794
|
+
runIds: [],
|
|
5795
|
+
warnings,
|
|
5796
|
+
notFound: false
|
|
5945
5797
|
};
|
|
5946
5798
|
}
|
|
5947
|
-
function
|
|
5948
|
-
return
|
|
5949
|
-
status: "not_requested",
|
|
5950
|
-
note: PLACEHOLDER_NOTE
|
|
5951
|
-
};
|
|
5799
|
+
function traceMetasToSessionRunRecords(metas) {
|
|
5800
|
+
return metas.map(metaToSessionRunRecord);
|
|
5952
5801
|
}
|
|
5953
5802
|
|
|
5954
|
-
// packages/core/src/
|
|
5955
|
-
function
|
|
5956
|
-
const
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
["Safe status", metadata.safeStatus],
|
|
5976
|
-
["Source traces", metadata.sourceTraceCount],
|
|
5977
|
-
["Runs", metadata.runIds.join(", ")],
|
|
5978
|
-
...metadata.sessionId ? [["Session", metadata.sessionId]] : [],
|
|
5979
|
-
...metadata.since ? [["Since", metadata.since]] : []
|
|
5980
|
-
]),
|
|
5981
|
-
"",
|
|
5982
|
-
"## Safety checks",
|
|
5983
|
-
"",
|
|
5984
|
-
`Aggregate: **${checks.aggregateStatus}**`,
|
|
5985
|
-
""
|
|
5986
|
-
];
|
|
5987
|
-
for (const run of checks.runs) {
|
|
5988
|
-
const source = run.sourceStatus !== void 0 && run.sourceStatus !== run.status ? `; source ${run.sourceStatus}` : "";
|
|
5989
|
-
lines.push(
|
|
5990
|
-
`- \`${run.runId}\`: artifact ${run.status}${source} (${run.findings} finding(s), ${run.errors} error(s), ${run.warnings} warning(s))`
|
|
5991
|
-
);
|
|
5992
|
-
}
|
|
5993
|
-
lines.push("", "## Redaction", "", `Total findings: ${redaction.totalFindings}`, "");
|
|
5994
|
-
for (const run of redaction.runs) {
|
|
5995
|
-
const detectors = run.detectors.length > 0 ? run.detectors.join(", ") : "none";
|
|
5996
|
-
lines.push(`- \`${run.runId}\`: ${run.findings} finding(s); detectors: ${detectors}`);
|
|
5997
|
-
}
|
|
5998
|
-
lines.push(
|
|
5999
|
-
"",
|
|
6000
|
-
"## Files",
|
|
6001
|
-
"",
|
|
6002
|
-
...metadata.files.map((file) => `- \`${file}\``),
|
|
6003
|
-
"",
|
|
6004
|
-
"_Review every generated artifact before sharing outside your team._",
|
|
6005
|
-
""
|
|
6006
|
-
);
|
|
6007
|
-
return lines.join("\n");
|
|
6008
|
-
}
|
|
6009
|
-
function sanitizeBundleRunId(runId) {
|
|
6010
|
-
let safe = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "run_unknown";
|
|
6011
|
-
safe = path7__default.default.basename(safe);
|
|
6012
|
-
safe = safe.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
6013
|
-
if (safe === "" || safe === "." || safe === "..") {
|
|
6014
|
-
safe = "run_unknown";
|
|
5803
|
+
// packages/core/src/sessions/cohort.ts
|
|
5804
|
+
function groupSessionCohorts(runs, options = {}) {
|
|
5805
|
+
const groupBy = options.groupBy ?? "session";
|
|
5806
|
+
const cohorts = [];
|
|
5807
|
+
if (groupBy === "session") {
|
|
5808
|
+
const index = buildSessionIndex(runs, options);
|
|
5809
|
+
for (const session of index.sessions) {
|
|
5810
|
+
cohorts.push({
|
|
5811
|
+
key: session.sessionId,
|
|
5812
|
+
kind: "session",
|
|
5813
|
+
runIds: [...session.runIds]
|
|
5814
|
+
});
|
|
5815
|
+
}
|
|
5816
|
+
if (index.unscopedRunIds.length > 0) {
|
|
5817
|
+
cohorts.push({
|
|
5818
|
+
key: "__unscoped__",
|
|
5819
|
+
kind: "session",
|
|
5820
|
+
runIds: [...index.unscopedRunIds]
|
|
5821
|
+
});
|
|
5822
|
+
}
|
|
5823
|
+
return cohorts.sort((a, b) => a.key.localeCompare(b.key));
|
|
6015
5824
|
}
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
throw new Error(`Bundle path escapes output directory: ${relativePath}`);
|
|
5825
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
5826
|
+
const unscoped = [];
|
|
5827
|
+
for (const run of runs) {
|
|
5828
|
+
const groupId = extractSessionWorkflowMetadata(run.metadata)?.groupId;
|
|
5829
|
+
if (!groupId) {
|
|
5830
|
+
unscoped.push(run.runId);
|
|
5831
|
+
continue;
|
|
5832
|
+
}
|
|
5833
|
+
const bucket = byGroup.get(groupId) ?? [];
|
|
5834
|
+
bucket.push(run.runId);
|
|
5835
|
+
byGroup.set(groupId, bucket);
|
|
6028
5836
|
}
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
5837
|
+
for (const [key, runIds] of [...byGroup.entries()].sort(
|
|
5838
|
+
([a], [b]) => a.localeCompare(b)
|
|
5839
|
+
)) {
|
|
5840
|
+
cohorts.push({
|
|
5841
|
+
key,
|
|
5842
|
+
kind: "group",
|
|
5843
|
+
runIds: runIds.sort()
|
|
5844
|
+
});
|
|
6035
5845
|
}
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
5846
|
+
if (unscoped.length > 0) {
|
|
5847
|
+
cohorts.push({
|
|
5848
|
+
key: "__unscoped__",
|
|
5849
|
+
kind: "group",
|
|
5850
|
+
runIds: unscoped.sort()
|
|
5851
|
+
});
|
|
6039
5852
|
}
|
|
6040
|
-
return
|
|
6041
|
-
}
|
|
6042
|
-
function defaultBundleOutputPath(runIds) {
|
|
6043
|
-
const label = runIds.length === 1 ? sanitizeBundleRunId(runIds[0]) : `multi-${runIds.length}`;
|
|
6044
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6045
|
-
return path7__default.default.resolve(`agent-inspect-bundle-${label}-${stamp}`);
|
|
5853
|
+
return cohorts;
|
|
6046
5854
|
}
|
|
6047
5855
|
|
|
6048
|
-
// packages/core/src/
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
}
|
|
6061
|
-
return hash.digest("hex");
|
|
5856
|
+
// packages/core/src/sessions/checks.ts
|
|
5857
|
+
function emptySummary() {
|
|
5858
|
+
return {
|
|
5859
|
+
passed: 0,
|
|
5860
|
+
failed: 0,
|
|
5861
|
+
warnings: 0,
|
|
5862
|
+
errors: 0,
|
|
5863
|
+
rulesEvaluated: 0,
|
|
5864
|
+
rulesPassed: 0,
|
|
5865
|
+
rulesWarning: 0,
|
|
5866
|
+
rulesFailed: 0,
|
|
5867
|
+
rulesError: 0
|
|
5868
|
+
};
|
|
6062
5869
|
}
|
|
6063
|
-
function
|
|
6064
|
-
return
|
|
5870
|
+
function mergeSummary(target, source) {
|
|
5871
|
+
return {
|
|
5872
|
+
passed: target.passed + source.passed,
|
|
5873
|
+
failed: target.failed + source.failed,
|
|
5874
|
+
warnings: target.warnings + source.warnings,
|
|
5875
|
+
errors: target.errors + source.errors,
|
|
5876
|
+
rulesEvaluated: target.rulesEvaluated + source.rulesEvaluated,
|
|
5877
|
+
rulesPassed: target.rulesPassed + source.rulesPassed,
|
|
5878
|
+
rulesWarning: target.rulesWarning + source.rulesWarning,
|
|
5879
|
+
rulesFailed: target.rulesFailed + source.rulesFailed,
|
|
5880
|
+
rulesError: target.rulesError + source.rulesError
|
|
5881
|
+
};
|
|
6065
5882
|
}
|
|
6066
|
-
function
|
|
6067
|
-
|
|
6068
|
-
return false;
|
|
6069
|
-
}
|
|
6070
|
-
const a = expected.toLowerCase();
|
|
6071
|
-
const b = actual.toLowerCase();
|
|
6072
|
-
if (a.length !== b.length) return false;
|
|
6073
|
-
let mismatch = 0;
|
|
6074
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
6075
|
-
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
6076
|
-
}
|
|
6077
|
-
return mismatch === 0;
|
|
5883
|
+
function sessionDiagnostic(code, message) {
|
|
5884
|
+
return { code, message, severity: "error" };
|
|
6078
5885
|
}
|
|
6079
|
-
function
|
|
6080
|
-
if (
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
5886
|
+
function aggregateSessionCheckResults(perRun, scope) {
|
|
5887
|
+
if (scope.notFound) {
|
|
5888
|
+
return {
|
|
5889
|
+
ok: false,
|
|
5890
|
+
status: "error",
|
|
5891
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
5892
|
+
scopeKind: scope.scopeKind,
|
|
5893
|
+
scopeLabel: scope.scopeLabel,
|
|
5894
|
+
runIds: [],
|
|
5895
|
+
runResults: [],
|
|
5896
|
+
summary: { ...emptySummary(), errors: 1 },
|
|
5897
|
+
findings: [],
|
|
5898
|
+
diagnostics: [
|
|
5899
|
+
sessionDiagnostic(
|
|
5900
|
+
"AI_CHECK_INVALID_ARGUMENTS",
|
|
5901
|
+
`${scope.scopeKind} not found: ${scope.scopeLabel}`
|
|
5902
|
+
)
|
|
5903
|
+
],
|
|
5904
|
+
ruleExecutions: [],
|
|
5905
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
5906
|
+
};
|
|
6086
5907
|
}
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
5908
|
+
if (scope.empty || perRun.length === 0) {
|
|
5909
|
+
return {
|
|
5910
|
+
ok: false,
|
|
5911
|
+
status: "error",
|
|
5912
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
5913
|
+
scopeKind: scope.scopeKind,
|
|
5914
|
+
scopeLabel: scope.scopeLabel,
|
|
5915
|
+
runIds: scope.runIds,
|
|
5916
|
+
runResults: [],
|
|
5917
|
+
summary: { ...emptySummary(), errors: 1 },
|
|
5918
|
+
findings: [],
|
|
5919
|
+
diagnostics: [
|
|
5920
|
+
sessionDiagnostic(
|
|
5921
|
+
"AI_CHECK_TRACE_UNREADABLE",
|
|
5922
|
+
`No readable traces in ${scope.scopeKind}: ${scope.scopeLabel}`
|
|
5923
|
+
)
|
|
5924
|
+
],
|
|
5925
|
+
ruleExecutions: [],
|
|
5926
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
5927
|
+
};
|
|
6090
5928
|
}
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
5929
|
+
let summary = emptySummary();
|
|
5930
|
+
const findings = [];
|
|
5931
|
+
const diagnostics = [];
|
|
5932
|
+
const ruleExecutions = [];
|
|
5933
|
+
const runResults = [];
|
|
5934
|
+
for (const result of perRun) {
|
|
5935
|
+
summary = mergeSummary(summary, result.summary);
|
|
5936
|
+
findings.push(...result.findings);
|
|
5937
|
+
diagnostics.push(...result.diagnostics);
|
|
5938
|
+
ruleExecutions.push(...result.ruleExecutions ?? []);
|
|
5939
|
+
if (result.runId) {
|
|
5940
|
+
runResults.push({ runId: result.runId, status: result.status });
|
|
6094
5941
|
}
|
|
6095
5942
|
}
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
5943
|
+
runResults.sort((a, b) => a.runId.localeCompare(b.runId));
|
|
5944
|
+
findings.sort((a, b) => {
|
|
5945
|
+
const runCmp = (a.evidence[0]?.runId ?? "").localeCompare(
|
|
5946
|
+
b.evidence[0]?.runId ?? ""
|
|
5947
|
+
);
|
|
5948
|
+
if (runCmp !== 0) return runCmp;
|
|
5949
|
+
return a.ruleId.localeCompare(b.ruleId);
|
|
5950
|
+
});
|
|
5951
|
+
ruleExecutions.sort((a, b) => {
|
|
5952
|
+
const runCmp = (a.runId ?? "").localeCompare(b.runId ?? "");
|
|
5953
|
+
if (runCmp !== 0) return runCmp;
|
|
5954
|
+
return a.ruleId.localeCompare(b.ruleId);
|
|
5955
|
+
});
|
|
5956
|
+
const hasErrors = diagnostics.some((item) => item.severity === "error");
|
|
5957
|
+
const status = hasErrors ? "error" : summary.failed > 0 ? "fail" : "pass";
|
|
5958
|
+
return {
|
|
5959
|
+
ok: status === "pass",
|
|
5960
|
+
status,
|
|
5961
|
+
format: perRun[0]?.format ?? "agent-inspect-jsonl",
|
|
5962
|
+
scopeKind: scope.scopeKind,
|
|
5963
|
+
scopeLabel: scope.scopeLabel,
|
|
5964
|
+
runIds: scope.runIds,
|
|
5965
|
+
runResults,
|
|
5966
|
+
summary,
|
|
5967
|
+
findings,
|
|
5968
|
+
diagnostics,
|
|
5969
|
+
ruleExecutions,
|
|
5970
|
+
...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
|
|
5971
|
+
};
|
|
6107
5972
|
}
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
5973
|
+
|
|
5974
|
+
// packages/core/src/sessions/index.ts
|
|
5975
|
+
function compareRuns(a, b) {
|
|
5976
|
+
const aStart = a.startedAt ?? 0;
|
|
5977
|
+
const bStart = b.startedAt ?? 0;
|
|
5978
|
+
if (aStart !== bStart) return aStart - bStart;
|
|
5979
|
+
return a.runId.localeCompare(b.runId);
|
|
6111
5980
|
}
|
|
6112
|
-
function
|
|
6113
|
-
const
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
if (base === "redaction-report.json") {
|
|
6128
|
-
return "redaction-report";
|
|
6129
|
-
}
|
|
6130
|
-
if (base === "summary.md") {
|
|
6131
|
-
return "summary";
|
|
5981
|
+
function buildGroups(runIds, metaByRunId) {
|
|
5982
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
5983
|
+
for (const runId of runIds) {
|
|
5984
|
+
const groupId = metaByRunId.get(runId)?.groupId;
|
|
5985
|
+
if (!groupId) continue;
|
|
5986
|
+
const existing = byGroup.get(groupId);
|
|
5987
|
+
if (existing) {
|
|
5988
|
+
existing.runIds.push(runId);
|
|
5989
|
+
} else {
|
|
5990
|
+
byGroup.set(groupId, {
|
|
5991
|
+
groupId,
|
|
5992
|
+
parentGroupId: metaByRunId.get(runId)?.parentGroupId,
|
|
5993
|
+
runIds: [runId]
|
|
5994
|
+
});
|
|
5995
|
+
}
|
|
6132
5996
|
}
|
|
6133
|
-
return
|
|
5997
|
+
return [...byGroup.values()].map((group) => ({
|
|
5998
|
+
...group,
|
|
5999
|
+
runIds: [...group.runIds].sort((a, b) => a.localeCompare(b))
|
|
6000
|
+
}));
|
|
6134
6001
|
}
|
|
6135
|
-
function
|
|
6136
|
-
const
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6002
|
+
function buildHandoffs(runIds, metaByRunId, warnings, sessionId) {
|
|
6003
|
+
const edges = [];
|
|
6004
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6005
|
+
const pushEdge = (edge) => {
|
|
6006
|
+
const key = `${edge.from}->${edge.to}:${edge.confidence}`;
|
|
6007
|
+
if (seen.has(key)) return;
|
|
6008
|
+
seen.add(key);
|
|
6009
|
+
edges.push(edge);
|
|
6010
|
+
};
|
|
6011
|
+
for (const runId of runIds) {
|
|
6012
|
+
const meta = metaByRunId.get(runId);
|
|
6013
|
+
if (!meta) continue;
|
|
6014
|
+
if (meta.handoffFrom && meta.handoffTo) {
|
|
6015
|
+
pushEdge({
|
|
6016
|
+
from: meta.handoffFrom,
|
|
6017
|
+
to: meta.handoffTo,
|
|
6018
|
+
source: "manual",
|
|
6019
|
+
confidence: "explicit"
|
|
6020
|
+
});
|
|
6021
|
+
continue;
|
|
6143
6022
|
}
|
|
6144
|
-
if (
|
|
6145
|
-
|
|
6023
|
+
if (meta.handoffFrom) {
|
|
6024
|
+
pushEdge({
|
|
6025
|
+
from: meta.handoffFrom,
|
|
6026
|
+
to: runId,
|
|
6027
|
+
source: "manual",
|
|
6028
|
+
confidence: "explicit"
|
|
6029
|
+
});
|
|
6030
|
+
}
|
|
6031
|
+
if (meta.handoffTo) {
|
|
6032
|
+
pushEdge({
|
|
6033
|
+
from: runId,
|
|
6034
|
+
to: meta.handoffTo,
|
|
6035
|
+
source: "manual",
|
|
6036
|
+
confidence: "explicit"
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
if (meta.subAgentId && meta.parentGroupId && !meta.handoffFrom && !meta.handoffTo) {
|
|
6040
|
+
pushEdge({
|
|
6041
|
+
from: meta.parentGroupId,
|
|
6042
|
+
to: meta.subAgentId,
|
|
6043
|
+
source: "inferred",
|
|
6044
|
+
confidence: "correlated"
|
|
6045
|
+
});
|
|
6046
|
+
warnings.push({
|
|
6047
|
+
code: "ambiguous-handoff-endpoints",
|
|
6048
|
+
message: "Handoff inferred from parentGroupId and subAgentId without explicit handoffFrom/handoffTo.",
|
|
6049
|
+
runId,
|
|
6050
|
+
sessionId
|
|
6051
|
+
});
|
|
6146
6052
|
}
|
|
6147
|
-
byPath.set(relativePath, {
|
|
6148
|
-
path: relativePath,
|
|
6149
|
-
sha256: sha256Hex(file.content),
|
|
6150
|
-
role: file.role ?? inferEvidenceFileRole(relativePath)
|
|
6151
|
-
});
|
|
6152
6053
|
}
|
|
6153
|
-
return
|
|
6054
|
+
return edges.sort((a, b) => {
|
|
6055
|
+
const from = a.from.localeCompare(b.from);
|
|
6056
|
+
if (from !== 0) return from;
|
|
6057
|
+
return a.to.localeCompare(b.to);
|
|
6058
|
+
});
|
|
6154
6059
|
}
|
|
6155
|
-
function
|
|
6156
|
-
const
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6060
|
+
function buildRetries(runIds, metaByRunId, warnings, sessionId) {
|
|
6061
|
+
const retries = [];
|
|
6062
|
+
for (const runId of runIds) {
|
|
6063
|
+
const meta = metaByRunId.get(runId);
|
|
6064
|
+
if (!meta) continue;
|
|
6065
|
+
if (meta.retryOf) {
|
|
6066
|
+
retries.push({
|
|
6067
|
+
runId,
|
|
6068
|
+
retryOf: meta.retryOf,
|
|
6069
|
+
attempt: meta.attempt,
|
|
6070
|
+
source: "manual",
|
|
6071
|
+
confidence: "explicit"
|
|
6072
|
+
});
|
|
6073
|
+
continue;
|
|
6163
6074
|
}
|
|
6164
|
-
if (
|
|
6165
|
-
|
|
6075
|
+
if (meta.attempt !== void 0 && meta.attempt > 1) {
|
|
6076
|
+
retries.push({
|
|
6077
|
+
runId,
|
|
6078
|
+
attempt: meta.attempt,
|
|
6079
|
+
source: "inferred",
|
|
6080
|
+
confidence: "correlated"
|
|
6081
|
+
});
|
|
6082
|
+
warnings.push({
|
|
6083
|
+
code: "ambiguous-retry-link",
|
|
6084
|
+
message: "attempt > 1 without retryOf; retry link is correlated only.",
|
|
6085
|
+
runId,
|
|
6086
|
+
sessionId
|
|
6087
|
+
});
|
|
6166
6088
|
}
|
|
6167
6089
|
}
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6090
|
+
return retries.sort((a, b) => a.runId.localeCompare(b.runId));
|
|
6091
|
+
}
|
|
6092
|
+
function buildCriticalPath(runs, handoffs) {
|
|
6093
|
+
const runById = new Map(runs.map((run) => [run.runId, run]));
|
|
6094
|
+
const explicitTargets = new Set(
|
|
6095
|
+
handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.to)
|
|
6096
|
+
);
|
|
6097
|
+
const explicitSources = new Set(
|
|
6098
|
+
handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
|
|
6099
|
+
);
|
|
6100
|
+
const ordered = [...runs].sort(compareRuns);
|
|
6101
|
+
const path14 = [];
|
|
6102
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6103
|
+
const pushRun = (run, confidence, source) => {
|
|
6104
|
+
if (visited.has(run.runId)) return;
|
|
6105
|
+
visited.add(run.runId);
|
|
6106
|
+
path14.push({
|
|
6107
|
+
runId: run.runId,
|
|
6108
|
+
name: run.name,
|
|
6109
|
+
startedAt: run.startedAt,
|
|
6110
|
+
durationMs: run.durationMs,
|
|
6111
|
+
confidence,
|
|
6112
|
+
source
|
|
6113
|
+
});
|
|
6171
6114
|
};
|
|
6172
|
-
|
|
6173
|
-
|
|
6115
|
+
for (const edge of handoffs) {
|
|
6116
|
+
if (edge.confidence !== "explicit") continue;
|
|
6117
|
+
const fromRun = [...runById.values()].find(
|
|
6118
|
+
(run) => run.runId === edge.from || metaRunIdMatches(run, edge.from, runById)
|
|
6119
|
+
);
|
|
6120
|
+
const toRun = [...runById.values()].find(
|
|
6121
|
+
(run) => run.runId === edge.to || metaRunIdMatches(run, edge.to, runById)
|
|
6122
|
+
);
|
|
6123
|
+
if (fromRun) pushRun(fromRun, "explicit", "manual");
|
|
6124
|
+
if (toRun) pushRun(toRun, "explicit", "manual");
|
|
6174
6125
|
}
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
version: parts.generatorVersion
|
|
6180
|
-
},
|
|
6181
|
-
createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6182
|
-
source: {
|
|
6183
|
-
runIds,
|
|
6184
|
-
traceSchemaVersions: [...parts.traceSchemaVersions].sort((a, b) => a.localeCompare(b)),
|
|
6185
|
-
sourceHashes: [...parts.sourceHashes].sort((a, b) => a.runId.localeCompare(b.runId))
|
|
6186
|
-
},
|
|
6187
|
-
policy: {
|
|
6188
|
-
redactionProfile: parts.redactionProfile,
|
|
6189
|
-
verificationPolicy: parts.verificationPolicy ?? parts.redactionProfile
|
|
6190
|
-
},
|
|
6191
|
-
assessment,
|
|
6192
|
-
...parts.semantics !== void 0 ? { semantics: { ...parts.semantics } } : {},
|
|
6193
|
-
...parts.contract !== void 0 ? { contract: { ...parts.contract } } : {},
|
|
6194
|
-
files: buildEvidenceFileEntries(parts.files)
|
|
6195
|
-
};
|
|
6196
|
-
}
|
|
6197
|
-
function validateEvidenceManifest(value) {
|
|
6198
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
6199
|
-
throw new Error("Evidence manifest must be a JSON object.");
|
|
6200
|
-
}
|
|
6201
|
-
const record = value;
|
|
6202
|
-
if (record.evidenceFormatVersion !== EVIDENCE_FORMAT_VERSION) {
|
|
6203
|
-
throw new Error(
|
|
6204
|
-
`Unsupported evidenceFormatVersion: ${String(record.evidenceFormatVersion)}`
|
|
6205
|
-
);
|
|
6206
|
-
}
|
|
6207
|
-
const generator = record.generator;
|
|
6208
|
-
if (generator === null || typeof generator !== "object" || Array.isArray(generator) || typeof generator.name !== "string" || typeof generator.version !== "string") {
|
|
6209
|
-
throw new Error("Evidence manifest requires generator.name and generator.version.");
|
|
6210
|
-
}
|
|
6211
|
-
const source = record.source;
|
|
6212
|
-
if (source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
6213
|
-
throw new Error("Evidence manifest requires source.");
|
|
6214
|
-
}
|
|
6215
|
-
const sourceRecord = source;
|
|
6216
|
-
if (!Array.isArray(sourceRecord.runIds) || sourceRecord.runIds.length === 0) {
|
|
6217
|
-
throw new Error("Evidence manifest source.runIds must be a non-empty array.");
|
|
6218
|
-
}
|
|
6219
|
-
if (!Array.isArray(sourceRecord.traceSchemaVersions)) {
|
|
6220
|
-
throw new Error("Evidence manifest source.traceSchemaVersions must be an array.");
|
|
6221
|
-
}
|
|
6222
|
-
if (!Array.isArray(sourceRecord.sourceHashes)) {
|
|
6223
|
-
throw new Error("Evidence manifest source.sourceHashes must be an array.");
|
|
6224
|
-
}
|
|
6225
|
-
const policy = record.policy;
|
|
6226
|
-
if (policy === null || typeof policy !== "object" || Array.isArray(policy)) {
|
|
6227
|
-
throw new Error("Evidence manifest requires policy.");
|
|
6228
|
-
}
|
|
6229
|
-
const assessment = record.assessment;
|
|
6230
|
-
if (assessment === null || typeof assessment !== "object" || Array.isArray(assessment) || typeof assessment.status !== "string") {
|
|
6231
|
-
throw new Error("Evidence manifest requires assessment.status.");
|
|
6126
|
+
for (const run of ordered) {
|
|
6127
|
+
if (visited.has(run.runId)) continue;
|
|
6128
|
+
const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
|
|
6129
|
+
pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
|
|
6232
6130
|
}
|
|
6233
|
-
|
|
6234
|
-
|
|
6131
|
+
return path14;
|
|
6132
|
+
}
|
|
6133
|
+
function metaRunIdMatches(run, token, runById) {
|
|
6134
|
+
const meta = extractSessionWorkflowMetadata(run.metadata);
|
|
6135
|
+
return meta?.subAgentId === token || meta?.groupId === token || runById.has(token);
|
|
6136
|
+
}
|
|
6137
|
+
function buildSessionIndex(inputRuns, options = {}) {
|
|
6138
|
+
const warnings = [];
|
|
6139
|
+
const runs = [...inputRuns].sort(compareRuns);
|
|
6140
|
+
const metaByRunId = /* @__PURE__ */ new Map();
|
|
6141
|
+
for (const run of runs) {
|
|
6142
|
+
metaByRunId.set(run.runId, extractSessionWorkflowMetadata(run.metadata));
|
|
6235
6143
|
}
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
const
|
|
6241
|
-
|
|
6242
|
-
|
|
6144
|
+
const sessionsByKey = /* @__PURE__ */ new Map();
|
|
6145
|
+
const unscopedRunIds = [];
|
|
6146
|
+
for (const run of runs) {
|
|
6147
|
+
const meta = metaByRunId.get(run.runId);
|
|
6148
|
+
const key = sessionKeyForRun(meta, {
|
|
6149
|
+
correlateByGroupId: options.correlateByGroupId === true
|
|
6150
|
+
});
|
|
6151
|
+
if (!key) {
|
|
6152
|
+
unscopedRunIds.push(run.runId);
|
|
6153
|
+
continue;
|
|
6243
6154
|
}
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6155
|
+
const bucket = sessionsByKey.get(key) ?? [];
|
|
6156
|
+
bucket.push(run);
|
|
6157
|
+
sessionsByKey.set(key, bucket);
|
|
6158
|
+
}
|
|
6159
|
+
const sessions = [...sessionsByKey.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([sessionId, sessionRuns]) => {
|
|
6160
|
+
const runIds = sessionRuns.map((run) => run.runId).sort();
|
|
6161
|
+
const handoffs = buildHandoffs(runIds, metaByRunId, warnings, sessionId);
|
|
6162
|
+
const retries = buildRetries(runIds, metaByRunId, warnings, sessionId);
|
|
6163
|
+
const groups = buildGroups(runIds, metaByRunId);
|
|
6164
|
+
const criticalPath = buildCriticalPath(sessionRuns, handoffs);
|
|
6165
|
+
const confidences = new Set(handoffs.map((edge) => edge.confidence));
|
|
6166
|
+
if (confidences.has("explicit") && confidences.has("correlated")) {
|
|
6167
|
+
warnings.push({
|
|
6168
|
+
code: "mixed-confidence-group",
|
|
6169
|
+
message: "Session aggregates explicit and correlated handoff edges.",
|
|
6170
|
+
sessionId
|
|
6171
|
+
});
|
|
6247
6172
|
}
|
|
6173
|
+
return enrichSessionSummary(
|
|
6174
|
+
{
|
|
6175
|
+
sessionId,
|
|
6176
|
+
runIds,
|
|
6177
|
+
groups,
|
|
6178
|
+
handoffs,
|
|
6179
|
+
retries,
|
|
6180
|
+
criticalPath
|
|
6181
|
+
},
|
|
6182
|
+
runs,
|
|
6183
|
+
{
|
|
6184
|
+
nowMs: options.nowMs,
|
|
6185
|
+
staleThresholdMs: options.staleThresholdMs
|
|
6186
|
+
}
|
|
6187
|
+
);
|
|
6188
|
+
});
|
|
6189
|
+
if (sessions.length === 0 && runs.length > 0) {
|
|
6190
|
+
warnings.push({
|
|
6191
|
+
code: "missing-session-id",
|
|
6192
|
+
message: "No sessionId (or correlated groupId) found on input runs."
|
|
6193
|
+
});
|
|
6248
6194
|
}
|
|
6249
|
-
|
|
6195
|
+
warnings.sort((a, b) => {
|
|
6196
|
+
const code = a.code.localeCompare(b.code);
|
|
6197
|
+
if (code !== 0) return code;
|
|
6198
|
+
return (a.runId ?? "").localeCompare(b.runId ?? "");
|
|
6199
|
+
});
|
|
6200
|
+
return {
|
|
6201
|
+
runs,
|
|
6202
|
+
sessions,
|
|
6203
|
+
unscopedRunIds: unscopedRunIds.sort(),
|
|
6204
|
+
warnings
|
|
6205
|
+
};
|
|
6250
6206
|
}
|
|
6251
|
-
|
|
6252
|
-
|
|
6207
|
+
var KNOWN_EVENTS = /* @__PURE__ */ new Set([
|
|
6208
|
+
"run_started",
|
|
6209
|
+
"run_completed",
|
|
6210
|
+
"step_started",
|
|
6211
|
+
"step_completed"
|
|
6212
|
+
]);
|
|
6213
|
+
function isRecord8(value) {
|
|
6214
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6215
|
+
}
|
|
6216
|
+
function safeParse(line) {
|
|
6253
6217
|
try {
|
|
6254
|
-
|
|
6255
|
-
} catch
|
|
6256
|
-
|
|
6257
|
-
throw new Error(`Evidence manifest is not valid JSON: ${message}`);
|
|
6218
|
+
return JSON.parse(line);
|
|
6219
|
+
} catch {
|
|
6220
|
+
return void 0;
|
|
6258
6221
|
}
|
|
6259
|
-
return validateEvidenceManifest(parsed);
|
|
6260
6222
|
}
|
|
6261
|
-
function
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6223
|
+
async function isAgentInspectTrace(filePath) {
|
|
6224
|
+
try {
|
|
6225
|
+
const rl = readline.createInterface({
|
|
6226
|
+
input: fs.createReadStream(filePath, { encoding: "utf8" }),
|
|
6227
|
+
crlfDelay: Infinity
|
|
6228
|
+
});
|
|
6229
|
+
let checked = 0;
|
|
6230
|
+
for await (const line of rl) {
|
|
6231
|
+
const trimmed = line.trim();
|
|
6232
|
+
if (trimmed === "") continue;
|
|
6233
|
+
const parsed = safeParse(trimmed);
|
|
6234
|
+
if (!parsed) continue;
|
|
6235
|
+
if (!isRecord8(parsed)) continue;
|
|
6236
|
+
checked += 1;
|
|
6237
|
+
if (isTraceEvent(parsed)) return true;
|
|
6238
|
+
const ev = parsed.event;
|
|
6239
|
+
const runId = parsed.runId;
|
|
6240
|
+
if (typeof ev === "string" && KNOWN_EVENTS.has(ev) && typeof runId === "string") {
|
|
6241
|
+
return true;
|
|
6270
6242
|
}
|
|
6271
|
-
|
|
6243
|
+
if (checked >= 20) break;
|
|
6272
6244
|
}
|
|
6245
|
+
return false;
|
|
6246
|
+
} catch {
|
|
6247
|
+
return false;
|
|
6273
6248
|
}
|
|
6274
|
-
return [...versions].sort((a, b) => a.localeCompare(b));
|
|
6275
6249
|
}
|
|
6276
6250
|
|
|
6277
|
-
// packages/core/src/
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
remediation: "For a file path, use openTraceFile(path).",
|
|
6283
|
-
relatedCodes: ["invalid_input"]
|
|
6284
|
-
},
|
|
6285
|
-
AI_TRACE_FORMAT_UNSUPPORTED: {
|
|
6286
|
-
code: "AI_TRACE_FORMAT_UNSUPPORTED",
|
|
6287
|
-
summary: "No trace reader could detect the input format.",
|
|
6288
|
-
remediation: "Pass an AgentInspect JSONL file via openTraceFile, or set options.format to a registered reader.",
|
|
6289
|
-
relatedCodes: ["unsupported_format"]
|
|
6290
|
-
},
|
|
6291
|
-
AI_TRACE_FORMAT_AMBIGUOUS: {
|
|
6292
|
-
code: "AI_TRACE_FORMAT_AMBIGUOUS",
|
|
6293
|
-
summary: "Multiple trace readers matched the input with equal confidence.",
|
|
6294
|
-
remediation: "Set options.format explicitly to disambiguate the reader.",
|
|
6295
|
-
relatedCodes: ["ambiguous_format"]
|
|
6296
|
-
},
|
|
6297
|
-
AI_TRACE_FACTS_INPUT_NOT_NORMALIZED: {
|
|
6298
|
-
code: "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED",
|
|
6299
|
-
summary: "TraceFacts requires TraceReadResult or PersistedInspectEvent[].",
|
|
6300
|
-
remediation: "Use openTraceFile() to normalize a JSONL trace first."
|
|
6301
|
-
},
|
|
6302
|
-
AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED: {
|
|
6303
|
-
code: "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
|
|
6304
|
-
summary: "Multiple runs are available; select a run before executing checks.",
|
|
6305
|
-
remediation: "Pass options.runId or TraceCheckInput.selectedRun.",
|
|
6306
|
-
relatedCodes: ["AI_CHECK_RUN_SELECTION_REQUIRED"]
|
|
6307
|
-
},
|
|
6308
|
-
AI_TRACE_RELATIONSHIP_SELF_PARENT: {
|
|
6309
|
-
code: "AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
6310
|
-
summary: "A parentId equals its own event/step id.",
|
|
6311
|
-
remediation: "Reject at capture (AI_LANGGRAPH_SELF_PARENT_REJECTED) or drop via logical projection; do not invent replacement parents.",
|
|
6312
|
-
relatedCodes: [
|
|
6313
|
-
"AI_LANGGRAPH_SELF_PARENT_REJECTED",
|
|
6314
|
-
"AI_LOGICAL_SELF_PARENT_REMOVED"
|
|
6315
|
-
]
|
|
6316
|
-
},
|
|
6317
|
-
AI_TRACE_RELATIONSHIP_CYCLE: {
|
|
6318
|
-
code: "AI_TRACE_RELATIONSHIP_CYCLE",
|
|
6319
|
-
summary: "Trace contains a parentId cycle.",
|
|
6320
|
-
remediation: "Use visibility-first tree linking for legacy fixtures; prefer acyclic capture for new adapter output.",
|
|
6321
|
-
relatedCodes: ["structure.cycle"]
|
|
6251
|
+
// packages/core/src/bundle/resolve.ts
|
|
6252
|
+
function parseSinceCutoff(since) {
|
|
6253
|
+
const trimmed = since.trim();
|
|
6254
|
+
if (trimmed === "") {
|
|
6255
|
+
throw new Error("--since requires a non-empty duration (e.g. 24h, 7d).");
|
|
6322
6256
|
}
|
|
6323
|
-
|
|
6324
|
-
function formatProgrammaticDiagnostic(code, detail) {
|
|
6325
|
-
const spec = PROGRAMMATIC_DIAGNOSTIC_SPECS[code];
|
|
6326
|
-
const summary = detail?.trim() ? detail.trim() : spec.summary;
|
|
6327
|
-
return `${code}: ${summary} Remediation: ${spec.remediation}`;
|
|
6328
|
-
}
|
|
6329
|
-
|
|
6330
|
-
// packages/core/src/checks/logical-events.ts
|
|
6331
|
-
function isRecord8(value) {
|
|
6332
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6333
|
-
}
|
|
6334
|
-
function legacyEvent(event) {
|
|
6335
|
-
const value = event.attributes?.legacyEvent;
|
|
6336
|
-
return typeof value === "string" ? value : void 0;
|
|
6257
|
+
return Date.now() - parseDuration(trimmed);
|
|
6337
6258
|
}
|
|
6338
|
-
function
|
|
6339
|
-
|
|
6340
|
-
if (
|
|
6259
|
+
function runActivityMs(run) {
|
|
6260
|
+
if (run.startedAt !== void 0 && Number.isFinite(run.startedAt)) return run.startedAt;
|
|
6261
|
+
if (run.endedAt !== void 0 && Number.isFinite(run.endedAt)) return run.endedAt;
|
|
6341
6262
|
return void 0;
|
|
6342
6263
|
}
|
|
6343
|
-
function
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
}
|
|
6352
|
-
function mergeAttributes(start, complete) {
|
|
6353
|
-
const merged = {
|
|
6354
|
-
...isRecord8(complete.attributes) ? complete.attributes : {},
|
|
6355
|
-
...isRecord8(start.attributes) ? start.attributes : {}
|
|
6356
|
-
};
|
|
6357
|
-
merged.legacyEvent = start.attributes?.legacyEvent ?? complete.attributes?.legacyEvent;
|
|
6358
|
-
merged.legacyCompleteEvent = complete.attributes?.legacyEvent;
|
|
6359
|
-
if (complete.attributes?.errorStack !== void 0) {
|
|
6360
|
-
merged.errorStack = complete.attributes.errorStack;
|
|
6264
|
+
function runsInSinceWindow(runs, since) {
|
|
6265
|
+
const cutoff = parseSinceCutoff(since);
|
|
6266
|
+
const ids = [];
|
|
6267
|
+
for (const run of runs) {
|
|
6268
|
+
const activity = runActivityMs(run);
|
|
6269
|
+
if (activity !== void 0 && activity >= cutoff) {
|
|
6270
|
+
ids.push(run.runId);
|
|
6271
|
+
}
|
|
6361
6272
|
}
|
|
6362
|
-
return
|
|
6273
|
+
return ids.sort((a, b) => a.localeCompare(b));
|
|
6363
6274
|
}
|
|
6364
|
-
function
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6275
|
+
function findSession(index, sessionId) {
|
|
6276
|
+
return index.sessions.find((session) => session.sessionId === sessionId);
|
|
6277
|
+
}
|
|
6278
|
+
function resolveBundleRunIds(index, runs, options) {
|
|
6279
|
+
const runId = options.runId?.trim();
|
|
6280
|
+
const sessionId = options.sessionId?.trim();
|
|
6281
|
+
const since = options.since?.trim();
|
|
6282
|
+
const modes = [runId ? 1 : 0, sessionId ? 1 : 0, since ? 1 : 0].reduce((a, b) => a + b, 0);
|
|
6283
|
+
if (modes === 0) {
|
|
6284
|
+
throw new Error(
|
|
6285
|
+
"bundle requires a run id, --session <sessionId>, or --since <duration>."
|
|
6286
|
+
);
|
|
6287
|
+
}
|
|
6288
|
+
if (modes > 1) {
|
|
6289
|
+
throw new Error(
|
|
6290
|
+
"bundle accepts only one target: a run id, --session, or --since (not combined)."
|
|
6291
|
+
);
|
|
6292
|
+
}
|
|
6293
|
+
if (runId) {
|
|
6294
|
+
const known = runs.some((run) => run.runId === runId);
|
|
6295
|
+
if (!known) {
|
|
6296
|
+
throw new Error(`Run "${runId}" was not found in the trace directory.`);
|
|
6297
|
+
}
|
|
6298
|
+
return { runIds: [runId] };
|
|
6299
|
+
}
|
|
6300
|
+
if (sessionId) {
|
|
6301
|
+
const session = findSession(index, sessionId);
|
|
6302
|
+
if (!session) {
|
|
6303
|
+
throw new Error(`Session "${sessionId}" was not found.`);
|
|
6384
6304
|
}
|
|
6305
|
+
if (session.runIds.length === 0) {
|
|
6306
|
+
throw new Error(`Session "${sessionId}" has no runs to bundle.`);
|
|
6307
|
+
}
|
|
6308
|
+
return {
|
|
6309
|
+
runIds: [...session.runIds].sort((a, b) => a.localeCompare(b)),
|
|
6310
|
+
sessionId
|
|
6311
|
+
};
|
|
6312
|
+
}
|
|
6313
|
+
const runIds = runsInSinceWindow(runs, since);
|
|
6314
|
+
if (runIds.length === 0) {
|
|
6315
|
+
throw new Error(`No runs matched --since ${since}.`);
|
|
6316
|
+
}
|
|
6317
|
+
return { runIds, since };
|
|
6318
|
+
}
|
|
6319
|
+
|
|
6320
|
+
// packages/core/src/bundle/safety-status.ts
|
|
6321
|
+
function aggregateBundleSafeStatus(statuses) {
|
|
6322
|
+
if (statuses.length === 0) return "UNKNOWN";
|
|
6323
|
+
if (statuses.some((status) => status === "UNSAFE")) return "UNSAFE";
|
|
6324
|
+
if (statuses.some((status) => status === "UNKNOWN")) return "UNKNOWN";
|
|
6325
|
+
if (statuses.some((status) => status === "SAFE WITH WARNINGS")) return "SAFE WITH WARNINGS";
|
|
6326
|
+
return "SAFE";
|
|
6327
|
+
}
|
|
6328
|
+
function toMetadataSafeStatus(status) {
|
|
6329
|
+
if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
|
|
6330
|
+
return status;
|
|
6331
|
+
}
|
|
6332
|
+
function bundleFailsOnSafety(status, allowUnsafe) {
|
|
6333
|
+
if (allowUnsafe) return false;
|
|
6334
|
+
return status === "UNSAFE" || status === "UNKNOWN";
|
|
6335
|
+
}
|
|
6336
|
+
|
|
6337
|
+
// packages/core/src/bundle/manifest.ts
|
|
6338
|
+
var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
|
|
6339
|
+
var PLACEHOLDER_NOTE = "No eval or performance artifacts were requested for this bundle.";
|
|
6340
|
+
function buildBundleMetadata(parts) {
|
|
6341
|
+
const aggregate = aggregateBundleSafeStatus(
|
|
6342
|
+
parts.checks.runs.map((run) => run.status)
|
|
6343
|
+
);
|
|
6344
|
+
return {
|
|
6345
|
+
createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6346
|
+
agentInspectVersion: parts.agentInspectVersion,
|
|
6347
|
+
redactionProfile: parts.profile,
|
|
6348
|
+
sourceTraceCount: parts.resolve.runIds.length,
|
|
6349
|
+
runIds: [...parts.resolve.runIds],
|
|
6350
|
+
safeStatus: toMetadataSafeStatus(aggregate),
|
|
6351
|
+
files: [...parts.files].sort((a, b) => a.localeCompare(b)),
|
|
6352
|
+
note: BUNDLE_NOTE,
|
|
6353
|
+
...parts.resolve.sessionId !== void 0 ? { sessionId: parts.resolve.sessionId } : {},
|
|
6354
|
+
...parts.resolve.since !== void 0 ? { since: parts.resolve.since } : {}
|
|
6385
6355
|
};
|
|
6386
6356
|
}
|
|
6387
|
-
function
|
|
6357
|
+
function buildPlaceholderArtifact() {
|
|
6388
6358
|
return {
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
projection: {
|
|
6392
|
-
paired: false,
|
|
6393
|
-
absorbedEventIds: Object.freeze([]),
|
|
6394
|
-
parentNormalized: extras?.parentNormalized === true,
|
|
6395
|
-
...{}
|
|
6396
|
-
}
|
|
6359
|
+
status: "not_requested",
|
|
6360
|
+
note: PLACEHOLDER_NOTE
|
|
6397
6361
|
};
|
|
6398
6362
|
}
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
byRun.set(event.runId, list);
|
|
6363
|
+
|
|
6364
|
+
// packages/core/src/bundle/summary.ts
|
|
6365
|
+
function markdownTable(rows) {
|
|
6366
|
+
const lines = ["| Field | Value |", "| --- | --- |"];
|
|
6367
|
+
for (const [key, value] of rows) {
|
|
6368
|
+
lines.push(`| ${key} | ${value ?? "unknown"} |`);
|
|
6406
6369
|
}
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
const
|
|
6411
|
-
const
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
eventIds: candidates.map((c) => c.eventId)
|
|
6440
|
-
});
|
|
6441
|
-
candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
|
|
6442
|
-
}
|
|
6443
|
-
match = candidates[0];
|
|
6444
|
-
} else if (stepId) {
|
|
6445
|
-
const candidates = completes.filter(
|
|
6446
|
-
(c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "step_completed" && stepIdOf(c) === stepId
|
|
6447
|
-
);
|
|
6448
|
-
if (candidates.length > 1) {
|
|
6449
|
-
diagnostics.push({
|
|
6450
|
-
code: "AI_LOGICAL_PAIR_AMBIGUOUS",
|
|
6451
|
-
message: `Multiple step_completed rows for stepId ${stepId}; using first by eventId.`,
|
|
6452
|
-
eventIds: candidates.map((c) => c.eventId)
|
|
6453
|
-
});
|
|
6454
|
-
candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
|
|
6455
|
-
}
|
|
6456
|
-
match = candidates[0];
|
|
6457
|
-
}
|
|
6458
|
-
if (match) {
|
|
6459
|
-
usedCompletes.add(match.eventId);
|
|
6460
|
-
absorbedIds.add(match.eventId);
|
|
6461
|
-
const paired = pairStartComplete(start, match);
|
|
6462
|
-
logical.push(paired);
|
|
6463
|
-
logicalByRawId.set(start.eventId, paired);
|
|
6464
|
-
logicalByRawId.set(match.eventId, paired);
|
|
6465
|
-
if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, paired.eventId);
|
|
6466
|
-
} else {
|
|
6467
|
-
diagnostics.push({
|
|
6468
|
-
code: "AI_LOGICAL_PAIR_UNMATCHED_START",
|
|
6469
|
-
message: `No matching complete for start ${start.eventId}.`,
|
|
6470
|
-
eventIds: [start.eventId]
|
|
6471
|
-
});
|
|
6472
|
-
const alone = asLogical(start);
|
|
6473
|
-
logical.push(alone);
|
|
6474
|
-
logicalByRawId.set(start.eventId, alone);
|
|
6475
|
-
if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
6476
|
-
}
|
|
6477
|
-
}
|
|
6478
|
-
for (const complete of completes) {
|
|
6479
|
-
if (usedCompletes.has(complete.eventId)) continue;
|
|
6480
|
-
diagnostics.push({
|
|
6481
|
-
code: "AI_LOGICAL_PAIR_UNMATCHED_COMPLETE",
|
|
6482
|
-
message: `No matching start for complete ${complete.eventId}.`,
|
|
6483
|
-
eventIds: [complete.eventId]
|
|
6484
|
-
});
|
|
6485
|
-
const alone = asLogical(complete);
|
|
6486
|
-
logical.push(alone);
|
|
6487
|
-
logicalByRawId.set(complete.eventId, alone);
|
|
6488
|
-
const stepId = stepIdOf(complete);
|
|
6489
|
-
if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
|
|
6490
|
-
stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
6491
|
-
}
|
|
6492
|
-
}
|
|
6493
|
-
for (const event of others) {
|
|
6494
|
-
const alone = asLogical(event);
|
|
6495
|
-
logical.push(alone);
|
|
6496
|
-
logicalByRawId.set(event.eventId, alone);
|
|
6497
|
-
const stepId = stepIdOf(event);
|
|
6498
|
-
if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
|
|
6499
|
-
stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
|
|
6500
|
-
}
|
|
6501
|
-
}
|
|
6370
|
+
return lines.join("\n");
|
|
6371
|
+
}
|
|
6372
|
+
function buildBundleSummaryMarkdown(parts) {
|
|
6373
|
+
const { metadata, checks, redaction } = parts;
|
|
6374
|
+
const lines = [
|
|
6375
|
+
"# AgentInspect trace bundle",
|
|
6376
|
+
"",
|
|
6377
|
+
metadata.note,
|
|
6378
|
+
"",
|
|
6379
|
+
"## Overview",
|
|
6380
|
+
"",
|
|
6381
|
+
markdownTable([
|
|
6382
|
+
["Created", metadata.createdAt],
|
|
6383
|
+
["AgentInspect", metadata.agentInspectVersion],
|
|
6384
|
+
["Redaction profile", metadata.redactionProfile],
|
|
6385
|
+
["Safe status", metadata.safeStatus],
|
|
6386
|
+
["Source traces", metadata.sourceTraceCount],
|
|
6387
|
+
["Runs", metadata.runIds.join(", ")],
|
|
6388
|
+
...metadata.sessionId ? [["Session", metadata.sessionId]] : [],
|
|
6389
|
+
...metadata.since ? [["Since", metadata.since]] : []
|
|
6390
|
+
]),
|
|
6391
|
+
"",
|
|
6392
|
+
"## Safety checks",
|
|
6393
|
+
"",
|
|
6394
|
+
`Aggregate: **${checks.aggregateStatus}**`,
|
|
6395
|
+
""
|
|
6396
|
+
];
|
|
6397
|
+
for (const run of checks.runs) {
|
|
6398
|
+
const source = run.sourceStatus !== void 0 && run.sourceStatus !== run.status ? `; source ${run.sourceStatus}` : "";
|
|
6399
|
+
lines.push(
|
|
6400
|
+
`- \`${run.runId}\`: artifact ${run.status}${source} (${run.findings} finding(s), ${run.errors} error(s), ${run.warnings} warning(s))`
|
|
6401
|
+
);
|
|
6502
6402
|
}
|
|
6503
|
-
|
|
6504
|
-
const
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6403
|
+
lines.push("", "## Redaction", "", `Total findings: ${redaction.totalFindings}`, "");
|
|
6404
|
+
for (const run of redaction.runs) {
|
|
6405
|
+
const detectors = run.detectors.length > 0 ? run.detectors.join(", ") : "none";
|
|
6406
|
+
lines.push(`- \`${run.runId}\`: ${run.findings} finding(s); detectors: ${detectors}`);
|
|
6407
|
+
}
|
|
6408
|
+
lines.push(
|
|
6409
|
+
"",
|
|
6410
|
+
"## Files",
|
|
6411
|
+
"",
|
|
6412
|
+
...metadata.files.map((file) => `- \`${file}\``),
|
|
6413
|
+
"",
|
|
6414
|
+
"_Review every generated artifact before sharing outside your team._",
|
|
6415
|
+
""
|
|
6416
|
+
);
|
|
6417
|
+
return lines.join("\n");
|
|
6418
|
+
}
|
|
6419
|
+
function sanitizeBundleRunId(runId) {
|
|
6420
|
+
let safe = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "run_unknown";
|
|
6421
|
+
safe = path7__default.default.basename(safe);
|
|
6422
|
+
safe = safe.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
6423
|
+
if (safe === "" || safe === "." || safe === "..") {
|
|
6424
|
+
safe = "run_unknown";
|
|
6425
|
+
}
|
|
6426
|
+
return safe;
|
|
6427
|
+
}
|
|
6428
|
+
function bundleRunAssetRelativePath(runId, extension) {
|
|
6429
|
+
const safe = sanitizeBundleRunId(runId);
|
|
6430
|
+
const ext = extension.startsWith(".") ? extension : `.${extension}`;
|
|
6431
|
+
return path7__default.default.posix.join("assets", "runs", `${safe}${ext}`);
|
|
6432
|
+
}
|
|
6433
|
+
function assertBundlePathContained(outputDir, relativePath) {
|
|
6434
|
+
const base = path7__default.default.resolve(outputDir);
|
|
6435
|
+
const resolved = path7__default.default.resolve(base, relativePath);
|
|
6436
|
+
if (resolved !== base && !resolved.startsWith(base + path7__default.default.sep)) {
|
|
6437
|
+
throw new Error(`Bundle path escapes output directory: ${relativePath}`);
|
|
6438
|
+
}
|
|
6439
|
+
return resolved;
|
|
6440
|
+
}
|
|
6441
|
+
function normalizeBundleOutputPath(out, options) {
|
|
6442
|
+
const trimmed = out.trim();
|
|
6443
|
+
if (trimmed === "") {
|
|
6444
|
+
throw new Error("--out requires a non-empty path.");
|
|
6445
|
+
}
|
|
6446
|
+
const resolved = path7__default.default.resolve(trimmed);
|
|
6447
|
+
if (options?.preserveZipExtension !== true && resolved.toLowerCase().endsWith(".zip")) {
|
|
6448
|
+
return resolved.slice(0, -4);
|
|
6449
|
+
}
|
|
6450
|
+
return resolved;
|
|
6451
|
+
}
|
|
6452
|
+
function defaultBundleOutputPath(runIds) {
|
|
6453
|
+
const label = runIds.length === 1 ? sanitizeBundleRunId(runIds[0]) : `multi-${runIds.length}`;
|
|
6454
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6455
|
+
return path7__default.default.resolve(`agent-inspect-bundle-${label}-${stamp}`);
|
|
6456
|
+
}
|
|
6457
|
+
|
|
6458
|
+
// packages/core/src/evidence/types.ts
|
|
6459
|
+
var EVIDENCE_FORMAT_VERSION = "1.0";
|
|
6460
|
+
var EVIDENCE_ASSESSMENT_NOTE = "Best-effort local safety verification only; not a compliance certification.";
|
|
6461
|
+
var EVIDENCE_MANIFEST_FILENAME = "evidence.json";
|
|
6462
|
+
var EVIDENCE_RESOLVED_CONTRACT_FILENAME = "contract.resolved.json";
|
|
6463
|
+
var SHA256_RE = /^[a-f0-9]{64}$/i;
|
|
6464
|
+
function sha256Hex(data) {
|
|
6465
|
+
const hash = crypto.createHash("sha256");
|
|
6466
|
+
if (typeof data === "string") {
|
|
6467
|
+
hash.update(data, "utf8");
|
|
6468
|
+
} else {
|
|
6469
|
+
hash.update(data);
|
|
6470
|
+
}
|
|
6471
|
+
return hash.digest("hex");
|
|
6472
|
+
}
|
|
6473
|
+
function isSha256Hex(value) {
|
|
6474
|
+
return SHA256_RE.test(value);
|
|
6475
|
+
}
|
|
6476
|
+
function sha256Equals(expected, actual) {
|
|
6477
|
+
if (!isSha256Hex(expected) || !isSha256Hex(actual)) {
|
|
6478
|
+
return false;
|
|
6479
|
+
}
|
|
6480
|
+
const a = expected.toLowerCase();
|
|
6481
|
+
const b = actual.toLowerCase();
|
|
6482
|
+
if (a.length !== b.length) return false;
|
|
6483
|
+
let mismatch = 0;
|
|
6484
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
6485
|
+
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
6486
|
+
}
|
|
6487
|
+
return mismatch === 0;
|
|
6488
|
+
}
|
|
6489
|
+
function assertEvidenceRelativePath(relativePath) {
|
|
6490
|
+
if (typeof relativePath !== "string" || relativePath.trim() === "") {
|
|
6491
|
+
throw new Error("Evidence file path must be a non-empty relative path.");
|
|
6492
|
+
}
|
|
6493
|
+
const trimmed = relativePath.trim().replaceAll("\\", "/");
|
|
6494
|
+
if (path7__default.default.isAbsolute(trimmed) || trimmed.startsWith("/")) {
|
|
6495
|
+
throw new Error(`Evidence file path must be relative: ${relativePath}`);
|
|
6496
|
+
}
|
|
6497
|
+
const parts = trimmed.split("/").filter((part) => part !== "");
|
|
6498
|
+
if (parts.length === 0) {
|
|
6499
|
+
throw new Error(`Evidence file path must be relative: ${relativePath}`);
|
|
6500
|
+
}
|
|
6501
|
+
for (const part of parts) {
|
|
6502
|
+
if (part === "." || part === "..") {
|
|
6503
|
+
throw new Error(`Evidence file path must not contain "." or "..": ${relativePath}`);
|
|
6523
6504
|
}
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6505
|
+
}
|
|
6506
|
+
return parts.join("/");
|
|
6507
|
+
}
|
|
6508
|
+
|
|
6509
|
+
// packages/core/src/evidence/manifest.ts
|
|
6510
|
+
function stable(value) {
|
|
6511
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
6512
|
+
if (value === null || typeof value !== "object") return value;
|
|
6513
|
+
const record = value;
|
|
6514
|
+
return Object.fromEntries(
|
|
6515
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
|
|
6516
|
+
);
|
|
6517
|
+
}
|
|
6518
|
+
function serializeEvidenceManifest(manifest) {
|
|
6519
|
+
return `${JSON.stringify(stable(manifest), null, 2)}
|
|
6520
|
+
`;
|
|
6521
|
+
}
|
|
6522
|
+
function inferEvidenceFileRole(relativePath) {
|
|
6523
|
+
const normalized = assertEvidenceRelativePath(relativePath);
|
|
6524
|
+
const base = normalized.includes("/") ? normalized.slice(normalized.lastIndexOf("/") + 1) : normalized;
|
|
6525
|
+
if (base === "evidence.html" || base === "trace.html" || base.endsWith(".html")) {
|
|
6526
|
+
return "report";
|
|
6527
|
+
}
|
|
6528
|
+
if (base === "trace.jsonl" || base.endsWith(".jsonl")) {
|
|
6529
|
+
return "redacted-trace";
|
|
6530
|
+
}
|
|
6531
|
+
if (base === "check-results.json") {
|
|
6532
|
+
return "checks";
|
|
6533
|
+
}
|
|
6534
|
+
if (base === "contract.resolved.json") {
|
|
6535
|
+
return "contract";
|
|
6536
|
+
}
|
|
6537
|
+
if (base === "redaction-report.json") {
|
|
6538
|
+
return "redaction-report";
|
|
6539
|
+
}
|
|
6540
|
+
if (base === "summary.md") {
|
|
6541
|
+
return "summary";
|
|
6542
|
+
}
|
|
6543
|
+
return "other";
|
|
6544
|
+
}
|
|
6545
|
+
function buildEvidenceFileEntries(files) {
|
|
6546
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
6547
|
+
for (const file of files) {
|
|
6548
|
+
const relativePath = assertEvidenceRelativePath(file.path);
|
|
6549
|
+
if (relativePath === EVIDENCE_MANIFEST_FILENAME) {
|
|
6550
|
+
throw new Error(
|
|
6551
|
+
`Do not include ${EVIDENCE_MANIFEST_FILENAME} in packaged file hashes (self-hash is undefined).`
|
|
6552
|
+
);
|
|
6559
6553
|
}
|
|
6560
|
-
if (
|
|
6561
|
-
|
|
6562
|
-
code: "AI_LOGICAL_SELF_PARENT_REMOVED",
|
|
6563
|
-
message: formatProgrammaticDiagnostic(
|
|
6564
|
-
"AI_TRACE_RELATIONSHIP_SELF_PARENT",
|
|
6565
|
-
`Removed self-parent edge on ${event.eventId} (was ${originalParentId}).`
|
|
6566
|
-
),
|
|
6567
|
-
eventIds: [event.eventId]
|
|
6568
|
-
});
|
|
6569
|
-
const { parentId: _drop, ...rest } = event;
|
|
6570
|
-
normalized.push({
|
|
6571
|
-
...rest,
|
|
6572
|
-
projection: {
|
|
6573
|
-
...event.projection,
|
|
6574
|
-
parentNormalized: true,
|
|
6575
|
-
originalParentId
|
|
6576
|
-
}
|
|
6577
|
-
});
|
|
6578
|
-
continue;
|
|
6554
|
+
if (byPath.has(relativePath)) {
|
|
6555
|
+
throw new Error(`Duplicate evidence file path: ${relativePath}`);
|
|
6579
6556
|
}
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
});
|
|
6585
|
-
normalized.push({
|
|
6586
|
-
...event,
|
|
6587
|
-
parentId: nextParent,
|
|
6588
|
-
projection: {
|
|
6589
|
-
...event.projection,
|
|
6590
|
-
parentNormalized: true,
|
|
6591
|
-
originalParentId
|
|
6592
|
-
}
|
|
6557
|
+
byPath.set(relativePath, {
|
|
6558
|
+
path: relativePath,
|
|
6559
|
+
sha256: sha256Hex(file.content),
|
|
6560
|
+
role: file.role ?? inferEvidenceFileRole(relativePath)
|
|
6593
6561
|
});
|
|
6594
6562
|
}
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6563
|
+
return [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
6564
|
+
}
|
|
6565
|
+
function buildEvidenceManifest(parts) {
|
|
6566
|
+
const runIds = [...parts.runIds];
|
|
6567
|
+
if (runIds.length === 0) {
|
|
6568
|
+
throw new Error("Evidence manifest requires at least one run id.");
|
|
6569
|
+
}
|
|
6570
|
+
for (const item of parts.sourceHashes) {
|
|
6571
|
+
if (!runIds.includes(item.runId)) {
|
|
6572
|
+
throw new Error(`sourceHashes runId "${item.runId}" is not listed in source.runIds.`);
|
|
6573
|
+
}
|
|
6574
|
+
if (item.algorithm !== "sha256") {
|
|
6575
|
+
throw new Error(`Unsupported source hash algorithm: ${item.algorithm}`);
|
|
6576
|
+
}
|
|
6577
|
+
}
|
|
6578
|
+
const assessment = {
|
|
6579
|
+
status: parts.assessmentStatus,
|
|
6580
|
+
note: parts.note ?? EVIDENCE_ASSESSMENT_NOTE
|
|
6581
|
+
};
|
|
6582
|
+
if (parts.sourceStatus !== void 0) {
|
|
6583
|
+
assessment.sourceStatus = parts.sourceStatus;
|
|
6584
|
+
}
|
|
6601
6585
|
return {
|
|
6602
|
-
|
|
6603
|
-
|
|
6586
|
+
evidenceFormatVersion: EVIDENCE_FORMAT_VERSION,
|
|
6587
|
+
generator: {
|
|
6588
|
+
name: parts.generatorName ?? "agent-inspect",
|
|
6589
|
+
version: parts.generatorVersion
|
|
6590
|
+
},
|
|
6591
|
+
createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6592
|
+
source: {
|
|
6593
|
+
runIds,
|
|
6594
|
+
traceSchemaVersions: [...parts.traceSchemaVersions].sort((a, b) => a.localeCompare(b)),
|
|
6595
|
+
sourceHashes: [...parts.sourceHashes].sort((a, b) => a.runId.localeCompare(b.runId))
|
|
6596
|
+
},
|
|
6597
|
+
policy: {
|
|
6598
|
+
redactionProfile: parts.redactionProfile,
|
|
6599
|
+
verificationPolicy: parts.verificationPolicy ?? parts.redactionProfile
|
|
6600
|
+
},
|
|
6601
|
+
assessment,
|
|
6602
|
+
...parts.semantics !== void 0 ? { semantics: { ...parts.semantics } } : {},
|
|
6603
|
+
...parts.contract !== void 0 ? { contract: { ...parts.contract } } : {},
|
|
6604
|
+
...parts.inputs !== void 0 ? { inputs: { ...parts.inputs } } : {},
|
|
6605
|
+
files: buildEvidenceFileEntries(parts.files)
|
|
6604
6606
|
};
|
|
6605
6607
|
}
|
|
6606
|
-
function
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
if (direct) return direct;
|
|
6610
|
-
const metadata = attrs?.metadata;
|
|
6611
|
-
if (isRecord8(metadata)) {
|
|
6612
|
-
const nested = pickString(metadata, ["toolName", "tool"]);
|
|
6613
|
-
if (nested) return nested;
|
|
6608
|
+
function validateEvidenceManifest(value) {
|
|
6609
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
6610
|
+
throw new Error("Evidence manifest must be a JSON object.");
|
|
6614
6611
|
}
|
|
6615
|
-
|
|
6616
|
-
|
|
6612
|
+
const record = value;
|
|
6613
|
+
if (record.evidenceFormatVersion !== EVIDENCE_FORMAT_VERSION) {
|
|
6614
|
+
throw new Error(
|
|
6615
|
+
`Unsupported evidenceFormatVersion: ${String(record.evidenceFormatVersion)}`
|
|
6616
|
+
);
|
|
6617
6617
|
}
|
|
6618
|
-
|
|
6618
|
+
const generator = record.generator;
|
|
6619
|
+
if (generator === null || typeof generator !== "object" || Array.isArray(generator) || typeof generator.name !== "string" || typeof generator.version !== "string") {
|
|
6620
|
+
throw new Error("Evidence manifest requires generator.name and generator.version.");
|
|
6621
|
+
}
|
|
6622
|
+
const source = record.source;
|
|
6623
|
+
if (source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
6624
|
+
throw new Error("Evidence manifest requires source.");
|
|
6625
|
+
}
|
|
6626
|
+
const sourceRecord = source;
|
|
6627
|
+
if (!Array.isArray(sourceRecord.runIds) || sourceRecord.runIds.length === 0) {
|
|
6628
|
+
throw new Error("Evidence manifest source.runIds must be a non-empty array.");
|
|
6629
|
+
}
|
|
6630
|
+
if (!Array.isArray(sourceRecord.traceSchemaVersions)) {
|
|
6631
|
+
throw new Error("Evidence manifest source.traceSchemaVersions must be an array.");
|
|
6632
|
+
}
|
|
6633
|
+
if (!Array.isArray(sourceRecord.sourceHashes)) {
|
|
6634
|
+
throw new Error("Evidence manifest source.sourceHashes must be an array.");
|
|
6635
|
+
}
|
|
6636
|
+
const policy = record.policy;
|
|
6637
|
+
if (policy === null || typeof policy !== "object" || Array.isArray(policy)) {
|
|
6638
|
+
throw new Error("Evidence manifest requires policy.");
|
|
6639
|
+
}
|
|
6640
|
+
const assessment = record.assessment;
|
|
6641
|
+
if (assessment === null || typeof assessment !== "object" || Array.isArray(assessment) || typeof assessment.status !== "string") {
|
|
6642
|
+
throw new Error("Evidence manifest requires assessment.status.");
|
|
6643
|
+
}
|
|
6644
|
+
if (!Array.isArray(record.files) || record.files.length === 0) {
|
|
6645
|
+
throw new Error("Evidence manifest files must be a non-empty array.");
|
|
6646
|
+
}
|
|
6647
|
+
for (const file of record.files) {
|
|
6648
|
+
if (file === null || typeof file !== "object" || Array.isArray(file)) {
|
|
6649
|
+
throw new Error("Evidence manifest file entries must be objects.");
|
|
6650
|
+
}
|
|
6651
|
+
const entry = file;
|
|
6652
|
+
if (typeof entry.path !== "string") {
|
|
6653
|
+
throw new Error("Evidence file entry requires path.");
|
|
6654
|
+
}
|
|
6655
|
+
assertEvidenceRelativePath(entry.path);
|
|
6656
|
+
if (typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/i.test(entry.sha256)) {
|
|
6657
|
+
throw new Error(`Evidence file entry requires sha256 hex for ${entry.path}.`);
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
return value;
|
|
6619
6661
|
}
|
|
6620
|
-
function
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6662
|
+
function parseEvidenceManifestJson(text) {
|
|
6663
|
+
let parsed;
|
|
6664
|
+
try {
|
|
6665
|
+
parsed = JSON.parse(text);
|
|
6666
|
+
} catch (error) {
|
|
6667
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6668
|
+
throw new Error(`Evidence manifest is not valid JSON: ${message}`);
|
|
6625
6669
|
}
|
|
6626
|
-
return
|
|
6670
|
+
return validateEvidenceManifest(parsed);
|
|
6671
|
+
}
|
|
6672
|
+
function collectTraceSchemaVersions(jsonl) {
|
|
6673
|
+
const versions = /* @__PURE__ */ new Set();
|
|
6674
|
+
for (const line of jsonl.split(/\r?\n/)) {
|
|
6675
|
+
const trimmed = line.trim();
|
|
6676
|
+
if (trimmed === "") continue;
|
|
6677
|
+
try {
|
|
6678
|
+
const row = JSON.parse(trimmed);
|
|
6679
|
+
if (typeof row.schemaVersion === "string" && row.schemaVersion.trim() !== "") {
|
|
6680
|
+
versions.add(row.schemaVersion.trim());
|
|
6681
|
+
}
|
|
6682
|
+
} catch {
|
|
6683
|
+
}
|
|
6684
|
+
}
|
|
6685
|
+
return [...versions].sort((a, b) => a.localeCompare(b));
|
|
6627
6686
|
}
|
|
6628
6687
|
|
|
6629
6688
|
// packages/core/src/checks/trace-facts.ts
|
|
@@ -6659,7 +6718,11 @@ function emptySummary2() {
|
|
|
6659
6718
|
failed: 0,
|
|
6660
6719
|
warnings: 0,
|
|
6661
6720
|
errors: 0,
|
|
6662
|
-
rulesEvaluated: 0
|
|
6721
|
+
rulesEvaluated: 0,
|
|
6722
|
+
rulesPassed: 0,
|
|
6723
|
+
rulesWarning: 0,
|
|
6724
|
+
rulesFailed: 0,
|
|
6725
|
+
rulesError: 0
|
|
6663
6726
|
};
|
|
6664
6727
|
}
|
|
6665
6728
|
function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
|
|
@@ -6671,7 +6734,11 @@ function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
|
|
|
6671
6734
|
summary: {
|
|
6672
6735
|
...emptySummary2(),
|
|
6673
6736
|
errors: diagnostics.filter((item) => item.severity === "error").length,
|
|
6674
|
-
rulesEvaluated: ruleExecutions.length
|
|
6737
|
+
rulesEvaluated: ruleExecutions.length,
|
|
6738
|
+
rulesPassed: ruleExecutions.filter((item) => item.status === "pass").length,
|
|
6739
|
+
rulesWarning: ruleExecutions.filter((item) => item.status === "warning").length,
|
|
6740
|
+
rulesFailed: ruleExecutions.filter((item) => item.status === "fail").length,
|
|
6741
|
+
rulesError: ruleExecutions.filter((item) => item.status === "error").length
|
|
6675
6742
|
},
|
|
6676
6743
|
findings: [],
|
|
6677
6744
|
diagnostics: [...diagnostics],
|
|
@@ -6831,7 +6898,7 @@ function normalizeFinding(rule, finding) {
|
|
|
6831
6898
|
...finding.action !== void 0 ? { action: finding.action } : {}
|
|
6832
6899
|
};
|
|
6833
6900
|
}
|
|
6834
|
-
function summarize(findings, diagnostics,
|
|
6901
|
+
function summarize(findings, diagnostics, ruleExecutions) {
|
|
6835
6902
|
return {
|
|
6836
6903
|
passed: findings.filter((finding) => finding.status === "pass").length,
|
|
6837
6904
|
failed: findings.filter(
|
|
@@ -6841,7 +6908,11 @@ function summarize(findings, diagnostics, rulesEvaluated) {
|
|
|
6841
6908
|
(finding) => finding.status === "warning" || finding.severity === "warning"
|
|
6842
6909
|
).length,
|
|
6843
6910
|
errors: diagnostics.filter((item) => item.severity === "error").length,
|
|
6844
|
-
rulesEvaluated
|
|
6911
|
+
rulesEvaluated: ruleExecutions.length,
|
|
6912
|
+
rulesPassed: ruleExecutions.filter((item) => item.status === "pass").length,
|
|
6913
|
+
rulesWarning: ruleExecutions.filter((item) => item.status === "warning").length,
|
|
6914
|
+
rulesFailed: ruleExecutions.filter((item) => item.status === "fail").length,
|
|
6915
|
+
rulesError: ruleExecutions.filter((item) => item.status === "error").length
|
|
6845
6916
|
};
|
|
6846
6917
|
}
|
|
6847
6918
|
function classifyRuleExecution(findings, threw) {
|
|
@@ -7318,7 +7389,7 @@ function runTraceChecks(input, options = {}) {
|
|
|
7318
7389
|
}
|
|
7319
7390
|
const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
|
|
7320
7391
|
const sortedFindings = findings.sort(compareFindings(eventById));
|
|
7321
|
-
const summary = summarize(sortedFindings, diagnostics, ruleExecutions
|
|
7392
|
+
const summary = summarize(sortedFindings, diagnostics, ruleExecutions);
|
|
7322
7393
|
const status = summary.failed > 0 ? "fail" : "pass";
|
|
7323
7394
|
return {
|
|
7324
7395
|
ok: status === "pass",
|
|
@@ -9395,6 +9466,28 @@ function asPositiveNumber(value, label) {
|
|
|
9395
9466
|
}
|
|
9396
9467
|
return value;
|
|
9397
9468
|
}
|
|
9469
|
+
function asExpectConfig(value, label) {
|
|
9470
|
+
if (value === void 0) return void 0;
|
|
9471
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
9472
|
+
throw new Error(`${label} must be an object.`);
|
|
9473
|
+
}
|
|
9474
|
+
const raw = value;
|
|
9475
|
+
const checkStatus = raw.checkStatus;
|
|
9476
|
+
if (checkStatus !== "pass" && checkStatus !== "fail" && checkStatus !== "error") {
|
|
9477
|
+
throw new Error(`${label}.checkStatus must be "pass", "fail", or "error".`);
|
|
9478
|
+
}
|
|
9479
|
+
const findingRuleIds = asStringArray(raw.findingRuleIds, `${label}.findingRuleIds`);
|
|
9480
|
+
const minAssertions = asPositiveNumber(raw.minAssertions, `${label}.minAssertions`);
|
|
9481
|
+
const rejectUnexpectedFindings = raw.rejectUnexpectedFindings === void 0 ? void 0 : typeof raw.rejectUnexpectedFindings === "boolean" ? raw.rejectUnexpectedFindings : (() => {
|
|
9482
|
+
throw new Error(`${label}.rejectUnexpectedFindings must be a boolean.`);
|
|
9483
|
+
})();
|
|
9484
|
+
return {
|
|
9485
|
+
checkStatus,
|
|
9486
|
+
...findingRuleIds !== void 0 ? { findingRuleIds } : {},
|
|
9487
|
+
...rejectUnexpectedFindings !== void 0 ? { rejectUnexpectedFindings } : {},
|
|
9488
|
+
...minAssertions !== void 0 ? { minAssertions } : {}
|
|
9489
|
+
};
|
|
9490
|
+
}
|
|
9398
9491
|
function validateCaseConfig(value, index) {
|
|
9399
9492
|
const diagnostics = [];
|
|
9400
9493
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
@@ -9437,7 +9530,8 @@ function validateCaseConfig(value, index) {
|
|
|
9437
9530
|
raw.expectedObservations,
|
|
9438
9531
|
`cases[${index}].expectedObservations`
|
|
9439
9532
|
)
|
|
9440
|
-
} : {}
|
|
9533
|
+
} : {},
|
|
9534
|
+
...asExpectConfig(raw.expect, `cases[${index}].expect`) !== void 0 ? { expect: asExpectConfig(raw.expect, `cases[${index}].expect`) } : {}
|
|
9441
9535
|
},
|
|
9442
9536
|
diagnostics
|
|
9443
9537
|
};
|
|
@@ -12074,6 +12168,78 @@ function validateExpectedObservations(suiteCase, read) {
|
|
|
12074
12168
|
}
|
|
12075
12169
|
return { ok: diagnostics.length === 0, diagnostics };
|
|
12076
12170
|
}
|
|
12171
|
+
function evaluateCaseExpect(suiteCase, checkResult, observationCount) {
|
|
12172
|
+
const expectation = suiteCase.expect;
|
|
12173
|
+
if (expectation === void 0) {
|
|
12174
|
+
return { ok: true, diagnostics: [] };
|
|
12175
|
+
}
|
|
12176
|
+
const diagnostics = [];
|
|
12177
|
+
if (checkResult.status !== expectation.checkStatus) {
|
|
12178
|
+
diagnostics.push(
|
|
12179
|
+
diagnostic4(
|
|
12180
|
+
"AI_SUITE_EXPECT_MISMATCH",
|
|
12181
|
+
`Expected check status "${expectation.checkStatus}", got "${checkResult.status}".`,
|
|
12182
|
+
"error",
|
|
12183
|
+
suiteCase.id
|
|
12184
|
+
)
|
|
12185
|
+
);
|
|
12186
|
+
}
|
|
12187
|
+
const failedFindings = checkResult.findings.filter(
|
|
12188
|
+
(finding) => finding.status === "fail" && finding.severity === "error"
|
|
12189
|
+
);
|
|
12190
|
+
const requiredIds = expectation.findingRuleIds ?? [];
|
|
12191
|
+
for (const ruleId of requiredIds) {
|
|
12192
|
+
if (!failedFindings.some((finding) => finding.ruleId === ruleId)) {
|
|
12193
|
+
diagnostics.push(
|
|
12194
|
+
diagnostic4(
|
|
12195
|
+
"AI_SUITE_EXPECT_MISMATCH",
|
|
12196
|
+
`Expected failing finding for rule "${ruleId}" was not present.`,
|
|
12197
|
+
"error",
|
|
12198
|
+
suiteCase.id
|
|
12199
|
+
)
|
|
12200
|
+
);
|
|
12201
|
+
}
|
|
12202
|
+
}
|
|
12203
|
+
const rejectUnexpected = expectation.rejectUnexpectedFindings !== false;
|
|
12204
|
+
if (rejectUnexpected && requiredIds.length > 0) {
|
|
12205
|
+
for (const finding of failedFindings) {
|
|
12206
|
+
if (!requiredIds.includes(finding.ruleId)) {
|
|
12207
|
+
diagnostics.push(
|
|
12208
|
+
diagnostic4(
|
|
12209
|
+
"AI_SUITE_EXPECT_MISMATCH",
|
|
12210
|
+
`Unexpected failing finding for rule "${finding.ruleId}".`,
|
|
12211
|
+
"error",
|
|
12212
|
+
suiteCase.id
|
|
12213
|
+
)
|
|
12214
|
+
);
|
|
12215
|
+
}
|
|
12216
|
+
}
|
|
12217
|
+
}
|
|
12218
|
+
if (expectation.minAssertions !== void 0) {
|
|
12219
|
+
const assertionCount = checkResult.ruleExecutions.length + observationCount;
|
|
12220
|
+
if (assertionCount < expectation.minAssertions) {
|
|
12221
|
+
diagnostics.push(
|
|
12222
|
+
diagnostic4(
|
|
12223
|
+
"AI_SUITE_EXPECT_MISMATCH",
|
|
12224
|
+
`Expected at least ${expectation.minAssertions} assertion(s), got ${assertionCount}.`,
|
|
12225
|
+
"error",
|
|
12226
|
+
suiteCase.id
|
|
12227
|
+
)
|
|
12228
|
+
);
|
|
12229
|
+
}
|
|
12230
|
+
}
|
|
12231
|
+
if (expectation.checkStatus === "fail" && checkResult.diagnostics.some((item) => item.severity === "error") && checkResult.status === "error") {
|
|
12232
|
+
diagnostics.push(
|
|
12233
|
+
diagnostic4(
|
|
12234
|
+
"AI_SUITE_EXPECT_MISMATCH",
|
|
12235
|
+
'Check ended in evaluator/config error; semantic failure expectation requires status "fail".',
|
|
12236
|
+
"error",
|
|
12237
|
+
suiteCase.id
|
|
12238
|
+
)
|
|
12239
|
+
);
|
|
12240
|
+
}
|
|
12241
|
+
return { ok: diagnostics.length === 0, diagnostics };
|
|
12242
|
+
}
|
|
12077
12243
|
async function runSuiteCase(suiteCase, config, options) {
|
|
12078
12244
|
const resolved = await resolveSuiteCaseTrace(suiteCase, options);
|
|
12079
12245
|
if (resolved.missing || resolved.tracePath === void 0) {
|
|
@@ -12126,9 +12292,21 @@ async function runSuiteCase(suiteCase, config, options) {
|
|
|
12126
12292
|
status: "pass",
|
|
12127
12293
|
format: read.format,
|
|
12128
12294
|
findings: [],
|
|
12129
|
-
diagnostics: []
|
|
12295
|
+
diagnostics: [],
|
|
12296
|
+
ruleExecutions: []
|
|
12297
|
+
};
|
|
12130
12298
|
const observationResult = validateExpectedObservations(suiteCase, read);
|
|
12131
|
-
const
|
|
12299
|
+
const checkOk = checkResult.ok;
|
|
12300
|
+
const observationsOk = observationResult.ok;
|
|
12301
|
+
const evalOk = config.eval?.requireSuccess === true ? checkResult.findings.every(
|
|
12302
|
+
(finding) => finding.ruleId !== "run.status" || finding.status !== "fail"
|
|
12303
|
+
) && checkResult.diagnostics.every((item) => item.severity !== "error") : void 0;
|
|
12304
|
+
const expectResult = evaluateCaseExpect(
|
|
12305
|
+
suiteCase,
|
|
12306
|
+
checkResult,
|
|
12307
|
+
compiled.observationCount
|
|
12308
|
+
);
|
|
12309
|
+
const checkFailureDiagnostics = [
|
|
12132
12310
|
...checkResult.diagnostics.map(
|
|
12133
12311
|
(item) => diagnostic4(
|
|
12134
12312
|
"AI_SUITE_CASE_CHECK_FAILED",
|
|
@@ -12144,15 +12322,24 @@ async function runSuiteCase(suiteCase, config, options) {
|
|
|
12144
12322
|
finding.severity,
|
|
12145
12323
|
suiteCase.id
|
|
12146
12324
|
)
|
|
12147
|
-
)
|
|
12148
|
-
...observationResult.diagnostics
|
|
12325
|
+
)
|
|
12149
12326
|
];
|
|
12150
|
-
const
|
|
12151
|
-
const
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12327
|
+
const expectMatched = suiteCase.expect !== void 0 && expectResult.ok && observationsOk;
|
|
12328
|
+
const diagnostics = expectMatched ? [
|
|
12329
|
+
...observationResult.diagnostics,
|
|
12330
|
+
...expectResult.diagnostics,
|
|
12331
|
+
diagnostic4(
|
|
12332
|
+
"AI_SUITE_CASE_CHECK_FAILED",
|
|
12333
|
+
`Expected semantic failure matched (check.status=${checkResult.status}).`,
|
|
12334
|
+
"info",
|
|
12335
|
+
suiteCase.id
|
|
12336
|
+
)
|
|
12337
|
+
] : [
|
|
12338
|
+
...checkFailureDiagnostics,
|
|
12339
|
+
...observationResult.diagnostics,
|
|
12340
|
+
...expectResult.diagnostics
|
|
12341
|
+
];
|
|
12342
|
+
const ok = suiteCase.expect !== void 0 ? expectResult.ok && observationsOk : checkOk && observationsOk;
|
|
12156
12343
|
const status = ok ? "pass" : checkResult.status === "error" ? "error" : "fail";
|
|
12157
12344
|
return {
|
|
12158
12345
|
id: suiteCase.id,
|
|
@@ -13345,6 +13532,18 @@ function renderGateReport(result, options = {}) {
|
|
|
13345
13532
|
}
|
|
13346
13533
|
|
|
13347
13534
|
// packages/core/src/inspect-run.ts
|
|
13535
|
+
var INSPECT_RUN_UNSUPPORTED_OPTION_KEYS = ["writer"];
|
|
13536
|
+
function warnUnsupportedInspectRunOptions(options) {
|
|
13537
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
13538
|
+
return;
|
|
13539
|
+
}
|
|
13540
|
+
const record = options;
|
|
13541
|
+
for (const key of INSPECT_RUN_UNSUPPORTED_OPTION_KEYS) {
|
|
13542
|
+
if (Object.prototype.hasOwnProperty.call(record, key) && record[key] !== void 0) {
|
|
13543
|
+
warn(formatProgrammaticDiagnostic("AI_INSPECT_RUN_UNSUPPORTED_OPTION"));
|
|
13544
|
+
}
|
|
13545
|
+
}
|
|
13546
|
+
}
|
|
13348
13547
|
function normalizeRunName(name) {
|
|
13349
13548
|
if (typeof name !== "string" || name.trim() === "") {
|
|
13350
13549
|
return "unnamed-run";
|
|
@@ -13362,6 +13561,7 @@ async function inspectRun(name, fn, options) {
|
|
|
13362
13561
|
if (typeof fn !== "function") {
|
|
13363
13562
|
throw new TypeError("inspectRun requires `fn` to be a function");
|
|
13364
13563
|
}
|
|
13564
|
+
warnUnsupportedInspectRunOptions(options);
|
|
13365
13565
|
if (options?.enabled === false) {
|
|
13366
13566
|
return Promise.resolve(fn());
|
|
13367
13567
|
}
|