agent-workflow-inspector 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -269,8 +269,8 @@ function otel(value) {
269
269
  if (++total > LIMITS.spans)
270
270
  throw new AwiError("Maximum span count exceeded");
271
271
  const s = record(raw);
272
- const traceId = string(s.traceId), id = string(s.spanId);
273
- if (!traceId || !/^[a-fA-F0-9]{32}$/.test(traceId) || /^0+$/.test(traceId) || !id || !/^[a-fA-F0-9]{16}$/.test(id) || /^0+$/.test(id))
272
+ const traceId2 = string(s.traceId), id = string(s.spanId);
273
+ if (!traceId2 || !/^[a-fA-F0-9]{32}$/.test(traceId2) || /^0+$/.test(traceId2) || !id || !/^[a-fA-F0-9]{16}$/.test(id) || /^0+$/.test(id))
274
274
  throw new AwiError("Invalid OTLP trace/span identity");
275
275
  const a = attributes(s.attributes);
276
276
  const kind = textAttribute(s.attributes, "gen_ai.operation.name");
@@ -345,7 +345,7 @@ function otel(value) {
345
345
  source: { format: "otel", index: total - 1 }
346
346
  };
347
347
  if (args !== void 0) validateValue(args);
348
- const key = traceId.toLowerCase();
348
+ const key = traceId2.toLowerCase();
349
349
  if (!grouped.has(key))
350
350
  grouped.set(key, { version: 1, traceId: key, operations: [] });
351
351
  grouped.get(key).operations.push(op);
@@ -426,14 +426,20 @@ var configSchema = z2.strictObject({
426
426
  maxOperations: positive.max(5e4).default(100),
427
427
  maxDepth: positive.max(1e3).default(20)
428
428
  }).default({ enabled: true, maxOperations: 100, maxDepth: 20 }),
429
- AWI006: z2.strictObject({ enabled: z2.boolean().default(true) }).default({ enabled: true })
429
+ AWI006: z2.strictObject({ enabled: z2.boolean().default(true) }).default({ enabled: true }),
430
+ AWI007: z2.strictObject({
431
+ enabled: z2.boolean().default(false),
432
+ threshold: positive.min(4).max(LIMITS.repetitionWindow).default(4),
433
+ minDistinctArguments: positive.min(3).max(LIMITS.repetitionWindow).default(3)
434
+ }).default({ enabled: false, threshold: 4, minDistinctArguments: 3 })
430
435
  }).default({
431
436
  AWI001: { enabled: true, threshold: 4 },
432
437
  AWI002: { enabled: true, threshold: 3 },
433
438
  AWI003: { enabled: true, minRepetitions: 3, maxCycleLength: 6 },
434
439
  AWI004: { enabled: true, threshold: 5, windowSeconds: 30 },
435
440
  AWI005: { enabled: true, maxOperations: 100, maxDepth: 20 },
436
- AWI006: { enabled: true }
441
+ AWI006: { enabled: true },
442
+ AWI007: { enabled: false, threshold: 4, minDistinctArguments: 3 }
437
443
  }),
