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.
Files changed (61) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/docs/NESTJS.md +56 -18
  4. package/package.json +1 -1
  5. package/packages/cli/dist/{chunk-SWUZRRDT.mjs → chunk-MQYHJL3W.mjs} +2216 -1938
  6. package/packages/cli/dist/chunk-MQYHJL3W.mjs.map +1 -0
  7. package/packages/cli/dist/index.cjs +975 -671
  8. package/packages/cli/dist/index.cjs.map +1 -1
  9. package/packages/cli/dist/index.mjs +32 -9
  10. package/packages/cli/dist/index.mjs.map +1 -1
  11. package/packages/cli/dist/{src-XFIPYODW.mjs → src-76R6E2LQ.mjs} +3 -3
  12. package/packages/cli/dist/{src-XFIPYODW.mjs.map → src-76R6E2LQ.mjs.map} +1 -1
  13. package/packages/core/dist/advanced.cjs +2079 -1879
  14. package/packages/core/dist/advanced.cjs.map +1 -1
  15. package/packages/core/dist/advanced.d.cts +56 -4
  16. package/packages/core/dist/advanced.d.ts +56 -4
  17. package/packages/core/dist/advanced.mjs +188 -23
  18. package/packages/core/dist/advanced.mjs.map +1 -1
  19. package/packages/core/dist/checks.cjs +126 -9
  20. package/packages/core/dist/checks.cjs.map +1 -1
  21. package/packages/core/dist/checks.d.cts +3 -3
  22. package/packages/core/dist/checks.d.ts +3 -3
  23. package/packages/core/dist/checks.mjs +3 -2
  24. package/packages/core/dist/{chunk-L5WLVBW4.mjs → chunk-AHUZNHFI.mjs} +4 -55
  25. package/packages/core/dist/chunk-AHUZNHFI.mjs.map +1 -0
  26. package/packages/core/dist/{chunk-2CCRUI42.mjs → chunk-CCYKNAOD.mjs} +119 -12
  27. package/packages/core/dist/chunk-CCYKNAOD.mjs.map +1 -0
  28. package/packages/core/dist/{chunk-7YYS742B.mjs → chunk-M3RJD3BP.mjs} +5 -5
  29. package/packages/core/dist/{chunk-7YYS742B.mjs.map → chunk-M3RJD3BP.mjs.map} +1 -1
  30. package/packages/core/dist/{chunk-2IRRUNIN.mjs → chunk-OAUNS5CS.mjs} +3 -3
  31. package/packages/core/dist/{chunk-2IRRUNIN.mjs.map → chunk-OAUNS5CS.mjs.map} +1 -1
  32. package/packages/core/dist/{chunk-IJJSPJXV.mjs → chunk-QOC3PJO3.mjs} +4 -4
  33. package/packages/core/dist/{chunk-IJJSPJXV.mjs.map → chunk-QOC3PJO3.mjs.map} +1 -1
  34. package/packages/core/dist/chunk-S6LQBLX5.mjs +67 -0
  35. package/packages/core/dist/chunk-S6LQBLX5.mjs.map +1 -0
  36. package/packages/core/dist/{chunk-3SPBELJB.mjs → chunk-SSG3ICI4.mjs} +16 -2
  37. package/packages/core/dist/chunk-SSG3ICI4.mjs.map +1 -0
  38. package/packages/core/dist/exporters.cjs +3 -3
  39. package/packages/core/dist/exporters.cjs.map +1 -1
  40. package/packages/core/dist/exporters.mjs +1 -1
  41. package/packages/core/dist/{index-EQihlEei.d.cts → index-DACU4Dy1.d.cts} +12 -0
  42. package/packages/core/dist/{index-CgOAGuM5.d.ts → index-b4tIGrzq.d.ts} +12 -0
  43. package/packages/core/dist/index.cjs +77 -0
  44. package/packages/core/dist/index.cjs.map +1 -1
  45. package/packages/core/dist/index.mjs +3 -2
  46. package/packages/core/dist/index.mjs.map +1 -1
  47. package/packages/core/dist/persisted.cjs +11 -0
  48. package/packages/core/dist/persisted.cjs.map +1 -1
  49. package/packages/core/dist/persisted.mjs +4 -3
  50. package/packages/core/dist/persisted.mjs.map +1 -1
  51. package/packages/core/dist/{programmatic-gZTWLObB.d.cts → programmatic-BLlC7Ytq.d.cts} +1 -1
  52. package/packages/core/dist/{programmatic-gZTWLObB.d.ts → programmatic-BLlC7Ytq.d.ts} +1 -1
  53. package/packages/core/dist/readers.cjs +11 -0
  54. package/packages/core/dist/readers.cjs.map +1 -1
  55. package/packages/core/dist/readers.d.cts +1 -1
  56. package/packages/core/dist/readers.d.ts +1 -1
  57. package/packages/core/dist/readers.mjs +4 -3
  58. package/packages/cli/dist/chunk-SWUZRRDT.mjs.map +0 -1
  59. package/packages/core/dist/chunk-2CCRUI42.mjs.map +0 -1
  60. package/packages/core/dist/chunk-3SPBELJB.mjs.map +0 -1
  61. 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 outcomeLine(summary) {
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 "Outcome unknown \u2014 inspect events may be incomplete.";
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(`Outcome: ${outcomeLine(summary)}`);
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/explain.ts
4573
- function toDisplayStatus(status) {
4574
- if (status === "ok") return "success";
4575
- if (status === "error") return "error";
4576
- if (status === "running") return "running";
4577
- return "unknown";
4578
- }
4579
- function flatten(nodes, out = []) {
4580
- for (const node of nodes) {
4581
- out.push({ node, index: out.length + 1 });
4582
- flatten(node.children, out);
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
- return out;
4585
- }
4586
- function redactValue(redactor, key, value) {
4587
- return redactor.redactValue(key, value);
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
- function topKinds(run) {
4599
- return Object.entries(run.metadata.kinds).filter(([, count]) => count > 0).sort((a, b) => {
4600
- if (b[1] !== a[1]) return b[1] - a[1];
4601
- return a[0].localeCompare(b[0]);
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 countErrorNodes(nodes) {
4605
- return nodes.filter((entry) => entry.node.event.status === "error").length;
4640
+ function legacyEvent(event) {
4641
+ const value = event.attributes?.legacyEvent;
4642
+ return typeof value === "string" ? value : void 0;
4606
4643
  }
4607
- function slowestNode(nodes) {
4608
- return nodes.filter(
4609
- (entry) => entry.node.event.durationMs !== void 0 && // The RUN boundary spans the whole run, so its duration always dominates
4610
- // and shadows every real step. Rank actual work nodes only, matching the
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 attributeFacts(nodes, redactor) {
4619
- const facts = [];
4620
- for (const entry of nodes) {
4621
- const attrs = entry.node.event.attributes;
4622
- if (attrs === void 0) continue;
4623
- for (const key of Object.keys(attrs).sort()) {
4624
- facts.push({
4625
- id: `node.${entry.index}.attributes.${key}`,
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 buildFacts(run, redactor) {
4637
- const nodes = flatten(run.children);
4638
- const facts = [
4639
- fact("run.id", "Run id", run.runId, redactor),
4640
- fact("run.name", "Run name", run.name ?? run.runId, redactor),
4641
- fact("run.status", "Run status", toDisplayStatus(run.status), redactor),
4642
- fact("run.totalEvents", "Total events", run.metadata.totalEvents, redactor),
4643
- fact("run.stepCount", "Top-level step count", run.children.length, redactor),
4644
- fact("run.nodeCount", "Total node count", nodes.length, redactor),
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
- facts.push(...attributeFacts(nodes, redactor));
4662
- return facts;
4668
+ return Object.keys(merged).length > 0 ? merged : void 0;
4663
4669
  }
4664
- function buildInferences(run, facts) {
4665
- const inferences = [];
4666
- const errorFact = facts.find((item) => item.id === "run.errorNodeCount");
4667
- const kindFact = facts.find((item) => item.id === "run.kinds");
4668
- const durationFact = facts.find((item) => item.id === "run.durationMs");
4669
- const errorNodeCount = typeof errorFact?.value === "number" ? errorFact.value : 0;
4670
- if (run.status === "error" || errorNodeCount > 0) {
4671
- inferences.push({
4672
- id: "outcome.error",
4673
- label: "Outcome",
4674
- text: "The run recorded an error status or at least one error node.",
4675
- evidence: ["run.status", "run.errorNodeCount"],
4676
- confidence: "deterministic"
4677
- });
4678
- } else if (run.status === "ok") {
4679
- inferences.push({
4680
- id: "outcome.success",
4681
- label: "Outcome",
4682
- text: "The run completed without observed error nodes.",
4683
- evidence: ["run.status", "run.errorNodeCount"],
4684
- confidence: "deterministic"
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 buildLocalExplanation(run, options = {}) {
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
- mode,
4715
- runId: String(redactValue(redactor, "runId", run.runId)),
4716
- ...run.name !== void 0 ? { name: String(redactValue(redactor, "name", run.name)) } : {},
4717
- ...run.status !== void 0 ? { status: toDisplayStatus(run.status) } : {},
4718
- redactionProfile,
4719
- facts,
4720
- inferences: mode === "dry-run" ? [] : buildInferences(run, facts),
4721
- notes: [
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
- // packages/core/src/search.ts
4729
- function parseDurationFilter(expr) {
4730
- const raw = expr.trim();
4731
- const m = raw.match(/^(>=|<=|>|<)\s*(.+)$/);
4732
- if (!m) {
4733
- throw new Error(
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 limit = options.limit ?? 50;
4773
- const sessionId = options.session?.trim();
4774
- const observationStatus2 = parseObservationFilter(options.observation);
4775
- const hasContentFilter = Boolean(
4776
- options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter || observationStatus2
4777
- );
4778
- const results = [];
4779
- const sessionLabel = sessionId && sessionId !== "" ? sessionId : void 0;
4780
- if (!hasContentFilter) {
4781
- for (const m of filtered) {
4782
- results.push({
4783
- runId: m.runId,
4784
- runName: m.name,
4785
- runStatus: m.status,
4786
- timestamp: m.startedAt,
4787
- durationMs: m.durationMs,
4788
- matchReason: sessionLabel ? `trace in session ${sessionLabel}` : "trace in directory",
4789
- matchedFields: sessionLabel ? ["run", "session"] : ["run"],
4790
- filePath: m.filePath,
4791
- ...sessionLabel ? { sessionId: sessionLabel } : {}
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
- for (const m of filtered) {
4803
- if (options.status && m.status !== options.status) continue;
4804
- let events = [];
4805
- try {
4806
- events = await readTraceEventsFromFile(m.filePath);
4807
- } catch {
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
- if (events.length === 0) continue;
4811
- const runMatches = matchRunLevel(m, {
4812
- stepTypeFilter,
4813
- nameQuery,
4814
- toolQuery,
4815
- durationFilter,
4816
- statusFilter: options.status
4817
- });
4818
- results.push(...runMatches);
4819
- const stepMatches = matchStepLevel(m, events, {
4820
- stepTypeFilter,
4821
- nameQuery,
4822
- toolQuery,
4823
- durationFilter,
4824
- statusFilter: options.status
4825
- });
4826
- results.push(...stepMatches);
4827
- if (observationStatus2) {
4828
- const outcomes = extractOutcomesFromTraceEvents(events);
4829
- const matched = outcomes.filter((outcome) => outcome.status === observationStatus2);
4830
- for (const outcome of matched) {
4831
- results.push({
4832
- runId: m.runId,
4833
- runName: m.name,
4834
- runStatus: m.status,
4835
- stepName: outcome.name,
4836
- timestamp: outcome.observedAt,
4837
- matchReason: `observation status=${outcome.status}`,
4838
- matchedFields: ["outcome.status", "outcome.name"],
4839
- filePath: m.filePath
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
- results.sort((a, b) => {
4845
- const ta = a.timestamp ?? 0;
4846
- const tb = b.timestamp ?? 0;
4847
- if (ta !== tb) return tb - ta;
4848
- const runCmp = a.runId.localeCompare(b.runId);
4849
- if (runCmp !== 0) return runCmp;
4850
- return (a.stepName ?? "").localeCompare(b.stepName ?? "");
4851
- });
4852
- return results.slice(0, limit);
4853
- }
4854
- function matchRunLevel(m, opts) {
4855
- if (opts.stepTypeFilter || opts.toolQuery) return [];
4856
- if (opts.statusFilter && m.status !== opts.statusFilter) return [];
4857
- if (opts.nameQuery && !nameMatches(m.name ?? m.runId, opts.nameQuery)) {
4858
- return [];
4859
- }
4860
- if (opts.durationFilter && !durationMatches(m.durationMs, opts.durationFilter)) {
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
- if (opts.nameQuery) {
4909
- if (!nameMatches(s.name, opts.nameQuery)) continue;
4910
- fields.push("step.name");
4911
- }
4912
- if (opts.toolQuery) {
4913
- const toolName2 = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
4914
- if (!nameMatches(toolName2, opts.toolQuery)) continue;
4915
- fields.push("step.tool");
4916
- }
4917
- if (opts.durationFilter) {
4918
- if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
4919
- fields.push("step.durationMs");
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
- return out;
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
- async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
4942
- const metas = [];
4943
- for (const fileName of fileNames) {
4944
- try {
4945
- const filePath = getPath(fileName);
4946
- const meta = await extractMetadata(filePath);
4947
- metas.push(meta);
4948
- } catch {
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
- return metas;
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 Math.trunc(value);
4924
+ return event.name;
4963
4925
  }
4964
- function extractSessionWorkflowMetadata(record) {
4926
+ function pickString(record, keys) {
4965
4927
  if (!record) return void 0;
4966
- const out = {};
4967
- let found = false;
4968
- const assignString = (key, value) => {
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/sessions/status.ts
5019
- var DEFAULT_STALE_THRESHOLD_MS = 864e5;
5020
- var EXPLICIT_STATUS_PRIORITY = {
5021
- error: 5,
5022
- waiting_input: 4,
5023
- idle: 3,
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 latestActivityMs(runs) {
5043
- let latest = 0;
5044
- for (const run of runs) {
5045
- const ms = activityMs(run);
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 latest;
4947
+ return out;
5049
4948
  }
5050
- function earliestStart(runs) {
5051
- let earliest;
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 latestEndWhenAllEnded(runs) {
5061
- if (runs.length === 0) return void 0;
5062
- let latest;
5063
- for (const run of runs) {
5064
- if (run.endedAt === void 0) return void 0;
5065
- if (latest === void 0 || run.endedAt > latest) latest = run.endedAt;
5066
- }
5067
- return latest;
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 pickExplicitStatus(runs) {
5070
- let best;
5071
- let bestPriority = 0;
5072
- for (const run of runs) {
5073
- const raw = run.metadata?.sessionStatus;
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 deriveLastError(runs) {
5084
- const errorRuns = runs.filter((run) => run.status === "error").sort((a, b) => activityMs(b) - activityMs(a));
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 deriveCheckSummary(runs) {
5093
- let pass = 0;
5094
- let fail = 0;
5095
- let warn2 = 0;
5096
- let found = false;
5097
- for (const run of runs) {
5098
- const summary = run.metadata?.checkSummary;
5099
- if (!summary || typeof summary !== "object") continue;
5100
- const record = summary;
5101
- if (typeof record.pass === "number") {
5102
- pass += record.pass;
5103
- found = true;
5104
- }
5105
- if (typeof record.fail === "number") {
5106
- fail += record.fail;
5107
- found = true;
5108
- }
5109
- if (typeof record.warn === "number") {
5110
- warn2 += record.warn;
5111
- found = true;
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 found ? { pass, fail, warn: warn2 } : void 0;
4997
+ return facts;
5115
4998
  }
5116
- function deriveObservationSummary(runs) {
5117
- for (const run of [...runs].sort((a, b) => activityMs(b) - activityMs(a))) {
5118
- const value = run.metadata?.observationSummary;
5119
- if (typeof value === "string" && value.trim() !== "") {
5120
- return value.trim();
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
- return void 0;
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 enrichSessionSummary(summary, runs, options = {}) {
5139
- const sessionRuns = runs.filter((run) => summary.runIds.includes(run.runId)).sort((a, b) => a.runId.localeCompare(b.runId));
5140
- const startedAt = earliestStart(sessionRuns);
5141
- const endedAt = latestEndWhenAllEnded(sessionRuns);
5142
- const durationMs2 = startedAt !== void 0 && endedAt !== void 0 ? endedAt - startedAt : void 0;
5143
- let correlationId;
5144
- let jobId;
5145
- let workflowId;
5146
- for (const run of sessionRuns) {
5147
- const meta = extractSessionWorkflowMetadata(run.metadata);
5148
- if (!correlationId && meta?.correlationId) correlationId = meta.correlationId;
5149
- if (!jobId && meta?.jobId) jobId = meta.jobId;
5150
- if (!workflowId && meta?.workflowName) workflowId = meta.workflowName;
5151
- else if (!workflowId && meta?.workflowStep) workflowId = meta.workflowStep;
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
- const lastMs = latestActivityMs(sessionRuns);
5154
- const lastActivity = lastMs > 0 ? new Date(lastMs).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString();
5155
- const retryCount2 = summary.retries.filter(
5156
- (retry) => retry.retryOf !== void 0 || (retry.attempt ?? 0) > 1
5157
- ).length;
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
- ...summary,
5160
- status: deriveSessionStatus(sessionRuns, options),
5161
- startedAt,
5162
- endedAt,
5163
- durationMs: durationMs2,
5164
- correlationId,
5165
- jobId,
5166
- workflowId,
5167
- lastError: deriveLastError(sessionRuns),
5168
- lastActivity,
5169
- retryCount: retryCount2,
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/sessions/activity.ts
5176
- function statusLine(session) {
5177
- const name = session.workflowId ?? session.correlationId ?? session.sessionId;
5178
- const status = session.status;
5179
- if (session.lastError) {
5180
- return `${name} session ${session.sessionId} failed at ${session.lastError.message}`;
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
- if (session.observationSummary) {
5183
- return `${name} session ${session.sessionId} ${status} with observation warning`;
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 isFailed(status) {
5198
- return status === "error";
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 isStale(status) {
5201
- return status === "stale";
5152
+ function normalizeStepTypeFilter(kind, type) {
5153
+ const v = (kind ?? type)?.trim().toLowerCase();
5154
+ return v && v !== "" ? v : void 0;
5202
5155
  }
5203
- function guardrailWarnings(session) {
5204
- const summary = session.checkSummary;
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 buildActivitySummary(index, options = {}) {
5209
- const nowMs = options.nowMs ?? Date.now();
5210
- const sinceMs = parseSinceMs(options.since, nowMs);
5211
- const sinceIso = new Date(sinceMs).toISOString();
5212
- const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 20;
5213
- const inWindow = index.sessions.filter((session) => {
5214
- const activityMs2 = Date.parse(session.lastActivity);
5215
- return Number.isFinite(activityMs2) && activityMs2 >= sinceMs;
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
- return {
5233
- since: sinceIso,
5234
- sessions: inWindow.length,
5235
- failed,
5236
- stale,
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
- if (today.length > 0) {
5251
- lines.push("Today");
5252
- for (const entry of today) {
5253
- lines.push(` ${entry.summary}`);
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
- lines.push("");
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
- lines.push(`Since ${summary.since}`);
5258
- lines.push(` ${summary.sessions} sessions`);
5259
- lines.push(` ${summary.failed} failed`);
5260
- lines.push(` ${summary.stale} stale`);
5261
- lines.push(` ${summary.guardrailWarnings} guardrail warnings`);
5262
- return lines.join("\n");
5263
- }
5264
-
5265
- // packages/core/src/sessions/types.ts
5266
- var SESSION_WORKFLOW_KEYS = [
5267
- "sessionId",
5268
- "conversationId",
5269
- "groupId",
5270
- "parentGroupId",
5271
- "attempt",
5272
- "retryOf",
5273
- "retryReason",
5274
- "handoffFrom",
5275
- "handoffTo",
5276
- "subAgentId",
5277
- "subAgentName",
5278
- "jobId",
5279
- "queueName",
5280
- "workflowName",
5281
- "workflowStep",
5282
- "toolCallId",
5283
- "mcpToolCallId",
5284
- "linkedStepId",
5285
- "operationId",
5286
- "attemptId",
5287
- "attemptNumber",
5288
- "fallbackOf",
5289
- "idempotencyKey",
5290
- "correlationId",
5291
- "requestId",
5292
- "decisionId"
5293
- ];
5294
-
5295
- // packages/core/src/sessions/load.ts
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
- return {
5310
- runId: meta.runId,
5311
- name: meta.name,
5312
- status: meta.status,
5313
- startedAt: meta.startedAt,
5314
- endedAt: meta.endedAt,
5315
- durationMs: meta.durationMs,
5316
- filePath: meta.filePath,
5317
- metadata
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
- async function loadSessionRunRecords(metas) {
5321
- const out = [];
5322
- for (const meta of metas) {
5323
- out.push(await enrichSessionRunRecord(meta));
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
- return out;
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
- if (groupId) {
5373
- const runIds = records.filter((run) => extractSessionWorkflowMetadata(run.metadata)?.groupId === groupId).map((run) => run.runId).sort();
5374
- if (runIds.length === 0) {
5375
- return {
5376
- metas: [],
5377
- scopeLabel: groupId,
5378
- scopeKind: "group",
5379
- runIds: [],
5380
- warnings,
5381
- notFound: true
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
- const runIdSet = new Set(runIds);
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
- // packages/core/src/sessions/cohort.ts
5408
- function groupSessionCohorts(runs, options = {}) {
5409
- const groupBy = options.groupBy ?? "session";
5410
- const cohorts = [];
5411
- if (groupBy === "session") {
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 byGroup = /* @__PURE__ */ new Map();
5430
- const unscoped = [];
5431
- for (const run of runs) {
5432
- const groupId = extractSessionWorkflowMetadata(run.metadata)?.groupId;
5433
- if (!groupId) {
5434
- unscoped.push(run.runId);
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 bucket = byGroup.get(groupId) ?? [];
5438
- bucket.push(run.runId);
5439
- byGroup.set(groupId, bucket);
5440
- }
5441
- for (const [key, runIds] of [...byGroup.entries()].sort(
5442
- ([a], [b]) => a.localeCompare(b)
5443
- )) {
5444
- cohorts.push({
5445
- key,
5446
- kind: "group",
5447
- runIds: runIds.sort()
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
- if (unscoped.length > 0) {
5451
- cohorts.push({
5452
- key: "__unscoped__",
5453
- kind: "group",
5454
- runIds: unscoped.sort()
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 cohorts;
5347
+ return metas;
5458
5348
  }
5459
5349
 
5460
- // packages/core/src/sessions/checks.ts
5461
- function emptySummary() {
5462
- return { passed: 0, failed: 0, warnings: 0, errors: 0, rulesEvaluated: 0 };
5350
+ // packages/core/src/sessions/metadata.ts
5351
+ function isNonEmptyString3(value) {
5352
+ return typeof value === "string" && value.trim() !== "";
5463
5353
  }
5464
- function mergeSummary(target, source) {
5465
- return {
5466
- passed: target.passed + source.passed,
5467
- failed: target.failed + source.failed,
5468
- warnings: target.warnings + source.warnings,
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 sessionDiagnostic(code, message) {
5474
- return { code, message, severity: "error" };
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 aggregateSessionCheckResults(perRun, scope) {
5477
- if (scope.notFound) {
5478
- return {
5479
- ok: false,
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
- if (scope.empty || perRun.length === 0) {
5499
- return {
5500
- ok: false,
5501
- status: "error",
5502
- format: perRun[0]?.format ?? "agent-inspect-jsonl",
5503
- scopeKind: scope.scopeKind,
5504
- scopeLabel: scope.scopeLabel,
5505
- runIds: scope.runIds,
5506
- runResults: [],
5507
- summary: { ...emptySummary(), errors: 1 },
5508
- findings: [],
5509
- diagnostics: [
5510
- sessionDiagnostic(
5511
- "AI_CHECK_TRACE_UNREADABLE",
5512
- `No readable traces in ${scope.scopeKind}: ${scope.scopeLabel}`
5513
- )
5514
- ],
5515
- ruleExecutions: [],
5516
- ...scope.sessionWarnings?.length ? { sessionWarnings: [...scope.sessionWarnings] } : {}
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
- let summary = emptySummary();
5520
- const findings = [];
5521
- const diagnostics = [];
5522
- const ruleExecutions = [];
5523
- const runResults = [];
5524
- for (const result of perRun) {
5525
- summary = mergeSummary(summary, result.summary);
5526
- findings.push(...result.findings);
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
- runResults.sort((a, b) => a.runId.localeCompare(b.runId));
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
- // packages/core/src/sessions/index.ts
5565
- function compareRuns(a, b) {
5566
- const aStart = a.startedAt ?? 0;
5567
- const bStart = b.startedAt ?? 0;
5568
- if (aStart !== bStart) return aStart - bStart;
5569
- return a.runId.localeCompare(b.runId);
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 buildGroups(runIds, metaByRunId) {
5572
- const byGroup = /* @__PURE__ */ new Map();
5573
- for (const runId of runIds) {
5574
- const groupId = metaByRunId.get(runId)?.groupId;
5575
- if (!groupId) continue;
5576
- const existing = byGroup.get(groupId);
5577
- if (existing) {
5578
- existing.runIds.push(runId);
5579
- } else {
5580
- byGroup.set(groupId, {
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 [...byGroup.values()].map((group) => ({
5588
- ...group,
5589
- runIds: [...group.runIds].sort((a, b) => a.localeCompare(b))
5590
- }));
5477
+ return best;
5591
5478
  }
5592
- function buildHandoffs(runIds, metaByRunId, warnings, sessionId) {
5593
- const edges = [];
5594
- const seen = /* @__PURE__ */ new Set();
5595
- const pushEdge = (edge) => {
5596
- const key = `${edge.from}->${edge.to}:${edge.confidence}`;
5597
- if (seen.has(key)) return;
5598
- seen.add(key);
5599
- edges.push(edge);
5600
- };
5601
- for (const runId of runIds) {
5602
- const meta = metaByRunId.get(runId);
5603
- if (!meta) continue;
5604
- if (meta.handoffFrom && meta.handoffTo) {
5605
- pushEdge({
5606
- from: meta.handoffFrom,
5607
- to: meta.handoffTo,
5608
- source: "manual",
5609
- confidence: "explicit"
5610
- });
5611
- continue;
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 (meta.handoffFrom) {
5614
- pushEdge({
5615
- from: meta.handoffFrom,
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 (meta.handoffTo) {
5622
- pushEdge({
5623
- from: runId,
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
- if (meta.subAgentId && meta.parentGroupId && !meta.handoffFrom && !meta.handoffTo) {
5630
- pushEdge({
5631
- from: meta.parentGroupId,
5632
- to: meta.subAgentId,
5633
- source: "inferred",
5634
- confidence: "correlated"
5635
- });
5636
- warnings.push({
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 edges.sort((a, b) => {
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 buildRetries(runIds, metaByRunId, warnings, sessionId) {
5651
- const retries = [];
5652
- for (const runId of runIds) {
5653
- const meta = metaByRunId.get(runId);
5654
- if (!meta) continue;
5655
- if (meta.retryOf) {
5656
- retries.push({
5657
- runId,
5658
- retryOf: meta.retryOf,
5659
- attempt: meta.attempt,
5660
- source: "manual",
5661
- confidence: "explicit"
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 buildCriticalPath(runs, handoffs) {
5683
- const runById = new Map(runs.map((run) => [run.runId, run]));
5684
- const explicitTargets = new Set(
5685
- handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.to)
5686
- );
5687
- const explicitSources = new Set(
5688
- handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
5689
- );
5690
- const ordered = [...runs].sort(compareRuns);
5691
- const path14 = [];
5692
- const visited = /* @__PURE__ */ new Set();
5693
- const pushRun = (run, confidence, source) => {
5694
- if (visited.has(run.runId)) return;
5695
- visited.add(run.runId);
5696
- path14.push({
5697
- runId: run.runId,
5698
- name: run.name,
5699
- startedAt: run.startedAt,
5700
- durationMs: run.durationMs,
5701
- confidence,
5702
- source
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
- for (const edge of handoffs) {
5706
- if (edge.confidence !== "explicit") continue;
5707
- const fromRun = [...runById.values()].find(
5708
- (run) => run.runId === edge.from || metaRunIdMatches(run, edge.from, runById)
5709
- );
5710
- const toRun = [...runById.values()].find(
5711
- (run) => run.runId === edge.to || metaRunIdMatches(run, edge.to, runById)
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
- for (const run of ordered) {
5717
- if (visited.has(run.runId)) continue;
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 path14;
5581
+ return `${name} session ${session.sessionId} ${status}`;
5722
5582
  }
5723
- function metaRunIdMatches(run, token, runById) {
5724
- const meta = extractSessionWorkflowMetadata(run.metadata);
5725
- return meta?.subAgentId === token || meta?.groupId === token || runById.has(token);
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 buildSessionIndex(inputRuns, options = {}) {
5728
- const warnings = [];
5729
- const runs = [...inputRuns].sort(compareRuns);
5730
- const metaByRunId = /* @__PURE__ */ new Map();
5731
- for (const run of runs) {
5732
- metaByRunId.set(run.runId, extractSessionWorkflowMetadata(run.metadata));
5733
- }
5734
- const sessionsByKey = /* @__PURE__ */ new Map();
5735
- const unscopedRunIds = [];
5736
- for (const run of runs) {
5737
- const meta = metaByRunId.get(run.runId);
5738
- const key = sessionKeyForRun(meta, {
5739
- correlateByGroupId: options.correlateByGroupId === true
5740
- });
5741
- if (!key) {
5742
- unscopedRunIds.push(run.runId);
5743
- continue;
5744
- }
5745
- const bucket = sessionsByKey.get(key) ?? [];
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
- if (sessions.length === 0 && runs.length > 0) {
5780
- warnings.push({
5781
- code: "missing-session-id",
5782
- message: "No sessionId (or correlated groupId) found on input runs."
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
- runs,
5792
- sessions,
5793
- unscopedRunIds: unscopedRunIds.sort(),
5794
- warnings
5629
+ since: sinceIso,
5630
+ sessions: inWindow.length,
5631
+ failed,
5632
+ stale,
5633
+ guardrailWarnings: guardrailWarningTotal,
5634
+ entries
5795
5635
  };
5796
5636
  }
5797
- var KNOWN_EVENTS = /* @__PURE__ */ new Set([
5798
- "run_started",
5799
- "run_completed",
5800
- "step_started",
5801
- "step_completed"
5802
- ]);
5803
- function isRecord7(value) {
5804
- return typeof value === "object" && value !== null && !Array.isArray(value);
5805
- }
5806
- function safeParse(line) {
5807
- try {
5808
- return JSON.parse(line);
5809
- } catch {
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
- return false;
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/bundle/resolve.ts
5842
- function parseSinceCutoff(since) {
5843
- const trimmed = since.trim();
5844
- if (trimmed === "") {
5845
- throw new Error("--since requires a non-empty duration (e.g. 24h, 7d).");
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 Date.now() - parseDuration(trimmed);
5848
- }
5849
- function runActivityMs(run) {
5850
- if (run.startedAt !== void 0 && Number.isFinite(run.startedAt)) return run.startedAt;
5851
- if (run.endedAt !== void 0 && Number.isFinite(run.endedAt)) return run.endedAt;
5852
- return void 0;
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 runsInSinceWindow(runs, since) {
5855
- const cutoff = parseSinceCutoff(since);
5856
- const ids = [];
5857
- for (const run of runs) {
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 ids.sort((a, b) => a.localeCompare(b));
5721
+ return out;
5864
5722
  }
5865
- function findSession(index, sessionId) {
5866
- return index.sessions.find((session) => session.sessionId === sessionId);
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 resolveBundleRunIds(index, runs, options) {
5869
- const runId = options.runId?.trim();
5737
+ function filterMetasBySessionScope(metas, records, options) {
5870
5738
  const sessionId = options.sessionId?.trim();
5871
- const since = options.since?.trim();
5872
- const modes = [runId ? 1 : 0, sessionId ? 1 : 0, since ? 1 : 0].reduce((a, b) => a + b, 0);
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 session = findSession(index, sessionId);
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
- throw new Error(`Session "${sessionId}" was not found.`);
5894
- }
5895
- if (session.runIds.length === 0) {
5896
- throw new Error(`Session "${sessionId}" has no runs to bundle.`);
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
- runIds: [...session.runIds].sort((a, b) => a.localeCompare(b)),
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
- const runIds = runsInSinceWindow(runs, since);
5904
- if (runIds.length === 0) {
5905
- throw new Error(`No runs matched --since ${since}.`);
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
- createdAt: parts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
5936
- agentInspectVersion: parts.agentInspectVersion,
5937
- redactionProfile: parts.profile,
5938
- sourceTraceCount: parts.resolve.runIds.length,
5939
- runIds: [...parts.resolve.runIds],
5940
- safeStatus: toMetadataSafeStatus(aggregate),
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 buildPlaceholderArtifact() {
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/bundle/summary.ts
5955
- function markdownTable(rows) {
5956
- const lines = ["| Field | Value |", "| --- | --- |"];
5957
- for (const [key, value] of rows) {
5958
- lines.push(`| ${key} | ${value ?? "unknown"} |`);
5959
- }
5960
- return lines.join("\n");
5961
- }
5962
- function buildBundleSummaryMarkdown(parts) {
5963
- const { metadata, checks, redaction } = parts;
5964
- const lines = [
5965
- "# AgentInspect trace bundle",
5966
- "",
5967
- metadata.note,
5968
- "",
5969
- "## Overview",
5970
- "",
5971
- markdownTable([
5972
- ["Created", metadata.createdAt],
5973
- ["AgentInspect", metadata.agentInspectVersion],
5974
- ["Redaction profile", metadata.redactionProfile],
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
- return safe;
6017
- }
6018
- function bundleRunAssetRelativePath(runId, extension) {
6019
- const safe = sanitizeBundleRunId(runId);
6020
- const ext = extension.startsWith(".") ? extension : `.${extension}`;
6021
- return path7__default.default.posix.join("assets", "runs", `${safe}${ext}`);
6022
- }
6023
- function assertBundlePathContained(outputDir, relativePath) {
6024
- const base = path7__default.default.resolve(outputDir);
6025
- const resolved = path7__default.default.resolve(base, relativePath);
6026
- if (resolved !== base && !resolved.startsWith(base + path7__default.default.sep)) {
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
- return resolved;
6030
- }
6031
- function normalizeBundleOutputPath(out, options) {
6032
- const trimmed = out.trim();
6033
- if (trimmed === "") {
6034
- throw new Error("--out requires a non-empty path.");
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
- const resolved = path7__default.default.resolve(trimmed);
6037
- if (options?.preserveZipExtension !== true && resolved.toLowerCase().endsWith(".zip")) {
6038
- return resolved.slice(0, -4);
5846
+ if (unscoped.length > 0) {
5847
+ cohorts.push({
5848
+ key: "__unscoped__",
5849
+ kind: "group",
5850
+ runIds: unscoped.sort()
5851
+ });
6039
5852
  }
6040
- return resolved;
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/evidence/types.ts
6049
- var EVIDENCE_FORMAT_VERSION = "1.0";
6050
- var EVIDENCE_ASSESSMENT_NOTE = "Best-effort local safety verification only; not a compliance certification.";
6051
- var EVIDENCE_MANIFEST_FILENAME = "evidence.json";
6052
- var EVIDENCE_RESOLVED_CONTRACT_FILENAME = "contract.resolved.json";
6053
- var SHA256_RE = /^[a-f0-9]{64}$/i;
6054
- function sha256Hex(data) {
6055
- const hash = crypto.createHash("sha256");
6056
- if (typeof data === "string") {
6057
- hash.update(data, "utf8");
6058
- } else {
6059
- hash.update(data);
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 isSha256Hex(value) {
6064
- return SHA256_RE.test(value);
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 sha256Equals(expected, actual) {
6067
- if (!isSha256Hex(expected) || !isSha256Hex(actual)) {
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 assertEvidenceRelativePath(relativePath) {
6080
- if (typeof relativePath !== "string" || relativePath.trim() === "") {
6081
- throw new Error("Evidence file path must be a non-empty relative path.");
6082
- }
6083
- const trimmed = relativePath.trim().replaceAll("\\", "/");
6084
- if (path7__default.default.isAbsolute(trimmed) || trimmed.startsWith("/")) {
6085
- throw new Error(`Evidence file path must be relative: ${relativePath}`);
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
- const parts = trimmed.split("/").filter((part) => part !== "");
6088
- if (parts.length === 0) {
6089
- throw new Error(`Evidence file path must be relative: ${relativePath}`);
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
- for (const part of parts) {
6092
- if (part === "." || part === "..") {
6093
- throw new Error(`Evidence file path must not contain "." or "..": ${relativePath}`);
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
- return parts.join("/");
6097
- }
6098
-
6099
- // packages/core/src/evidence/manifest.ts
6100
- function stable(value) {
6101
- if (Array.isArray(value)) return value.map(stable);
6102
- if (value === null || typeof value !== "object") return value;
6103
- const record = value;
6104
- return Object.fromEntries(
6105
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
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
- function serializeEvidenceManifest(manifest) {
6109
- return `${JSON.stringify(stable(manifest), null, 2)}
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 inferEvidenceFileRole(relativePath) {
6113
- const normalized = assertEvidenceRelativePath(relativePath);
6114
- const base = normalized.includes("/") ? normalized.slice(normalized.lastIndexOf("/") + 1) : normalized;
6115
- if (base === "evidence.html" || base === "trace.html" || base.endsWith(".html")) {
6116
- return "report";
6117
- }
6118
- if (base === "trace.jsonl" || base.endsWith(".jsonl")) {
6119
- return "redacted-trace";
6120
- }
6121
- if (base === "check-results.json") {
6122
- return "checks";
6123
- }
6124
- if (base === "contract.resolved.json") {
6125
- return "contract";
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 "other";
5997
+ return [...byGroup.values()].map((group) => ({
5998
+ ...group,
5999
+ runIds: [...group.runIds].sort((a, b) => a.localeCompare(b))
6000
+ }));
6134
6001
  }
6135
- function buildEvidenceFileEntries(files) {
6136
- const byPath = /* @__PURE__ */ new Map();
6137
- for (const file of files) {
6138
- const relativePath = assertEvidenceRelativePath(file.path);
6139
- if (relativePath === EVIDENCE_MANIFEST_FILENAME) {
6140
- throw new Error(
6141
- `Do not include ${EVIDENCE_MANIFEST_FILENAME} in packaged file hashes (self-hash is undefined).`
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 (byPath.has(relativePath)) {
6145
- throw new Error(`Duplicate evidence file path: ${relativePath}`);
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 [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
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 buildEvidenceManifest(parts) {
6156
- const runIds = [...parts.runIds];
6157
- if (runIds.length === 0) {
6158
- throw new Error("Evidence manifest requires at least one run id.");
6159
- }
6160
- for (const item of parts.sourceHashes) {
6161
- if (!runIds.includes(item.runId)) {
6162
- throw new Error(`sourceHashes runId "${item.runId}" is not listed in source.runIds.`);
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 (item.algorithm !== "sha256") {
6165
- throw new Error(`Unsupported source hash algorithm: ${item.algorithm}`);
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
- const assessment = {
6169
- status: parts.assessmentStatus,
6170
- note: parts.note ?? EVIDENCE_ASSESSMENT_NOTE
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
- if (parts.sourceStatus !== void 0) {
6173
- assessment.sourceStatus = parts.sourceStatus;
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
- return {
6176
- evidenceFormatVersion: EVIDENCE_FORMAT_VERSION,
6177
- generator: {
6178
- name: parts.generatorName ?? "agent-inspect",
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
- if (!Array.isArray(record.files) || record.files.length === 0) {
6234
- throw new Error("Evidence manifest files must be a non-empty array.");
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
- for (const file of record.files) {
6237
- if (file === null || typeof file !== "object" || Array.isArray(file)) {
6238
- throw new Error("Evidence manifest file entries must be objects.");
6239
- }
6240
- const entry = file;
6241
- if (typeof entry.path !== "string") {
6242
- throw new Error("Evidence file entry requires path.");
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
- assertEvidenceRelativePath(entry.path);
6245
- if (typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/i.test(entry.sha256)) {
6246
- throw new Error(`Evidence file entry requires sha256 hex for ${entry.path}.`);
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
- return value;
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
- function parseEvidenceManifestJson(text) {
6252
- let parsed;
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
- parsed = JSON.parse(text);
6255
- } catch (error) {
6256
- const message = error instanceof Error ? error.message : String(error);
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 collectTraceSchemaVersions(jsonl) {
6262
- const versions = /* @__PURE__ */ new Set();
6263
- for (const line of jsonl.split(/\r?\n/)) {
6264
- const trimmed = line.trim();
6265
- if (trimmed === "") continue;
6266
- try {
6267
- const row = JSON.parse(trimmed);
6268
- if (typeof row.schemaVersion === "string" && row.schemaVersion.trim() !== "") {
6269
- versions.add(row.schemaVersion.trim());
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
- } catch {
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/diagnostics/programmatic.ts
6278
- var PROGRAMMATIC_DIAGNOSTIC_SPECS = Object.freeze({
6279
- AI_TRACE_INPUT_INVALID: {
6280
- code: "AI_TRACE_INPUT_INVALID",
6281
- summary: 'Expected { type: "file", path }, { type: "directory", path }, { type: "string", content }, { type: "buffer", content }, or { type: "stdin" }.',
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 stepIdOf(event) {
6339
- const value = event.attributes?.stepId;
6340
- if (typeof value === "string" && value.trim() !== "") return value;
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 cloneEvent(event) {
6344
- return {
6345
- ...event,
6346
- ...event.attributes !== void 0 ? { attributes: { ...event.attributes } } : {},
6347
- ...event.error !== void 0 ? { error: { ...event.error } } : {},
6348
- ...event.tokenUsage !== void 0 ? { tokenUsage: { ...event.tokenUsage } } : {},
6349
- ...event.source !== void 0 ? { source: { ...event.source } } : {}
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 Object.keys(merged).length > 0 ? merged : void 0;
6273
+ return ids.sort((a, b) => a.localeCompare(b));
6363
6274
  }
6364
- function pairStartComplete(start, complete) {
6365
- const attributes = mergeAttributes(start, complete);
6366
- const paired = {
6367
- ...cloneEvent(start),
6368
- status: complete.status,
6369
- timestamp: complete.timestamp ?? start.timestamp,
6370
- ...complete.endedAt !== void 0 ? { endedAt: complete.endedAt } : {},
6371
- ...complete.durationMs !== void 0 ? { durationMs: complete.durationMs } : {},
6372
- ...complete.error !== void 0 ? { error: { ...complete.error } } : {},
6373
- ...complete.tokenUsage !== void 0 && start.tokenUsage === void 0 ? { tokenUsage: { ...complete.tokenUsage } } : {},
6374
- ...attributes !== void 0 ? { attributes } : {}
6375
- };
6376
- return {
6377
- ...paired,
6378
- sourceEventIds: Object.freeze([start.eventId, complete.eventId]),
6379
- projection: {
6380
- paired: true,
6381
- absorbedEventIds: Object.freeze([complete.eventId]),
6382
- parentNormalized: false,
6383
- ...start.parentId !== void 0 ? { originalParentId: start.parentId } : {}
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 asLogical(event, extras) {
6357
+ function buildPlaceholderArtifact() {
6388
6358
  return {
6389
- ...cloneEvent(event),
6390
- sourceEventIds: Object.freeze([event.eventId]),
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
- function projectLogicalEvents(events) {
6400
- const diagnostics = [];
6401
- const byRun = /* @__PURE__ */ new Map();
6402
- for (const event of events) {
6403
- const list = byRun.get(event.runId) ?? [];
6404
- list.push(event);
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
- const absorbedIds = /* @__PURE__ */ new Set();
6408
- const logicalByRawId = /* @__PURE__ */ new Map();
6409
- const stepIdToLogicalId = /* @__PURE__ */ new Map();
6410
- const logical = [];
6411
- const orderedRuns = [...byRun.keys()].sort((a, b) => a.localeCompare(b));
6412
- for (const runId of orderedRuns) {
6413
- const runEvents = byRun.get(runId) ?? [];
6414
- const starts = [];
6415
- const completes = [];
6416
- const others = [];
6417
- for (const event of runEvents) {
6418
- const legacy = legacyEvent(event);
6419
- if (legacy === "step_started" || legacy === "run_started" && event.status === "running") {
6420
- starts.push(event);
6421
- } else if (legacy === "step_completed" || legacy === "run_completed") {
6422
- completes.push(event);
6423
- } else {
6424
- others.push(event);
6425
- }
6426
- }
6427
- const usedCompletes = /* @__PURE__ */ new Set();
6428
- for (const start of starts) {
6429
- const stepId = stepIdOf(start);
6430
- let match;
6431
- if (legacyEvent(start) === "run_started") {
6432
- const candidates = completes.filter(
6433
- (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "run_completed"
6434
- );
6435
- if (candidates.length > 1) {
6436
- diagnostics.push({
6437
- code: "AI_LOGICAL_PAIR_AMBIGUOUS",
6438
- message: `Multiple run_completed rows for run ${runId}; using first by eventId.`,
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
- const logicalById = new Map(logical.map((e) => [e.eventId, e]));
6504
- const normalized = [];
6505
- for (const event of logical) {
6506
- const originalParentId = event.parentId;
6507
- if (!originalParentId) {
6508
- normalized.push(event);
6509
- continue;
6510
- }
6511
- let nextParent = originalParentId;
6512
- let remapped = false;
6513
- const viaAbsorbed = logicalByRawId.get(originalParentId);
6514
- if (viaAbsorbed && viaAbsorbed.eventId !== originalParentId) {
6515
- nextParent = viaAbsorbed.eventId;
6516
- remapped = true;
6517
- } else if (!logicalById.has(originalParentId)) {
6518
- const viaStep = stepIdToLogicalId.get(`${event.runId}:${originalParentId}`);
6519
- if (viaStep) {
6520
- nextParent = viaStep;
6521
- remapped = true;
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
- if (!remapped) {
6525
- if (originalParentId === event.eventId) {
6526
- diagnostics.push({
6527
- code: "AI_LOGICAL_SELF_PARENT_REMOVED",
6528
- message: formatProgrammaticDiagnostic(
6529
- "AI_TRACE_RELATIONSHIP_SELF_PARENT",
6530
- `Removed self-parent edge on ${event.eventId}.`
6531
- ),
6532
- eventIds: [event.eventId]
6533
- });
6534
- const { parentId: _drop, ...rest } = event;
6535
- normalized.push({
6536
- ...rest,
6537
- projection: {
6538
- ...event.projection,
6539
- parentNormalized: true,
6540
- originalParentId
6541
- }
6542
- });
6543
- continue;
6544
- }
6545
- if (!logicalById.has(originalParentId) && !logicalByRawId.has(originalParentId)) {
6546
- const mapping = event.attributes?.parentMapping;
6547
- const unresolved = mapping === "unresolved" || event.attributes?.parentUnresolved === true || event.attributes?.unresolvedParent === true || // LangGraph/framework scaffolding sentinel labels are not event ids.
6548
- /^LangGraph$/i.test(originalParentId) || originalParentId.startsWith("unresolved:");
6549
- if (!unresolved) {
6550
- diagnostics.push({
6551
- code: "AI_LOGICAL_PARENT_UNRESOLVED",
6552
- message: `Parent ${originalParentId} unresolved for ${event.eventId}.`,
6553
- eventIds: [event.eventId]
6554
- });
6555
- }
6556
- }
6557
- normalized.push(event);
6558
- continue;
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 (nextParent === event.eventId) {
6561
- diagnostics.push({
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
- diagnostics.push({
6581
- code: "AI_LOGICAL_PARENT_REMAPPED",
6582
- message: `Remapped parent ${originalParentId} \u2192 ${nextParent} for ${event.eventId}.`,
6583
- eventIds: [event.eventId]
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
- const rawIndex = new Map(events.map((e, i) => [e.eventId, i]));
6596
- normalized.sort((a, b) => {
6597
- const ai = rawIndex.get(a.sourceEventIds[0]) ?? 0;
6598
- const bi = rawIndex.get(b.sourceEventIds[0]) ?? 0;
6599
- return ai - bi || a.eventId.localeCompare(b.eventId);
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
- logicalEvents: Object.freeze(normalized),
6603
- diagnostics: Object.freeze(diagnostics)
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 resolveCanonicalToolName(event) {
6607
- const attrs = event.attributes;
6608
- const direct = pickString(attrs, ["toolName", "tool"]);
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
- for (const prefix of ["tool:", "function:", "mcp-tools:"]) {
6616
- if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
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
- return event.name;
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 pickString(record, keys) {
6621
- if (!record) return void 0;
6622
- for (const key of keys) {
6623
- const value = record[key];
6624
- if (typeof value === "string" && value.trim() !== "") return value.trim();
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 void 0;
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, rulesEvaluated) {
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.length);
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 diagnostics = [
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 checkOk = checkResult.ok;
12151
- const observationsOk = observationResult.ok;
12152
- const evalOk = config.eval?.requireSuccess === true ? checkResult.findings.every(
12153
- (finding) => finding.ruleId !== "run.status" || finding.status !== "fail"
12154
- ) && checkResult.diagnostics.every((item) => item.severity !== "error") : void 0;
12155
- const ok = checkOk && observationsOk;
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
  }