agent-inspect 6.29.0 → 6.29.2

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 (54) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -1
  3. package/docs/API.md +4 -0
  4. package/docs/CLI.md +21 -1
  5. package/docs/LIMITATIONS.md +2 -1
  6. package/docs/NETWORK-BEHAVIOR.md +8 -0
  7. package/docs/SAFE-TRACE-SHARING.md +1 -1
  8. package/docs/STANDARDS.md +1 -1
  9. package/docs/TRACE-CONTRACTS.md +11 -0
  10. package/package.json +2 -2
  11. package/packages/cli/dist/{chunk-D7PT6TXM.mjs → chunk-UK4HD5M3.mjs} +126 -26
  12. package/packages/cli/dist/chunk-UK4HD5M3.mjs.map +1 -0
  13. package/packages/cli/dist/index.cjs +240 -106
  14. package/packages/cli/dist/index.cjs.map +1 -1
  15. package/packages/cli/dist/index.mjs +108 -82
  16. package/packages/cli/dist/index.mjs.map +1 -1
  17. package/packages/cli/dist/{src-QONEBW2F.mjs → src-CMPIJGUE.mjs} +3 -3
  18. package/packages/cli/dist/{src-QONEBW2F.mjs.map → src-CMPIJGUE.mjs.map} +1 -1
  19. package/packages/core/dist/advanced.cjs +138 -25
  20. package/packages/core/dist/advanced.cjs.map +1 -1
  21. package/packages/core/dist/advanced.d.cts +13 -2
  22. package/packages/core/dist/advanced.d.ts +13 -2
  23. package/packages/core/dist/advanced.mjs +109 -33
  24. package/packages/core/dist/advanced.mjs.map +1 -1
  25. package/packages/core/dist/checks.cjs +240 -35
  26. package/packages/core/dist/checks.cjs.map +1 -1
  27. package/packages/core/dist/checks.d.cts +2 -2
  28. package/packages/core/dist/checks.d.ts +2 -2
  29. package/packages/core/dist/checks.mjs +3 -3
  30. package/packages/core/dist/{chunk-TNOPL75V.mjs → chunk-5RF7FYGL.mjs} +3 -3
  31. package/packages/core/dist/{chunk-TNOPL75V.mjs.map → chunk-5RF7FYGL.mjs.map} +1 -1
  32. package/packages/core/dist/{chunk-3B24355S.mjs → chunk-65LFRW2T.mjs} +3 -3
  33. package/packages/core/dist/{chunk-3B24355S.mjs.map → chunk-65LFRW2T.mjs.map} +1 -1
  34. package/packages/core/dist/{chunk-MVRZ7JBH.mjs → chunk-COZPMOUF.mjs} +3 -3
  35. package/packages/core/dist/{chunk-MVRZ7JBH.mjs.map → chunk-COZPMOUF.mjs.map} +1 -1
  36. package/packages/core/dist/{chunk-LGTPSHH2.mjs → chunk-QZIXYP7U.mjs} +243 -38
  37. package/packages/core/dist/chunk-QZIXYP7U.mjs.map +1 -0
  38. package/packages/core/dist/{chunk-TIGBAVED.mjs → chunk-XRI3GB6I.mjs} +42 -5
  39. package/packages/core/dist/chunk-XRI3GB6I.mjs.map +1 -0
  40. package/packages/core/dist/exporters.cjs +10 -5
  41. package/packages/core/dist/exporters.cjs.map +1 -1
  42. package/packages/core/dist/exporters.d.cts +12 -2
  43. package/packages/core/dist/exporters.d.ts +12 -2
  44. package/packages/core/dist/exporters.mjs +13 -8
  45. package/packages/core/dist/exporters.mjs.map +1 -1
  46. package/packages/core/dist/{index-C61p7wBR.d.cts → index-BaQ48jrq.d.cts} +9 -1
  47. package/packages/core/dist/{index-DTO2iGeq.d.ts → index-CrnEOoRu.d.ts} +9 -1
  48. package/packages/core/dist/index.cjs +38 -1
  49. package/packages/core/dist/index.cjs.map +1 -1
  50. package/packages/core/dist/index.mjs +4 -4
  51. package/packages/core/dist/writers.mjs +1 -1
  52. package/packages/cli/dist/chunk-D7PT6TXM.mjs.map +0 -1
  53. package/packages/core/dist/chunk-LGTPSHH2.mjs.map +0 -1
  54. package/packages/core/dist/chunk-TIGBAVED.mjs.map +0 -1