438
444
  fingerprint: z2.strictObject({
439
445
  ignorePaths: z2.array(
@@ -616,7 +622,8 @@ var titles = {
616
622
  AWI003: "Repeating execution cycle",
617
623
  AWI004: "Retry storm",
618
624
  AWI005: "Excessive execution steps or depth",
619
- AWI006: "Recorded resource usage associated with suspicious execution"
625
+ AWI006: "Recorded resource usage associated with suspicious execution",
626
+ AWI007: "Repeated failure with argument drift"
620
627
  };
621
628
  function analyze(traces, configInput = {}, showContent = false) {
622
629
  const config = resolveConfig(configInput);
@@ -666,7 +673,7 @@ function analyze(traces, configInput = {}, showContent = false) {
666
673
  validated.push(result.data);
667
674
  }
668
675
  const report = {
669
- version: 1,
676
+ version: 2,
670
677
  fingerprintVersion: "awi-sha256-v1",
671
678
  complete: true,
672
679
  summaries: [],
@@ -680,20 +687,20 @@ function analyze(traces, configInput = {}, showContent = false) {
680
687
  else grouped.set(t.traceId, { ...t, operations: [...t.operations] });
681
688
  }
682
689
  let capped = false;
683
- function add(ruleId, traceId, ops, evidence, explanation, confidence = "moderate") {
690
+ function add(ruleId, traceId2, ops, evidence, explanation, confidence = "moderate") {
684
691
  if (report.findings.length >= LIMITS.findings) {
685
692
  capped = true;
686
693
  return;
687
694
  }
688
695
  report.findings.push({
689
696
  ruleId,
690
- traceId,
697
+ traceId: traceId2,
691
698
  severity: ruleId === "AWI002" || ruleId === "AWI004" ? "high" : "medium",
692
699
  confidence,
693
700
  title: titles[ruleId],
694
701
  description: "Heuristic finding based on recorded execution evidence; intentional behavior may match.",
695
702
  explanation,
696
- remediation: "Review the referenced operations and configured policy before changing the workflow.",
703
+ remediation: ruleId === "AWI007" ? "Review the shared error code and retry policy; consider bounded retries or escalation before varying arguments further." : "Review the referenced operations and configured policy before changing the workflow.",
697
704
  operationIds: ops.map((o) => o.id),
698
705
  evidence: {
699
706
  ...evidence,
@@ -704,9 +711,10 @@ function analyze(traces, configInput = {}, showContent = false) {
704
711
  }
705
712
  });
706
713
  }
707
- for (const trace of [...grouped.values()].sort(
708
- (a, b) => a.traceId < b.traceId ? -1 : a.traceId > b.traceId ? 1 : 0
709
- )) {
714
+ const orderedTraces = [...grouped.values()].sort(
715
+ (a, b) => compare(a.traceId, b.traceId)
716
+ );
717
+ for (const trace of orderedTraces) {
710
718
  trace.operations.sort((a, b) => compare(a.id, b.id));
711
719
  const normalized = normalize(trace);
712
720
  report.warnings.push(...normalized.warnings);
@@ -773,6 +781,43 @@ function analyze(traces, configInput = {}, showContent = false) {
773
781
  "Overlapping sibling operations excluded from sequence rules."
774
782
  );
775
783
  for (const run of runs) {
784
+ if (config.rules.AWI007.enabled) {
785
+ const policy2 = config.rules.AWI007;
786
+ let failures = [];
787
+ const distinct = /* @__PURE__ */ new Set();
788
+ let tool;
789
+ let code;
790
+ const flush = () => {
791
+ if (failures.length >= policy2.threshold && distinct.size >= policy2.minDistinctArguments)
792
+ add(
793
+ "AWI007",
794
+ trace.traceId,
795
+ failures,
796
+ {
797
+ failedCalls: failures.length,
798
+ distinctArgumentFingerprints: distinct.size
799
+ },
800
+ "Consecutive recorded failures share a tool and explicit error code while argument fingerprints vary. This does not establish intent or lack of progress."
801
+ );
802
+ failures = [];
803
+ distinct.clear();
804
+ };
805
+ for (const op of run) {
806
+ if (op.type !== "tool") continue;
807
+ const fp = fps.get(op.id);
808
+ if (op.status !== "error" || !op.error?.code?.trim() || op.arguments === void 0 || !fp) {
809
+ flush();
810
+ continue;
811
+ }
812
+ const name = op.toolName ?? op.name;
813
+ if (name !== tool || op.error.code !== code) flush();
814
+ tool = name;
815
+ code = op.error.code;
816
+ failures.push(op);
817
+ distinct.add(fp);
818
+ }
819
+ flush();
820
+ }
776
821
  let segment = [];
777
822
  const segments = [];
778
823
  for (const op of run) {
@@ -927,7 +972,12 @@ function analyze(traces, configInput = {}, showContent = false) {
927
972
  recorded,
928
973
  {
929
974
  recordedUsage: tokens,
930
- recordedCosts: costs,
975
+ recordedCosts: Object.fromEntries(
976
+ Object.keys(costs).sort(compare).map((currency, index) => [
977
+ `currency-${index + 1}`,
978
+ costs[currency]
979
+ ])
980
+ ),
931
981
  coverage: "Only implicated leaf operations with recorded usage or cost",
932
982
  relatedRules: [...new Set(related.map((f) => f.ruleId))]
933
983
  },
@@ -943,6 +993,28 @@ function analyze(traces, configInput = {}, showContent = false) {
943
993
  report.findings.sort(
944
994
  (a, b) => severityRank[b.severity] - severityRank[a.severity] || compare(a.traceId, b.traceId) || compare(a.ruleId, b.ruleId) || compare(a.operationIds.join("\0"), b.operationIds.join("\0"))
945
995
  );
996
+ const traceRefs = new Map(
997
+ orderedTraces.map((trace, index) => [
998
+ trace.traceId,
999
+ {
1000
+ id: `trace-${index + 1}`,
1001
+ operations: new Map(
1002
+ trace.operations.map((op, index2) => [op.id, `op-${index2 + 1}`])
1003
+ )
1004
+ }
1005
+ ])
1006
+ );
1007
+ for (const summary of report.summaries)
1008
+ summary.traceId = traceRefs.get(summary.traceId).id;
1009
+ for (const finding of report.findings) {
1010
+ const refs = traceRefs.get(finding.traceId);
1011
+ finding.traceId = refs.id;
1012
+ finding.operationIds = finding.operationIds.map(
1013
+ (id) => refs.operations.get(id)
1014
+ );
1015
+ for (const reference of finding.evidence.references)
1016
+ reference.operationId = refs.operations.get(reference.operationId);
1017
+ }
946
1018
  report.warnings = [...new Set(report.warnings)].sort();
947
1019
  try {
948
1020
  validateValue(report);
@@ -952,8 +1024,9 @@ function analyze(traces, configInput = {}, showContent = false) {
952
1024
  throw error;
953
1025
  }
954
1026
  if (showContent)
955
- report.content = [...grouped.values()].sort((a, b) => compare(a.traceId, b.traceId)).map((t) => ({
956
- traceId: t.traceId,
1027
+ report.content = orderedTraces.map((t) => ({
1028
+ traceId: traceRefs.get(t.traceId).id,
1029
+ recordedTraceId: t.traceId,
957
1030
  operations: redact(t.operations)
958
1031
  }));
959
1032
  return report;
@@ -981,7 +1054,8 @@ function humanReport(report, options = {}) {
981
1054
  ${paint("[" + f.severity.toUpperCase() + "]", f.severity)} ${safeText(f.ruleId)} ${safeText(f.title)} (${safeText(f.confidence)} evidence)`,
982
1055
  ` Trace: ${safeText(f.traceId)}; operations: ${f.operationIds.map(safeText).join(", ") || "trace-wide"}`,
983
1056
  ` ${safeText(f.explanation)}`,
984
- ` Evidence: ${safeText(JSON.stringify(f.evidence))}`
1057
+ ` Evidence: ${safeText(JSON.stringify(f.evidence))}`,
1058
+ ` Remediation: ${safeText(f.remediation)}`
985
1059
  );
986
1060
  for (const warning of report.warnings)
987
1061
  lines.push("Warning: " + safeText(warning));
@@ -1009,13 +1083,15 @@ var usage = z4.strictObject({
1009
1083
  outputTokens: z4.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
1010
1084
  totalTokens: z4.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional()
1011
1085
  });
1086
+ var traceId = z4.string().regex(/^trace-[1-9][0-9]*$/);
1087
+ var operationId = z4.string().regex(/^op-[1-9][0-9]*$/);
1012
1088
  var reportSchema = z4.strictObject({
1013
- version: z4.literal(1),
1089
+ version: z4.literal(2),
1014
1090
  fingerprintVersion: z4.literal("awi-sha256-v1"),
1015
1091
  complete: z4.boolean(),
1016
1092
  summaries: z4.array(
1017
1093
  z4.strictObject({
1018
- traceId: z4.string(),
1094
+ traceId,
1019
1095
  operations: z4.number().int().nonnegative(),
1020
1096
  toolCalls: z4.number().int().nonnegative(),
1021
1097
  maxDepth: z4.number().int().nonnegative(),
@@ -1031,7 +1107,8 @@ var reportSchema = z4.strictObject({
1031
1107
  "AWI003",
1032
1108
  "AWI004",
1033
1109
  "AWI005",
1034
- "AWI006"
1110
+ "AWI006",
1111
+ "AWI007"
1035
1112
  ]),
1036
1113
  severity: z4.enum(["low", "medium", "high"]),
1037
1114
  confidence: z4.enum(["high", "moderate"]),
@@ -1039,15 +1116,26 @@ var reportSchema = z4.strictObject({
1039
1116
  description: z4.string(),
1040
1117
  explanation: z4.string(),
1041
1118
  remediation: z4.string(),
1042
- traceId: z4.string(),
1043
- operationIds: z4.array(z4.string()),
1044
- evidence: z4.record(z4.string(), z4.unknown())
1119
+ traceId,
1120
+ operationIds: z4.array(operationId),
1121
+ evidence: z4.looseObject({
1122
+ references: z4.array(
1123
+ z4.strictObject({
1124
+ operationId,
1125
+ source: z4.strictObject({
1126
+ format: z4.enum(["awi", "otel"]),
1127
+ index: z4.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)
1128
+ }).optional()
1129
+ })
1130
+ )
1131
+ })
1045
1132
  })
1046
1133
  ),
1047
1134
  warnings: z4.array(z4.string()),
1048
1135
  content: z4.array(
1049
1136
  z4.strictObject({
1050
- traceId: z4.string(),
1137
+ traceId,
1138
+ recordedTraceId: z4.string(),
1051
1139
  operations: z4.array(z4.unknown())
1052
1140
  })
1053
1141
  ).optional()