agent-inspect 6.31.5 → 6.31.7

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 +13 -0
  2. package/README.md +1 -1
  3. package/docs/AI-SDK-ADOPTION.md +62 -34
  4. package/docs/SAFE-TRACE-SHARING.md +1 -1
  5. package/package.json +1 -1
  6. package/packages/cli/dist/{chunk-LKEPABND.mjs → chunk-QV73NM25.mjs} +100 -31
  7. package/packages/cli/dist/chunk-QV73NM25.mjs.map +1 -0
  8. package/packages/cli/dist/index.cjs +474 -92
  9. package/packages/cli/dist/index.cjs.map +1 -1
  10. package/packages/cli/dist/index.mjs +379 -67
  11. package/packages/cli/dist/index.mjs.map +1 -1
  12. package/packages/cli/dist/{src-4WYF5RFI.mjs → src-KFOBXFPN.mjs} +3 -3
  13. package/packages/cli/dist/{src-4WYF5RFI.mjs.map → src-KFOBXFPN.mjs.map} +1 -1
  14. package/packages/core/dist/advanced.cjs +95 -23
  15. package/packages/core/dist/advanced.cjs.map +1 -1
  16. package/packages/core/dist/advanced.mjs +11 -11
  17. package/packages/core/dist/checks.cjs +2 -5
  18. package/packages/core/dist/checks.cjs.map +1 -1
  19. package/packages/core/dist/checks.mjs +3 -3
  20. package/packages/core/dist/{chunk-OSGNGRHF.mjs → chunk-3RV3JILQ.mjs} +34 -16
  21. package/packages/core/dist/chunk-3RV3JILQ.mjs.map +1 -0
  22. package/packages/core/dist/{chunk-NY4ILMXK.mjs → chunk-7YYS742B.mjs} +50 -13
  23. package/packages/core/dist/chunk-7YYS742B.mjs.map +1 -0
  24. package/packages/core/dist/{chunk-2KJR6X4V.mjs → chunk-DK3OW6UI.mjs} +3 -3
  25. package/packages/core/dist/chunk-DK3OW6UI.mjs.map +1 -0
  26. package/packages/core/dist/{chunk-PKDBFBJ7.mjs → chunk-LXYPM2ZO.mjs} +5 -8
  27. package/packages/core/dist/{chunk-PKDBFBJ7.mjs.map → chunk-LXYPM2ZO.mjs.map} +1 -1
  28. package/packages/core/dist/{chunk-INE5IWLN.mjs → chunk-N5PZAI6Q.mjs} +3 -3
  29. package/packages/core/dist/{chunk-INE5IWLN.mjs.map → chunk-N5PZAI6Q.mjs.map} +1 -1
  30. package/packages/core/dist/{chunk-Q5SLKV2Y.mjs → chunk-QLYUWKK7.mjs} +19 -2
  31. package/packages/core/dist/chunk-QLYUWKK7.mjs.map +1 -0
  32. package/packages/core/dist/{chunk-MTMTXTJL.mjs → chunk-VQUB3WRI.mjs} +4 -4
  33. package/packages/core/dist/{chunk-MTMTXTJL.mjs.map → chunk-VQUB3WRI.mjs.map} +1 -1
  34. package/packages/core/dist/exporters.cjs +253 -50
  35. package/packages/core/dist/exporters.cjs.map +1 -1
  36. package/packages/core/dist/exporters.mjs +208 -42
  37. package/packages/core/dist/exporters.mjs.map +1 -1
  38. package/packages/core/dist/index.cjs +1 -1
  39. package/packages/core/dist/index.cjs.map +1 -1
  40. package/packages/core/dist/index.mjs +4 -4
  41. package/packages/core/dist/persisted.cjs +17 -0
  42. package/packages/core/dist/persisted.cjs.map +1 -1
  43. package/packages/core/dist/persisted.mjs +2 -2
  44. package/packages/core/dist/readers.cjs +48 -13
  45. package/packages/core/dist/readers.cjs.map +1 -1
  46. package/packages/core/dist/readers.mjs +2 -2
  47. package/packages/core/dist/writers.cjs +1 -1
  48. package/packages/core/dist/writers.cjs.map +1 -1
  49. package/packages/core/dist/writers.mjs +1 -1
  50. package/packages/cli/dist/chunk-LKEPABND.mjs.map +0 -1
  51. package/packages/core/dist/chunk-2KJR6X4V.mjs.map +0 -1
  52. package/packages/core/dist/chunk-NY4ILMXK.mjs.map +0 -1
  53. package/packages/core/dist/chunk-OSGNGRHF.mjs.map +0 -1
  54. package/packages/core/dist/chunk-Q5SLKV2Y.mjs.map +0 -1