@@ -1462,6 +1462,76 @@ function evaluateControlRules(events, rules, runEvidence2, observationNames) {
1462
1462
  return findings;
1463
1463
  }
1464
1464
 
1465
+ // packages/core/src/checks/canonical-equality.ts
1466
+ var DEFAULT_MAX_DEPTH = 8;
1467
+ var DEFAULT_MAX_BYTES = 16384;
1468
+ function sortKeysDeep(input, depth, maxDepth) {
1469
+ if (depth > maxDepth) {
1470
+ throw new Error("AI_CHECK_CANONICAL_DEPTH_EXCEEDED");
1471
+ }
1472
+ if (input === null || typeof input !== "object") return input;
1473
+ if (Array.isArray(input)) {
1474
+ return input.map((item) => sortKeysDeep(item, depth + 1, maxDepth));
1475
+ }
1476
+ const record = input;
1477
+ const out = {};
1478
+ for (const key of Object.keys(record).sort()) {
1479
+ out[key] = sortKeysDeep(record[key], depth + 1, maxDepth);
1480
+ }
1481
+ return out;
1482
+ }
1483
+ function encodeCanonicalStructured(value, limits) {
1484
+ const maxDepth = DEFAULT_MAX_DEPTH;
1485
+ const maxBytes = DEFAULT_MAX_BYTES;
1486
+ try {
1487
+ const sorted = sortKeysDeep(value, 0, maxDepth);
1488
+ const encoded = JSON.stringify(sorted);
1489
+ if (encoded === void 0) {
1490
+ return {
1491
+ ok: false,
1492
+ reason: "Value is not JSON-serializable for canonical comparison.",
1493
+ code: "AI_CHECK_CANONICAL_UNSERIALIZABLE"
1494
+ };
1495
+ }
1496
+ if (Buffer.byteLength(encoded, "utf8") > maxBytes) {
1497
+ return {
1498
+ ok: false,
1499
+ reason: "Canonical structured value exceeds bounded size limit.",
1500
+ code: "AI_CHECK_CANONICAL_SIZE_EXCEEDED"
1501
+ };
1502
+ }
1503
+ return { ok: true, value: encoded };
1504
+ } catch (error) {
1505
+ const message = error instanceof Error ? error.message : String(error);
1506
+ if (message === "AI_CHECK_CANONICAL_DEPTH_EXCEEDED") {
1507
+ return {
1508
+ ok: false,
1509
+ reason: "Canonical structured value exceeds bounded depth limit.",
1510
+ code: "AI_CHECK_CANONICAL_DEPTH_EXCEEDED"
1511
+ };
1512
+ }
1513
+ return {
1514
+ ok: false,
1515
+ reason: "Canonical structured encoding failed.",
1516
+ code: "AI_CHECK_CANONICAL_UNSERIALIZABLE"
1517
+ };
1518
+ }
1519
+ }
1520
+ function canonicalStructuredEqual(left, right, limits) {
1521
+ const a = encodeCanonicalStructured(left);
1522
+ if (!a.ok) return { equal: false, reason: a.reason, code: a.code };
1523
+ const b = encodeCanonicalStructured(right);
1524
+ if (!b.ok) return { equal: false, reason: b.reason, code: b.code };
1525
+ if (a.value !== b.value) {
1526
+ return {
1527
+ equal: false,
1528
+ reason: "Canonical structured values differ.",
1529
+ code: "AI_CHECK_CANONICAL_NOT_EQUAL"
1530
+ };
1531
+ }
1532
+ return { equal: true };
1533
+ }
1534
+
1465
1535
  // packages/core/src/checks/tool-arguments.ts
