agent-inspect 6.25.0 → 6.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -6
- package/README.md +1 -1
- package/docs/CLI.md +2 -1
- package/docs/KNOWN-ISSUES.md +5 -0
- package/docs/LIMITATIONS.md +2 -0
- package/docs/SESSIONS-AND-OUTCOMES.md +22 -2
- package/docs/TRACE-CONTRACTS.md +4 -1
- package/package.json +1 -1
- package/packages/cli/dist/{chunk-NHVN5AYW.mjs → chunk-HPR2OJPU.mjs} +2 -2
- package/packages/cli/dist/{chunk-NHVN5AYW.mjs.map → chunk-HPR2OJPU.mjs.map} +1 -1
- package/packages/cli/dist/index.cjs +22 -6
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +25 -9
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/cli/dist/{src-SXWEZTIS.mjs → src-J43ECFHC.mjs} +3 -3
- package/packages/cli/dist/{src-SXWEZTIS.mjs.map → src-J43ECFHC.mjs.map} +1 -1
- package/packages/core/dist/advanced.cjs +13 -10
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +9 -3
- package/packages/core/dist/advanced.d.ts +9 -3
- package/packages/core/dist/advanced.mjs +14 -13
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/core/dist/checks.cjs +176 -51
- package/packages/core/dist/checks.cjs.map +1 -1
- package/packages/core/dist/checks.d.cts +10 -5
- package/packages/core/dist/checks.d.ts +10 -5
- package/packages/core/dist/checks.mjs +1 -1
- package/packages/core/dist/{chunk-VMDCDWBE.mjs → chunk-LHSXRIYU.mjs} +178 -53
- package/packages/core/dist/chunk-LHSXRIYU.mjs.map +1 -0
- package/packages/core/dist/chunk-VMDCDWBE.mjs.map +0 -1
|
@@ -1500,6 +1500,99 @@ function eventEvidence(event) {
|
|
|
1500
1500
|
status: event.status
|
|
1501
1501
|
};
|
|
1502
1502
|
}
|
|
1503
|
+
function eventTime(event) {
|
|
1504
|
+
return event.startedAt ?? event.timestamp ?? "";
|
|
1505
|
+
}
|
|
1506
|
+
function sortByTime(events) {
|
|
1507
|
+
return [...events].sort((a, b) => eventTime(a).localeCompare(eventTime(b)));
|
|
1508
|
+
}
|
|
1509
|
+
function attemptNumberOf(event) {
|
|
1510
|
+
const workflow = workflowFor(event);
|
|
1511
|
+
const value = workflow.attemptNumber ?? workflow.attempt;
|
|
1512
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1513
|
+
}
|
|
1514
|
+
function attemptIdOf(event) {
|
|
1515
|
+
const value = workflowFor(event).attemptId;
|
|
1516
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
1517
|
+
}
|
|
1518
|
+
function countOperationAttempts(members) {
|
|
1519
|
+
const attemptIds = new Set(
|
|
1520
|
+
members.map((event) => attemptIdOf(event)).filter((value) => value !== void 0)
|
|
1521
|
+
);
|
|
1522
|
+
if (attemptIds.size > 0) {
|
|
1523
|
+
return attemptIds.size;
|
|
1524
|
+
}
|
|
1525
|
+
const numbers = members.map((event) => attemptNumberOf(event)).filter((value) => value !== void 0).sort((a, b) => a - b);
|
|
1526
|
+
if (numbers.length > 0) {
|
|
1527
|
+
const unique = [...new Set(numbers)];
|
|
1528
|
+
const min = unique[0];
|
|
1529
|
+
const max = unique[unique.length - 1];
|
|
1530
|
+
const contiguous = unique.length === max - min + 1 && unique.every((value, index) => value === min + index);
|
|
1531
|
+
if (contiguous) {
|
|
1532
|
+
return unique.length;
|
|
1533
|
+
}
|
|
1534
|
+
return unique.length;
|
|
1535
|
+
}
|
|
1536
|
+
return members.filter((event) => event.kind === "TOOL" || event.kind === "LLM").length;
|
|
1537
|
+
}
|
|
1538
|
+
function classifyRetryAttempt(event, priorOrdered, allMembers) {
|
|
1539
|
+
const workflow = workflowFor(event);
|
|
1540
|
+
const attemptNumber = attemptNumberOf(event);
|
|
1541
|
+
const attemptId = attemptIdOf(event);
|
|
1542
|
+
const retryOf = workflow.retryOf;
|
|
1543
|
+
if (typeof retryOf === "string" && retryOf.trim() !== "") {
|
|
1544
|
+
const target = allMembers.find(
|
|
1545
|
+
(candidate) => candidate.eventId === retryOf || attemptIdOf(candidate) === retryOf || workflowFor(candidate).operationId === retryOf
|
|
1546
|
+
);
|
|
1547
|
+
if (!target) {
|
|
1548
|
+
return { kind: "unknown", reason: "retryOf target missing" };
|
|
1549
|
+
}
|
|
1550
|
+
if (eventTime(target) > eventTime(event)) {
|
|
1551
|
+
return { kind: "unknown", reason: "retryOf target does not precede retry" };
|
|
1552
|
+
}
|
|
1553
|
+
return { kind: "retry" };
|
|
1554
|
+
}
|
|
1555
|
+
if (attemptNumber !== void 0) {
|
|
1556
|
+
if (attemptNumber > 1) {
|
|
1557
|
+
return { kind: "retry" };
|
|
1558
|
+
}
|
|
1559
|
+
if (attemptNumber === 1) {
|
|
1560
|
+
return { kind: "first" };
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (attemptId !== void 0) {
|
|
1564
|
+
const priorDistinct = priorOrdered.some((candidate) => {
|
|
1565
|
+
const priorId = attemptIdOf(candidate);
|
|
1566
|
+
return priorId !== void 0 && priorId !== attemptId;
|
|
1567
|
+
});
|
|
1568
|
+
if (priorDistinct) {
|
|
1569
|
+
return { kind: "retry" };
|
|
1570
|
+
}
|
|
1571
|
+
if (priorOrdered.length === 0) {
|
|
1572
|
+
return { kind: "first" };
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
const priorFinished = priorOrdered.filter(
|
|
1576
|
+
(candidate) => candidate.status === "ok" || candidate.status === "error"
|
|
1577
|
+
);
|
|
1578
|
+
if (priorFinished.length > 0) {
|
|
1579
|
+
return { kind: "retry" };
|
|
1580
|
+
}
|
|
1581
|
+
if (priorOrdered.length === 0) {
|
|
1582
|
+
return { kind: "first" };
|
|
1583
|
+
}
|
|
1584
|
+
return { kind: "unknown", reason: "ambiguous attempt identity" };
|
|
1585
|
+
}
|
|
1586
|
+
function hasDuplicateAttemptIds(members) {
|
|
1587
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1588
|
+
for (const event of members) {
|
|
1589
|
+
const id = attemptIdOf(event);
|
|
1590
|
+
if (!id) continue;
|
|
1591
|
+
if (seen.has(id)) return true;
|
|
1592
|
+
seen.add(id);
|
|
1593
|
+
}
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1503
1596
|
function evaluateRetrySafetyRules(events, rules, _runEvidence) {
|
|
1504
1597
|
const findings = [];
|
|
1505
1598
|
const byOperation = /* @__PURE__ */ new Map();
|
|
@@ -1513,11 +1606,18 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
|
|
|
1513
1606
|
}
|
|
1514
1607
|
if (rules.maxAttempts !== void 0) {
|
|
1515
1608
|
for (const [operationId, members] of byOperation) {
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1609
|
+
if (hasDuplicateAttemptIds(members)) {
|
|
1610
|
+
findings.push(
|
|
1611
|
+
fail2(
|
|
1612
|
+
"contract.retry.duplicate-attempt-id",
|
|
1613
|
+
`Operation ${operationId} has duplicate attemptId values.`,
|
|
1614
|
+
members.slice(0, 4).map(eventEvidence),
|
|
1615
|
+
"unique attemptId",
|
|
1616
|
+
"duplicate"
|
|
1617
|
+
)
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
const count = countOperationAttempts(members);
|
|
1521
1621
|
if (count > rules.maxAttempts) {
|
|
1522
1622
|
findings.push(
|
|
1523
1623
|
fail2(
|
|
@@ -1552,18 +1652,18 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
|
|
|
1552
1652
|
const workflow = workflowFor(event);
|
|
1553
1653
|
const fallbackOf = workflow.fallbackOf;
|
|
1554
1654
|
if (!fallbackOf) continue;
|
|
1555
|
-
const prior = byOperation.get(fallbackOf) ??
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
if (!
|
|
1655
|
+
const prior = byOperation.get(fallbackOf) ?? [];
|
|
1656
|
+
const fallbackStart = eventTime(event);
|
|
1657
|
+
const earlierFailure = prior.some(
|
|
1658
|
+
(candidate) => candidate.status === "error" && eventTime(candidate) !== "" && fallbackStart !== "" && eventTime(candidate) < fallbackStart
|
|
1659
|
+
);
|
|
1660
|
+
if (!earlierFailure) {
|
|
1561
1661
|
findings.push(
|
|
1562
1662
|
fail2(
|
|
1563
1663
|
"contract.retry.fallback-after-failure",
|
|
1564
|
-
`Fallback for ${fallbackOf} appeared without
|
|
1664
|
+
`Fallback for ${fallbackOf} appeared without an earlier failure in the related operation.`,
|
|
1565
1665
|
[eventEvidence(event)],
|
|
1566
|
-
"
|
|
1666
|
+
"earlier error attempt",
|
|
1567
1667
|
{ fallbackOf }
|
|
1568
1668
|
)
|
|
1569
1669
|
);
|
|
@@ -1585,39 +1685,46 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
|
|
|
1585
1685
|
byToolOp.set(key, list);
|
|
1586
1686
|
}
|
|
1587
1687
|
for (const [, members] of byToolOp) {
|
|
1588
|
-
const ordered =
|
|
1589
|
-
const aTime = a.startedAt ?? a.timestamp ?? "";
|
|
1590
|
-
const bTime = b.startedAt ?? b.timestamp ?? "";
|
|
1591
|
-
return aTime.localeCompare(bTime);
|
|
1592
|
-
});
|
|
1688
|
+
const ordered = sortByTime(members);
|
|
1593
1689
|
let sawOk = false;
|
|
1594
1690
|
let sawSideEffectOk = false;
|
|
1691
|
+
const prior = [];
|
|
1595
1692
|
for (const event of ordered) {
|
|
1596
1693
|
const name = resolveCanonicalToolName(event);
|
|
1597
|
-
const
|
|
1598
|
-
if (
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1694
|
+
const classification = classifyRetryAttempt(event, prior, members);
|
|
1695
|
+
if (classification.kind === "unknown" && rules.requireIdempotencyEvidenceForRetry) {
|
|
1696
|
+
findings.push(
|
|
1697
|
+
fail2(
|
|
1698
|
+
"contract.retry.ambiguous-identity",
|
|
1699
|
+
`Ambiguous attempt identity for tool ${name}; strict retry evidence cannot pass.`,
|
|
1700
|
+
[eventEvidence(event)],
|
|
1701
|
+
"explicit attempt identity",
|
|
1702
|
+
classification.reason
|
|
1703
|
+
)
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
const isRetry = classification.kind === "retry";
|
|
1707
|
+
if (isRetry && rules.requireIdempotencyEvidenceForRetry && !hasIdempotencyEvidence(event)) {
|
|
1708
|
+
findings.push(
|
|
1709
|
+
fail2(
|
|
1710
|
+
"contract.retry.idempotency-evidence",
|
|
1711
|
+
`Retry of tool ${name} lacks idempotencyKey / noSideEffect evidence.`,
|
|
1712
|
+
[eventEvidence(event)],
|
|
1713
|
+
"idempotencyKey|noSideEffect",
|
|
1714
|
+
{ code: "AI_CHECK_RETRY_EVIDENCE_UNAVAILABLE" }
|
|
1715
|
+
)
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
if (sawOk && nonIdempotent.has(name) && sawSideEffectOk && !hasIdempotencyEvidence(event)) {
|
|
1719
|
+
findings.push(
|
|
1720
|
+
fail2(
|
|
1721
|
+
"contract.retry.non-idempotent-side-effect",
|
|
1722
|
+
`Retry of non-idempotent tool ${name} after a confirmed ok side effect.`,
|
|
1723
|
+
[eventEvidence(event)],
|
|
1724
|
+
"no retry after side effect",
|
|
1725
|
+
name
|
|
1726
|
+
)
|
|
1727
|
+
);
|
|
1621
1728
|
}
|
|
1622
1729
|
if (event.status === "ok") {
|
|
1623
1730
|
sawOk = true;
|
|
@@ -1625,25 +1732,43 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
|
|
|
1625
1732
|
sawSideEffectOk = true;
|
|
1626
1733
|
}
|
|
1627
1734
|
}
|
|
1735
|
+
prior.push(event);
|
|
1628
1736
|
}
|
|
1629
1737
|
}
|
|
1630
1738
|
}
|
|
1631
1739
|
if (rules.requireRecoveredFailureVisible) {
|
|
1632
1740
|
for (const [operationId, members] of byOperation) {
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1635
|
-
|
|
1741
|
+
const ordered = sortByTime(members);
|
|
1742
|
+
const toolish = ordered.filter(
|
|
1743
|
+
(event) => (event.kind === "TOOL" || event.kind === "LLM") && (event.status === "ok" || event.status === "error")
|
|
1744
|
+
);
|
|
1745
|
+
if (toolish.length < 2) continue;
|
|
1746
|
+
let sawEarlierError = false;
|
|
1747
|
+
let sawLaterOkAfterError = false;
|
|
1748
|
+
for (const event of toolish) {
|
|
1749
|
+
if (event.status === "error") {
|
|
1750
|
+
sawEarlierError = true;
|
|
1751
|
+
} else if (event.status === "ok" && sawEarlierError) {
|
|
1752
|
+
sawLaterOkAfterError = true;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
const hasOk = toolish.some((event) => event.status === "ok");
|
|
1756
|
+
const attemptish = toolish.some((event) => {
|
|
1636
1757
|
const meta = workflowFor(event);
|
|
1637
|
-
return meta.attemptId !== void 0 || meta.attemptNumber !== void 0 || meta.attempt !== void 0;
|
|
1638
|
-
}) ||
|
|
1639
|
-
if (
|
|
1758
|
+
return meta.attemptId !== void 0 || meta.attemptNumber !== void 0 || meta.attempt !== void 0 || meta.retryOf !== void 0;
|
|
1759
|
+
}) || toolish.length > 1;
|
|
1760
|
+
if (!attemptish || !hasOk) continue;
|
|
1761
|
+
if (!sawLaterOkAfterError) {
|
|
1640
1762
|
findings.push(
|
|
1641
1763
|
fail2(
|
|
1642
1764
|
"contract.retry.recovered-failure-visible",
|
|
1643
|
-
`Operation ${operationId}
|
|
1765
|
+
`Operation ${operationId} does not show an earlier error attempt before a later success in the same chain.`,
|
|
1644
1766
|
members.slice(0, 4).map(eventEvidence),
|
|
1645
|
-
"error
|
|
1646
|
-
{
|
|
1767
|
+
"earlier error then later ok",
|
|
1768
|
+
{
|
|
1769
|
+
sawEarlierError,
|
|
1770
|
+
statuses: toolish.map((event) => event.status)
|
|
1771
|
+
}
|
|
1647
1772
|
)
|
|
1648
1773
|
);
|
|
1649
1774
|
}
|