@@ -185,7 +185,7 @@ var Redactor = class {
185
185
  };
186
186
 
187
187
  // packages/core/src/safety/credential-value-patterns.ts
188
- var KEY_VALUE_SECRET_PATTERN = /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=(?!\[(?:REDACTED(?::[^\]]*)?|HASH:[0-9a-f]{8})\](?![^\s"'\\&#?;/]))([^\s"'\\]{8,})/i;
188
+ var KEY_VALUE_SECRET_PATTERN = /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=(?!\[(?:REDACTED(?::[^\]]*)?|HASH:[0-9a-f]{8})\](?![^\s"'\\&#?]))([^\s"'\\]{8,})/i;
189
189
  var HIGH_CONFIDENCE_CREDENTIAL_PATTERNS = [
190
190
  /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
191
191
  /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
@@ -223,6 +223,8 @@ var CONNECTION_PROTOCOLS = /* @__PURE__ */ new Set([
223
223
  "amqps:"
224
224
  ]);
225
225
  var CONNECTION_URI = /^(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|rediss?|amqps?):\/\/[^\s]+$/i;
226
+ var CONNECTION_USERINFO = /^(postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|rediss?|amqps?):\/\/([^@/?#]+)@(.+)$/i;
227
+ var CONNECTION_HAS_USERINFO = /^(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|rediss?|amqps?):\/\/[^@/?#]+@/i;
226
228
  var UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
227
229
  var HEX_SEGMENT = /^[0-9a-f]{12,}$/i;
228
230
  var NUMERIC_SEGMENT = /^\d{5,}$/;
@@ -246,6 +248,28 @@ function parseRedactableUrl(value) {
246
248
  return void 0;
247
249
  }
248
250
  }
251
+ function redactConnectionUserinfoFallback(value, replacement) {
252
+ const trimmed = value.trim();
253
+ const match = CONNECTION_USERINFO.exec(trimmed);
254
+ if (match) {
255
+ const [, scheme, , rest] = match;
256
+ if (scheme && rest && rest.length > 0) {
257
+ return {
258
+ value: `${scheme}://${rest}`,
259
+ credentialRedacted: true,
260
+ identifiersMasked: false
261
+ };
262
+ }
263
+ }
264
+ if (looksLikeConnectionUri(trimmed) && CONNECTION_HAS_USERINFO.test(trimmed)) {
265
+ return {
266
+ value: replacement,
267
+ credentialRedacted: true,
268
+ identifiersMasked: false
269
+ };
270
+ }
271
+ return void 0;
272
+ }
249
273
  function isHttpProtocol(protocol) {
250
274
  return protocol === "http:" || protocol === "https:";
251
275
  }
@@ -287,9 +311,11 @@ function redactUrlString(value, options) {
287
311
  identifiersMasked: false
288
312
  };
289
313
  if (!looksLikeUrlForRedaction(value)) return unchanged;
290
- const url = parseRedactableUrl(value);
291
- if (!url) return unchanged;
292
314
  const replacement = options.replacement ?? "[REDACTED]";
315
+ const url = parseRedactableUrl(value);
316
+ if (!url) {
317
+ return redactConnectionUserinfoFallback(value, replacement) ?? unchanged;
318
+ }
293
319
  const idPlaceholder = options.idPlaceholder ?? "[id]";
294
320
  const http = isHttpProtocol(url.protocol);
295
321
  let credentialRedacted = url.username.length > 0 || url.password.length > 0;
@@ -1188,6 +1214,10 @@ function exportOpenInference(tree, options) {
1188
1214
  warnings
1189
1215
  };
1190
1216
  }
1217
+ var OTLP_STATUS_CODE_UNSET = 0;
1218
+ var OTLP_STATUS_CODE_OK = 1;
1219
+ var OTLP_STATUS_CODE_ERROR = 2;
1220
+ var OTLP_SPAN_KIND_INTERNAL = 1;
1191
1221
  function hexFrom2(seed, byteLen) {
1192
1222
  return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
1193
1223
  }
@@ -1200,7 +1230,19 @@ function stringAttr(key, value) {
1200
1230
  return { key, value: { stringValue: value } };
1201
1231
  }
1202
1232
  function intAttr(key, value) {
1203
- return { key, value: { intValue: String(value) } };
1233
+ return { key, value: { intValue: String(Math.trunc(value)) } };
1234
+ }
1235
+ function doubleAttr(key, value) {
1236
+ return { key, value: { doubleValue: value } };
1237
+ }
1238
+ function boolAttr(key, value) {
1239
+ return { key, value: { boolValue: value } };
1240
+ }
1241
+ function numberAttr(key, value) {
1242
+ if (Number.isFinite(value) && !Number.isInteger(value)) {
1243
+ return doubleAttr(key, value);
1244
+ }
1245
+ return intAttr(key, value);
1204
1246
  }
1205
1247
  function genAiOperationName(kind) {
1206
1248
  switch (kind) {
@@ -1214,10 +1256,27 @@ function genAiOperationName(kind) {
1214
1256
  return void 0;
1215
1257
  }
1216
1258
  }
1259
+ function resolveSpanTimes(ev) {
1260
+ const startedAtMs = typeof ev.attributes?.startedAtMs === "number" && Number.isFinite(ev.attributes.startedAtMs) ? ev.attributes.startedAtMs : void 0;
1261
+ const endedAtMs = typeof ev.attributes?.endedAtMs === "number" && Number.isFinite(ev.attributes.endedAtMs) ? ev.attributes.endedAtMs : void 0;
1262
+ const startMs = startedAtMs ?? ev.timestamp;
1263
+ const startNs = String(unixNano2(startMs));
1264
+ if (endedAtMs !== void 0) {
1265
+ return { startNs, endNs: String(unixNano2(endedAtMs)) };
1266
+ }
1267
+ if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
1268
+ return {
1269
+ startNs,
1270
+ endNs: String(unixNano2(startMs) + unixNano2(ev.durationMs))
1271
+ };
1272
+ }
1273
+ return { startNs };
1274
+ }
1217
1275
  function exportOtlpJson(tree, options) {
1218
1276
  const warnings = [
1219
1277
  "OTLP JSON export uses OTel GenAI-aligned attributes where applicable; experimental until verified against specific collectors.",
1220
- "Not OTLP gRPC/protobuf \u2014 JSON mapping only. Generated locally; no network upload."
1278
+ "Not OTLP gRPC/protobuf \u2014 JSON mapping only. Generated locally; no network upload.",
1279
+ "Status and span kind use OTLP numeric enums (StatusCode 0/1/2, SpanKind INTERNAL=1)."
1221
1280
  ];
1222
1281
  const traceId = hexFrom2(`trace:${tree.runId}`, 16);
1223
1282
  const includeAttributes = options?.includeAttributes ?? false;
@@ -1229,11 +1288,7 @@ function exportOtlpJson(tree, options) {
1229
1288
  const ev = n.event;
1230
1289
  const spanId = hexFrom2(`${tree.runId}:${ev.eventId}`, 8);
1231
1290
  const parentSpanId = ev.parentId ? hexFrom2(`${tree.runId}:${ev.parentId}`, 8) : void 0;
1232
- const startNs = String(unixNano2(ev.timestamp));
1233
- let endNs;
1234
- if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
1235
- endNs = String(unixNano2(ev.timestamp) + unixNano2(ev.durationMs));
1236
- }
1291
+ const { startNs, endNs } = resolveSpanTimes(ev);
1237
1292
  const attrs = [
1238
1293
  stringAttr("agent_inspect.kind", ev.kind),
1239
1294
  stringAttr("agent_inspect.confidence", ev.confidence),
@@ -1263,29 +1318,28 @@ function exportOtlpJson(tree, options) {
1263
1318
  if (includeAttributes && meta && typeof meta === "object") {
1264
1319
  for (const [k, v] of Object.entries(meta)) {
1265
1320
  if (k === "tokens" || k === "model") continue;
1266
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
1267
- attrs.push(
1268
- stringAttr(
1269
- `agent_inspect.preview.${k}`,
1270
- typeof v === "string" ? v.slice(0, maxLen) : String(v)
1271
- )
1272
- );
1321
+ if (typeof v === "string") {
1322
+ attrs.push(stringAttr(`agent_inspect.preview.${k}`, v.slice(0, maxLen)));
1323
+ } else if (typeof v === "number" && Number.isFinite(v)) {
1324
+ attrs.push(numberAttr(`agent_inspect.preview.${k}`, v));
1325
+ } else if (typeof v === "boolean") {
1326
+ attrs.push(boolAttr(`agent_inspect.preview.${k}`, v));
1273
1327
  }
1274
1328
  }
1275
1329
  }
1276
- let statusCode = "STATUS_CODE_UNSET";
1330
+ let statusCode = OTLP_STATUS_CODE_UNSET;
1277
1331
  let statusMessage;
1278
1332
  if (ev.status === "error") {
1279
- statusCode = "STATUS_CODE_ERROR";
1333
+ statusCode = OTLP_STATUS_CODE_ERROR;
1280
1334
  statusMessage = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error").slice(0, maxLen) : "error";
1281
1335
  } else if (ev.status === "ok") {
1282
- statusCode = "STATUS_CODE_OK";
1336
+ statusCode = OTLP_STATUS_CODE_OK;
1283
1337
  }
1284
1338
  const spanJson = {
1285
1339
  traceId,
1286
1340
  spanId,
1287
1341
  name: ev.name,
1288
- kind: "SPAN_KIND_INTERNAL",
1342
+ kind: OTLP_SPAN_KIND_INTERNAL,
1289
1343
  startTimeUnixNano: startNs,
1290
1344
  attributes: attrs,
1291
1345
  status: {
@@ -1327,6 +1381,136 @@ function exportOtlpJson(tree, options) {
1327
1381
 
1328
1382
  // packages/core/src/exporters/validation.ts
1329
1383
  var EXPERIMENTAL = "Experimental compatibility export \u2014 verify against your target tooling before relying on it.";
1384
+ function pushPath(errors, path4, message) {
1385
+ errors.push(`${path4}: ${message}`);
1386
+ }
1387
+ function isRecord3(value) {
1388
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1389
+ }
1390
+ function validateOtlpJson(content) {
1391
+ const errors = [];
1392
+ const warnings = [
1393
+ EXPERIMENTAL,
1394
+ "OTLP JSON mapping uses OTel GenAI-aligned attributes where applicable; collectors may require transformation.",
1395
+ "protocol-valid \u2260 profile-complete \u2260 fixture-complete \u2014 nested path diagnostics report protocol shape only."
1396
+ ];
1397
+ let parsed;
1398
+ try {
1399
+ parsed = JSON.parse(content);
1400
+ } catch {
1401
+ pushPath(errors, "$", "OTLP JSON export is not valid JSON");
1402
+ return { ok: false, format: "otlp-json", errors, warnings };
1403
+ }
1404
+ if (!isRecord3(parsed)) {
1405
+ pushPath(errors, "$", "OTLP JSON export must be an object");
1406
+ return { ok: false, format: "otlp-json", errors, warnings };
1407
+ }
1408
+ if (!Array.isArray(parsed.resourceSpans)) {
1409
+ pushPath(errors, "$.resourceSpans", "must be an array");
1410
+ return { ok: false, format: "otlp-json", errors, warnings };
1411
+ }
1412
+ parsed.resourceSpans.forEach((resourceSpan, rsi) => {
1413
+ const rsPath = `$.resourceSpans[${rsi}]`;
1414
+ if (!isRecord3(resourceSpan)) {
1415
+ pushPath(errors, rsPath, "must be an object");
1416
+ return;
1417
+ }
1418
+ if (!Array.isArray(resourceSpan.scopeSpans)) {
1419
+ pushPath(errors, `${rsPath}.scopeSpans`, "must be an array");
1420
+ return;
1421
+ }
1422
+ resourceSpan.scopeSpans.forEach((scopeSpan, ssi) => {
1423
+ const ssPath = `${rsPath}.scopeSpans[${ssi}]`;
1424
+ if (!isRecord3(scopeSpan)) {
1425
+ pushPath(errors, ssPath, "must be an object");
1426
+ return;
1427
+ }
1428
+ if (!Array.isArray(scopeSpan.spans)) {
1429
+ pushPath(errors, `${ssPath}.spans`, "must be an array");
1430
+ return;
1431
+ }
1432
+ scopeSpan.spans.forEach((span, spi) => {
1433
+ const spanPath = `${ssPath}.spans[${spi}]`;
1434
+ if (!isRecord3(span)) {
1435
+ pushPath(errors, spanPath, "must be an object");
1436
+ return;
1437
+ }
1438
+ if (typeof span.traceId !== "string" || span.traceId.length === 0) {
1439
+ pushPath(errors, `${spanPath}.traceId`, "must be a non-empty string");
1440
+ }
1441
+ if (typeof span.spanId !== "string" || span.spanId.length === 0) {
1442
+ pushPath(errors, `${spanPath}.spanId`, "must be a non-empty string");
1443
+ }
1444
+ if (typeof span.name !== "string") {
1445
+ pushPath(errors, `${spanPath}.name`, "must be a string");
1446
+ }
1447
+ if (typeof span.startTimeUnixNano !== "string" || !/^\d+$/.test(span.startTimeUnixNano)) {
1448
+ pushPath(
1449
+ errors,
1450
+ `${spanPath}.startTimeUnixNano`,
1451
+ "must be a decimal-string 64-bit integer"
1452
+ );
1453
+ }
1454
+ if (span.endTimeUnixNano !== void 0 && (typeof span.endTimeUnixNano !== "string" || !/^\d+$/.test(span.endTimeUnixNano))) {
1455
+ pushPath(
1456
+ errors,
1457
+ `${spanPath}.endTimeUnixNano`,
1458
+ "must be a decimal-string 64-bit integer when present"
1459
+ );
1460
+ }
1461
+ if (typeof span.kind === "number") {
1462
+ if (!Number.isInteger(span.kind)) {
1463
+ pushPath(errors, `${spanPath}.kind`, "must be an integer SpanKind enum");
1464
+ }
1465
+ } else if (typeof span.kind === "string") {
1466
+ warnings.push(
1467
+ `${spanPath}.kind: string SpanKind enum is legacy; prefer numeric (INTERNAL=1)`
1468
+ );
1469
+ } else if (span.kind !== void 0) {
1470
+ pushPath(
1471
+ errors,
1472
+ `${spanPath}.kind`,
1473
+ "must be numeric SpanKind or historical string enum"
1474
+ );
1475
+ }
1476
+ if (!isRecord3(span.status)) {
1477
+ pushPath(errors, `${spanPath}.status`, "must be an object");
1478
+ } else {
1479
+ const code = span.status.code;
1480
+ if (typeof code === "number") {
1481
+ if (![0, 1, 2].includes(code)) {
1482
+ pushPath(
1483
+ errors,
1484
+ `${spanPath}.status.code`,
1485
+ "must be numeric StatusCode 0 (UNSET), 1 (OK), or 2 (ERROR)"
1486
+ );
1487
+ }
1488
+ } else if (typeof code === "string") {
1489
+ const upper = code.toUpperCase();
1490
+ if (upper !== "STATUS_CODE_UNSET" && upper !== "STATUS_CODE_OK" && upper !== "STATUS_CODE_ERROR" && upper !== "UNSET" && upper !== "OK" && upper !== "ERROR" && !/^[012]$/.test(code.trim())) {
1491
+ pushPath(
1492
+ errors,
1493
+ `${spanPath}.status.code`,
1494
+ "unrecognized StatusCode string"
1495
+ );
1496
+ } else {
1497
+ warnings.push(
1498
+ `${spanPath}.status.code: string StatusCode is legacy; prefer numeric 0/1/2`
1499
+ );
1500
+ }
1501
+ } else {
1502
+ pushPath(
1503
+ errors,
1504
+ `${spanPath}.status.code`,
1505
+ "must be numeric StatusCode 0/1/2 or historical string enum"
1506
+ );
1507
+ }
1508
+ }
1509
+ });
1510
+ });
1511
+ });
1512
+ return { ok: errors.length === 0, format: "otlp-json", errors, warnings };
1513
+ }
1330
1514
  function validateExportContent(format, content) {
1331
1515
  const errors = [];
1332
1516
  const warnings = [EXPERIMENTAL];
@@ -1372,25 +1556,7 @@ function validateExportContent(format, content) {
1372
1556
  return { ok: errors.length === 0, format, errors, warnings };
1373
1557
  }
1374
1558
  if (format === "otlp-json") {
1375
- let parsed;
1376
- try {
1377
- parsed = JSON.parse(content);
1378
- } catch {
1379
- errors.push("OTLP JSON export is not valid JSON");
1380
- return { ok: false, format, errors, warnings };
1381
- }
1382
- if (!parsed || typeof parsed !== "object") {
1383
- errors.push("OTLP JSON export must be an object");
1384
- return { ok: false, format, errors, warnings };
1385
- }
1386
- const o = parsed;
1387
- if (!Array.isArray(o.resourceSpans)) {
1388
- errors.push("OTLP JSON export must include resourceSpans array");
1389
- }
1390
- warnings.push(
1391
- "OTLP JSON mapping uses OTel GenAI-aligned attributes where applicable; collectors may require transformation."
1392
- );
1393
- return { ok: errors.length === 0, format, errors, warnings };
1559
+ return validateOtlpJson(content);
1394
1560
  }
1395
1561
  errors.push(`Unsupported export format`);
1396
1562
  return { ok: false, format, errors, warnings };
@@ -1891,6 +2057,8 @@ function formatStepLabel(stepType, stepName) {
1891
2057
  }
1892
2058
 
1893
2059
  // packages/core/src/timeline.ts
2060
+ var ROOT_TIMELINE_DEPTH = 0;
2061
+ var MAX_TIMELINE_DEPTH = 1e3;
1894
2062
  function finite(n) {
1895
2063
  return typeof n === "number" && Number.isFinite(n);
1896
2064
  }
@@ -1961,16 +2129,51 @@ function buildRunTimeline(events, options = {}) {
1961
2129
  const computeDepth = (stepId) => {
1962
2130
  const cached = depthCache.get(stepId);
1963
2131
  if (cached !== void 0) return cached;
1964
- const node = steps.get(stepId);
1965
- if (!node) return 0;
1966
- const parent = node.parentId;
1967
- if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
1968
- depthCache.set(stepId, 0);
1969
- return 0;
1970
- }
1971
- const d = Math.min(1e3, computeDepth(parent) + 1);
1972
- depthCache.set(stepId, d);
1973
- return d;
2132
+ const ancestry = [];
2133
+ const ancestryIndexes = /* @__PURE__ */ new Map();
2134
+ let currentStepId = stepId;
2135
+ let resolvedDepth;
2136
+ while (resolvedDepth === void 0) {
2137
+ const cachedDepth = depthCache.get(currentStepId);
2138
+ if (cachedDepth !== void 0) {
2139
+ resolvedDepth = cachedDepth;
2140
+ continue;
2141
+ }
2142
+ const cycleStart = ancestryIndexes.get(currentStepId);
2143
+ if (cycleStart !== void 0) {
2144
+ const cycleStepIds = ancestry.splice(cycleStart);
2145
+ for (const cycleStepId of cycleStepIds) {
2146
+ depthCache.set(cycleStepId, ROOT_TIMELINE_DEPTH);
2147
+ }
2148
+ resolvedDepth = ROOT_TIMELINE_DEPTH;
2149
+ continue;
2150
+ }
2151
+ const currentStep = steps.get(currentStepId);
2152
+ if (!currentStep) {
2153
+ resolvedDepth = ROOT_TIMELINE_DEPTH;
2154
+ continue;
2155
+ }
2156
+ ancestryIndexes.set(currentStepId, ancestry.length);
2157
+ ancestry.push(currentStepId);
2158
+ const parentId = currentStep.parentId;
2159
+ if (typeof parentId !== "string" || parentId.trim() === "" || !steps.has(parentId)) {
2160
+ ancestry.pop();
2161
+ depthCache.set(currentStepId, ROOT_TIMELINE_DEPTH);
2162
+ resolvedDepth = ROOT_TIMELINE_DEPTH;
2163
+ continue;
2164
+ }
2165
+ currentStepId = parentId;
2166
+ }
2167
+ ancestry.reverse();
2168
+ for (const ancestorStepId of ancestry) {
2169
+ resolvedDepth = Math.min(MAX_TIMELINE_DEPTH, resolvedDepth + 1);
2170
+ depthCache.set(ancestorStepId, resolvedDepth);
2171
+ }
2172
+ const resolvedStepDepth = depthCache.get(stepId);
2173
+ if (resolvedStepDepth === void 0) {
2174
+ throw new Error(`Failed to resolve timeline depth for step "${stepId}"`);
2175
+ }
2176
+ return resolvedStepDepth;
1974
2177
  };
1975
2178
  const entries = [];
1976
2179
  for (const [stepId, s] of steps.entries()) {