1466
1536
  function decodeToken(token) {
1467
1537
  return token.replace(/~1/g, "/").replace(/~0/g, "~");
@@ -1507,17 +1577,30 @@ function jsonType(value) {
1507
1577
  return typeof value;
1508
1578
  }
1509
1579
  function valuesEqual(left, right) {
1510
- return JSON.stringify(left) === JSON.stringify(right);
1580
+ return canonicalStructuredEqual(left, right).equal === true;
1581
+ }
1582
+ function isStructuredArgumentEvidence(value) {
1583
+ return value !== void 0 && value !== null && typeof value === "object";
1511
1584
  }
1512
1585
  function extractToolArgumentPayload(event) {
1513
1586
  const attrs2 = event.attributes ?? {};
1514
1587
  for (const key of ["arguments", "input", "toolArguments"]) {
1515
1588
  const candidate = attrs2[key];
1516
- if (candidate !== void 0 && candidate !== null && typeof candidate === "object") {
1589
+ if (isStructuredArgumentEvidence(candidate)) {
1517
1590
  return { present: true, value: candidate };
1518
1591
  }
1519
1592
  }
1520
- if (event.inputSummary !== void 0 && event.inputSummary !== null && typeof event.inputSummary === "object") {
1593
+ const metadata = attrs2.metadata;
1594
+ if (isStructuredArgumentEvidence(metadata) && !Array.isArray(metadata)) {
1595
+ const nested = metadata;
1596
+ for (const key of ["arguments", "input", "toolArguments"]) {
1597
+ const candidate = nested[key];
1598
+ if (isStructuredArgumentEvidence(candidate)) {
1599
+ return { present: true, value: candidate };
1600
+ }
1601
+ }
1602
+ }
1603
+ if (isStructuredArgumentEvidence(event.inputSummary)) {
1521
1604
  return { present: true, value: event.inputSummary };
1522
1605
  }
1523
1606
  return { present: false, value: void 0 };
@@ -1980,11 +2063,9 @@ function argumentDigestOf(event) {
1980
2063
  function argumentFingerprint(event) {
1981
2064
  const payload = extractToolArgumentPayload(event);
1982
2065
  if (payload.present) {
1983
- try {
1984
- return { kind: "structured", value: JSON.stringify(payload.value) };
1985
- } catch {
1986
- return { kind: "missing" };
1987
- }
2066
+ const encoded = encodeCanonicalStructured(payload.value);
2067
+ if (!encoded.ok) return { kind: "missing" };
2068
+ return { kind: "structured", value: encoded.value };
1988
2069
  }
1989
2070
  const digest = argumentDigestOf(event);
1990
2071
  if (digest) return { kind: "digest", value: digest };
@@ -2079,19 +2160,84 @@ function llmReferencesTool(llm, toolEvent, requireExplicit) {
2079
2160
  if (toolCallId && refs.has(toolCallId)) return true;
2080
2161
  return false;
2081
2162
  }
2163
+ function attemptIdOf2(event) {
2164
+ const value = workflowFor2(event).attemptId;
2165
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
2166
+ }
2167
+ function hasDuplicateAttemptIds2(members) {
2168
+ const seen = /* @__PURE__ */ new Set();
2169
+ for (const event of members) {
2170
+ const id = attemptIdOf2(event);
2171
+ if (!id) continue;
2172
+ if (seen.has(id)) return true;
2173
+ seen.add(id);
2174
+ }
2175
+ return false;
2176
+ }
2177
+ function findRetryTarget(members, retryOf) {
2178
+ return members.find(
2179
+ (candidate) => candidate.eventId === retryOf || attemptIdOf2(candidate) === retryOf || workflowFor2(candidate).operationId === retryOf
2180
+ );
2181
+ }
2082
2182
  function groupToolAttempts(events, toolName2) {
2183
+ const members = events.filter(
2184
+ (event) => event.kind === "TOOL" && resolveCanonicalToolName(event) === toolName2
2185
+ );
2186
+ const parent = /* @__PURE__ */ new Map();
2187
+ const ensure = (id) => {
2188
+ if (!parent.has(id)) parent.set(id, id);
2189
+ return id;
2190
+ };
2191
+ const find = (id) => {
2192
+ ensure(id);
2193
+ let cur = id;
2194
+ while (parent.get(cur) !== cur) {
2195
+ cur = parent.get(cur);
2196
+ }
2197
+ let walk = id;
2198
+ while (parent.get(walk) !== cur) {
2199
+ const next = parent.get(walk);
2200
+ parent.set(walk, cur);
2201
+ walk = next;
2202
+ }
2203
+ return cur;
2204
+ };
2205
+ const union = (a, b) => {
2206
+ const ra = find(a);
2207
+ const rb = find(b);
2208
+ if (ra !== rb) parent.set(ra, rb);
2209
+ };
2210
+ const nodeId = (event) => {
2211
+ const operationId = workflowFor2(event).operationId;
2212
+ if (typeof operationId === "string" && operationId.trim() !== "") {
2213
+ return `op:${operationId.trim()}`;
2214
+ }
2215
+ return `event:${event.eventId}`;
2216
+ };
2217
+ for (const event of members) {
2218
+ ensure(nodeId(event));
2219
+ }
2220
+ for (const event of members) {
2221
+ const retryOf = workflowFor2(event).retryOf;
2222
+ if (typeof retryOf !== "string" || retryOf.trim() === "") continue;
2223
+ const target = findRetryTarget(members, retryOf.trim());
2224
+ if (target) {
2225
+ union(nodeId(event), nodeId(target));
2226
+ }
2227
+ }
2083
2228
  const groups = /* @__PURE__ */ new Map();
2084
- for (const event of events) {
2085
- if (event.kind !== "TOOL") continue;
2086
- if (resolveCanonicalToolName(event) !== toolName2) continue;
2087
- const workflow = workflowFor2(event);
2088
- const key = workflow.operationId ?? `__tool__:${toolName2}`;
2229
+ for (const event of members) {
2230
+ const key = find(nodeId(event));
2089
2231
  const list = groups.get(key) ?? [];
2090
2232
  list.push(event);
2091
2233
  groups.set(key, list);
2092
2234
  }
2093
2235
  return groups;
2094
2236
  }
2237
+ function latestAttempt(ordered) {
2238
+ if (ordered.length === 0) return void 0;
2239
+ return ordered[ordered.length - 1];
2240
+ }
2095
2241
  function evaluateRecoveryOperations(events, operations) {
2096
2242
  const findings = [];
2097
2243
  const llmEvents = events.filter((event) => event.kind === "LLM");
@@ -2114,6 +2260,17 @@ function evaluateRecoveryOperations(events, operations) {
2114
2260
  }
2115
2261
  for (const [operationKey, members] of groups) {
2116
2262
  const ordered = sortByTime2(members);
2263
+ if (hasDuplicateAttemptIds2(members)) {
2264
+ findings.push(
2265
+ fail3(
2266
+ "contract.retry.operations.duplicate-attempt-id",
2267
+ `Tool ${operation.tool} operation ${operationKey} has duplicate attemptId values.`,
2268
+ members.slice(0, 4).map(eventEvidence2),
2269
+ "unique attemptId",
2270
+ "duplicate"
2271
+ )
2272
+ );
2273
+ }
2117
2274
  if (operation.maxAttempts !== void 0) {
2118
2275
  const count = countOperationAttempts(members);
2119
2276
  if (count > operation.maxAttempts) {
@@ -2150,8 +2307,21 @@ function evaluateRecoveryOperations(events, operations) {
2150
2307
  let sawEarlierError = false;
2151
2308
  let sawLaterOkAfterError = false;
2152
2309
  let lastOk;
2310
+ let identityUnavailable = false;
2153
2311
  for (const event of ordered) {
2154
2312
  const classification = classifyRetryAttempt(event, prior, members);
2313
+ if (classification.kind === "unknown") {
2314
+ identityUnavailable = true;
2315
+ findings.push(
2316
+ fail3(
2317
+ "contract.retry.operations.attempt-identity",
2318
+ `Tool ${operation.tool} operation ${operationKey} has ambiguous attempt identity (${classification.reason}).`,
2319
+ [eventEvidence2(event)],
2320
+ "explicit operationId/attemptId/retryOf chronology",
2321
+ { code: "AI_CHECK_RECOVERY_ATTEMPT_IDENTITY_UNAVAILABLE", reason: classification.reason }
2322
+ )
2323
+ );
2324
+ }
2155
2325
  const isRetry = classification.kind === "retry";
2156
2326
  if (isRetry && operation.requireFailureBeforeRetry) {
2157
2327
  const earlierFailure = prior.some(
@@ -2186,6 +2356,16 @@ function evaluateRecoveryOperations(events, operations) {
2186
2356
  )
2187
2357
  );
2188
2358
  }
2359
+ } else {
2360
+ findings.push(
2361
+ fail3(
2362
+ "contract.retry.operations.retryable-error",
2363
+ `Retry of tool ${operation.tool} has no prior error code evidence.`,
2364
+ [eventEvidence2(event)],
2365
+ [...allowed],
2366
+ null
2367
+ )
2368
+ );
2189
2369
  }
2190
2370
  }
2191
2371
  if (isRetry && (operation.requireSameArguments === true || operation.requireSameArguments === "structured-or-digest")) {
@@ -2203,6 +2383,16 @@ function evaluateRecoveryOperations(events, operations) {
2203
2383
  )
2204
2384
  );
2205
2385
  }
2386
+ } else {
2387
+ findings.push(
2388
+ fail3(
2389
+ "contract.retry.operations.same-arguments",
2390
+ "Structured or digest argument evidence unavailable for same-arguments check.",
2391
+ [eventEvidence2(event)],
2392
+ "matching structured args or digests",
2393
+ { code: "AI_CHECK_RECOVERY_ARGUMENT_EVIDENCE_UNAVAILABLE" }
2394
+ )
2395
+ );
2206
2396
  }
2207
2397
  }
2208
2398
  if (event.status === "error") {
@@ -2215,23 +2405,23 @@ function evaluateRecoveryOperations(events, operations) {
2215
2405
  }
2216
2406
  prior.push(event);
2217
2407
  }
2408
+ const latest = latestAttempt(ordered);
2218
2409
  if (operation.requireTerminalSuccess) {
2219
- const hasOk = ordered.some((event) => event.status === "ok");
2220
- if (!hasOk) {
2410
+ if (!latest || latest.status !== "ok") {
2221
2411
  findings.push(
2222
2412
  fail3(
2223
2413
  "contract.retry.operations.terminal-success",
2224
- `Tool ${operation.tool} operation ${operationKey} has no terminal ok success.`,
2414
+ `Tool ${operation.tool} operation ${operationKey} does not terminate with ok on the latest attempt.`,
2225
2415
  ordered.slice(0, 4).map(eventEvidence2),
2226
- "ok",
2227
- ordered.map((event) => event.status)
2416
+ "latest attempt ok",
2417
+ latest?.status ?? "missing"
2228
2418
  )
2229
2419
  );
2230
2420
  }
2231
2421
  }
2232
2422
  if (operation.requireRecoveredFailureVisible) {
2233
- const hasOk = ordered.some((event) => event.status === "ok");
2234
- if (ordered.length > 1 && hasOk && !sawLaterOkAfterError) {
2423
+ const latestOk = latest?.status === "ok";
2424
+ if (ordered.length > 1 && latestOk && !sawLaterOkAfterError) {
2235
2425
  findings.push(
2236
2426
  fail3(
2237
2427
  "contract.retry.operations.recovered-failure-visible",
@@ -2244,21 +2434,36 @@ function evaluateRecoveryOperations(events, operations) {
2244
2434
  }
2245
2435
  }
2246
2436
  const dependency = operation.successfulResultDependency;
2247
- if (dependency?.consumerKind === "LLM" && lastOk) {
2248
- const requireExplicit = dependency.requireExplicitReference === true;
2249
- const referenced = llmEvents.some(
2250
- (llmEvent) => llmReferencesTool(llmEvent, lastOk, requireExplicit)
2251
- );
2252
- if (!referenced) {
2253
- findings.push(
2254
- fail3(
2255
- "contract.retry.operations.successful-result-dependency",
2256
- requireExplicit ? `Successful ${operation.tool} result is not explicitly referenced by a later LLM event.` : `Successful ${operation.tool} result has no later LLM consumer.`,
2257
- [eventEvidence2(lastOk)],
2258
- "LLM reference to successful tool event",
2259
- { code: "AI_CHECK_RECOVERY_RESULT_DEPENDENCY_MISSING" }
2260
- )
2261
- );
2437
+ if (dependency?.consumerKind === "LLM") {
2438
+ const successForDependency = latest?.status === "ok" ? latest : identityUnavailable ? void 0 : lastOk;
2439
+ if (successForDependency) {
2440
+ if (sideEffectClass === "write" && isUnevaluableWriteCompletion(successForDependency) && !hasIdempotencyEvidence2(successForDependency)) {
2441
+ findings.push(
2442
+ fail3(
2443
+ "contract.retry.operations.successful-result-dependency",
2444
+ `Successful ${operation.tool} result dependency is unevaluable because write completion evidence is unavailable.`,
2445
+ [eventEvidence2(successForDependency)],
2446
+ "LLM reference to successful tool event",
2447
+ { code: "AI_CHECK_RECOVERY_WRITE_COMPLETION_UNAVAILABLE" }
2448
+ )
2449
+ );
2450
+ } else {
2451
+ const requireExplicit = dependency.requireExplicitReference === true;
2452
+ const referenced = llmEvents.some(
2453
+ (llmEvent) => llmReferencesTool(llmEvent, successForDependency, requireExplicit)
2454
+ );
2455
+ if (!referenced) {
2456
+ findings.push(
2457
+ fail3(
2458
+ "contract.retry.operations.successful-result-dependency",
2459
+ requireExplicit ? `Successful ${operation.tool} result is not explicitly referenced by a later LLM event.` : `Successful ${operation.tool} result has no later LLM consumer.`,
2460
+ [eventEvidence2(successForDependency)],
2461
+ "LLM reference to successful tool event",
2462
+ { code: "AI_CHECK_RECOVERY_RESULT_DEPENDENCY_MISSING" }
2463
+ )
2464
+ );
2465
+ }
2466
+ }
2262
2467
  }
2263
2468
  }
2264
2469
  }