agentfootprint-lens 0.20.0 → 0.23.5

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.
@@ -306,6 +306,7 @@ function relTime(runStartMs) {
306
306
  }
307
307
 
308
308
  // src/core/LensRecorder.ts
309
+ var DEFAULT_MAX_EVENTS = 5e4;
309
310
  var KNOWN_EVENT_TYPES = new Set(ALL_EVENT_TYPES);
310
311
  var LensRecorder = class {
311
312
  constructor(rootLabel = "Run", options = {}) {
@@ -387,7 +388,18 @@ var LensRecorder = class {
387
388
  /** Unknown types already warned about — warn ONCE per type, not per
388
389
  * event, so a chatty unknown emitter can't flood the console. */
389
390
  this.warnedUnknownTypes = /* @__PURE__ */ new Set();
391
+ /** Entries evicted by the cap so far. Surfaced via `getDiagnostics()`. */
392
+ this.droppedEventCount = 0;
393
+ /** Eviction already warned about — warn ONCE per run, not per batch. */
394
+ this.warnedEviction = false;
390
395
  this.debug = options.debug;
396
+ const cap = options.maxEvents ?? DEFAULT_MAX_EVENTS;
397
+ if (cap !== Number.POSITIVE_INFINITY && (!Number.isInteger(cap) || cap < 1)) {
398
+ throw new RangeError(
399
+ `LensRecorder: maxEvents must be a positive integer or Infinity, got ${cap}`
400
+ );
401
+ }
402
+ this.maxEvents = cap;
391
403
  this.root = {
392
404
  id: "run-root",
393
405
  kind: "run",
@@ -417,6 +429,8 @@ var LensRecorder = class {
417
429
  this.unknownEventTypes.clear();
418
430
  this.bracketMismatchCount = 0;
419
431
  this.warnedUnknownTypes.clear();
432
+ this.droppedEventCount = 0;
433
+ this.warnedEviction = false;
420
434
  this.liveState.clear();
421
435
  this.boundary.clear();
422
436
  this.runtime.reset();
@@ -436,14 +450,18 @@ var LensRecorder = class {
436
450
  * `composition.exit`, ...) whose kind didn't match the top of the
437
451
  * build stack (malformed ordering). The close is skipped; the
438
452
  * tree stays partially structured rather than crashing.
453
+ * - `droppedEvents` — entries evicted by the `maxEvents` FIFO cap
454
+ * (U3). When non-zero, log-derived views cover only the retained
455
+ * tail of the run.
439
456
  *
440
- * Both are `{}` / `0` on a well-formed run. Reset by `clear()`.
441
- * Returns a fresh snapshot object on every call.
457
+ * All are `{}` / `0` on a well-formed run that stayed under the cap.
458
+ * Reset by `clear()`. Returns a fresh snapshot object on every call.
442
459
  */
443
460
  getDiagnostics() {
444
461
  return {
445
462
  unknownEventTypes: Object.fromEntries(this.unknownEventTypes),
446
- bracketMismatches: this.bracketMismatchCount
463
+ bracketMismatches: this.bracketMismatchCount,
464
+ droppedEvents: this.droppedEventCount
447
465
  };
448
466
  }
449
467
  /** Whether diagnostic warnings go to the console: explicit option
@@ -604,8 +622,52 @@ var LensRecorder = class {
604
622
  this.top().events.push(entry);
605
623
  this.noteUnknownType(event.type);
606
624
  this.dispatch(event, runOffsetMs, entry);
625
+ this.enforceCap();
607
626
  this.bumpVersion();
608
627
  }
628
+ /**
629
+ * U3 — enforce the `maxEvents` FIFO cap. When the store exceeds the
630
+ * cap, evict the oldest entries down to ~90% of the cap in ONE batch
631
+ * (amortized O(1) per event: one O(retained) rebuild per ~10%-of-cap
632
+ * pushes), then prune the SAME evicted entries from every run-tree
633
+ * node's `events` list — entry objects are shared references, so
634
+ * skipping the tree would hide the memory, not release it.
635
+ *
636
+ * `SequenceStore` is append-only by design (no removal API), so the
637
+ * batch rebuild (clear + re-push retained) is the supported eviction
638
+ * path; the per-step key + range indices rebuild correctly during
639
+ * re-push.
640
+ */
641
+ enforceCap() {
642
+ if (this.store.size <= this.maxEvents) return;
643
+ const all = this.store.getAll();
644
+ const evictBatch = Math.max(1, Math.floor(this.maxEvents / 10));
645
+ const retainCount = Math.max(1, this.maxEvents - evictBatch);
646
+ const dropCount = all.length - retainCount;
647
+ const retained = all.slice(dropCount);
648
+ this.store.clear();
649
+ for (const e of retained) this.store.push(e);
650
+ this.droppedEventCount += dropCount;
651
+ this.pruneNodeEvents(this.root, retained[0].seq);
652
+ if (this.debugEnabled() && !this.warnedEviction) {
653
+ this.warnedEviction = true;
654
+ console.warn(
655
+ `[lens] LensRecorder: maxEvents cap (${this.maxEvents}) reached \u2014 evicting oldest events (FIFO, ~10% per batch). Log-derived views now cover only the retained tail; evicted total in getDiagnostics().droppedEvents. Raise the cap via lensRecorder('Run', { maxEvents }) or pass Infinity to disable. (Warned once.)`
656
+ );
657
+ }
658
+ }
659
+ /** Drop entries with `seq < minSeq` from a node's `events` list (and
660
+ * its descendants'). Per-node lists are seq-ordered, so this is a
661
+ * prefix splice — in place, preserving the node object identity the
662
+ * build stack may still hold. */
663
+ pruneNodeEvents(node, minSeq) {
664
+ if (node.events.length > 0 && node.events[0].seq < minSeq) {
665
+ let keepFrom = 0;
666
+ while (keepFrom < node.events.length && node.events[keepFrom].seq < minSeq) keepFrom++;
667
+ node.events.splice(0, keepFrom);
668
+ }
669
+ for (const child of node.children) this.pruneNodeEvents(child, minSeq);
670
+ }
609
671
  /** Notify all subscribers + bump version. Delegated to ChangeNotifier. */
610
672
  bumpVersion() {
611
673
  this.notifier.notify();
@@ -904,7 +966,13 @@ var LensRecorder = class {
904
966
  }
905
967
  /** Summary stats — computed lazily via `store.aggregate()`.
906
968
  * Single-pass fold; types derived from the AgentfootprintEvent
907
- * discriminated union. */
969
+ * discriminated union.
970
+ *
971
+ * U3 caveat: once the `maxEvents` cap has evicted entries
972
+ * (`getDiagnostics().droppedEvents > 0`), the folded counts/tokens
973
+ * reflect only RETAINED events. `startedAt` / `durationMs` stay
974
+ * anchored to the true first event of the run (tracked outside the
975
+ * store), so the time axis never shifts. */
908
976
  selectSummary() {
909
977
  const init = {
910
978
  llmCallCount: 0,
@@ -944,7 +1012,7 @@ var LensRecorder = class {
944
1012
  }
945
1013
  }, init);
946
1014
  const entries = this.store.getAll();
947
- const startedAt = entries[0]?.wallClockMs ?? 0;
1015
+ const startedAt = this.runStartMs ?? entries[0]?.wallClockMs ?? 0;
948
1016
  const endedAt = entries[entries.length - 1]?.wallClockMs;
949
1017
  return {
950
1018
  startedAt,
@@ -1658,6 +1726,47 @@ function selectCommentaryRanges(boundary) {
1658
1726
  }));
1659
1727
  }
1660
1728
 
1729
+ // src/core/selectors/selectToolChoiceCall.ts
1730
+ function execIndex(runtimeStageId) {
1731
+ const hash = runtimeStageId.lastIndexOf("#");
1732
+ if (hash < 0) return void 0;
1733
+ const n = Number(runtimeStageId.slice(hash + 1));
1734
+ return Number.isInteger(n) && n >= 0 ? n : void 0;
1735
+ }
1736
+ function selectToolChoiceCall(calls, cursorRuntimeStageId, cursorKind) {
1737
+ if (calls.length === 0) return void 0;
1738
+ const last = calls[calls.length - 1];
1739
+ if (cursorKind === "user-in") return void 0;
1740
+ if (cursorKind === "user-out") return last;
1741
+ if (!cursorRuntimeStageId) return last;
1742
+ const base = cursorRuntimeStageId.split("#")[0];
1743
+ if (base === "__root__") {
1744
+ return cursorKind === "group-start" ? void 0 : last;
1745
+ }
1746
+ const exact = calls.find((c) => c.runtimeStageId === cursorRuntimeStageId);
1747
+ if (exact) return exact;
1748
+ const cursorIdx = execIndex(cursorRuntimeStageId);
1749
+ if (cursorIdx === void 0) return void 0;
1750
+ const prefix = `${base}/`;
1751
+ let within;
1752
+ let withinIdx = Infinity;
1753
+ let prev;
1754
+ let prevIdx = -1;
1755
+ for (const c of calls) {
1756
+ const idx = execIndex(c.runtimeStageId);
1757
+ if (idx === void 0) continue;
1758
+ if (c.runtimeStageId.startsWith(prefix) && idx > cursorIdx && idx < withinIdx) {
1759
+ within = c;
1760
+ withinIdx = idx;
1761
+ }
1762
+ if (idx <= cursorIdx && idx > prevIdx) {
1763
+ prev = c;
1764
+ prevIdx = idx;
1765
+ }
1766
+ }
1767
+ return within ?? prev;
1768
+ }
1769
+
1661
1770
  // src/core/translate/helpers/makeNodeId.ts
1662
1771
  function makeRootNodeId(kind, id) {
1663
1772
  return `${kind.toLowerCase()}:${id}`;
@@ -2000,7 +2109,8 @@ import { walkSubflowSpec, splitStageId } from "footprintjs/trace";
2000
2109
  import { createTraceStructureRecorder } from "footprint-explainable-ui/flowchart";
2001
2110
  import { stageRole } from "agentfootprint";
2002
2111
  function emphasisForRole(role) {
2003
- if (role === "hero-slot" || role === "hero-llm" || role === "hero-action") return "hero";
2112
+ if (role === "hero-slot" || role === "hero-llm" || role === "hero-action")
2113
+ return "hero";
2004
2114
  if (role === "plumbing") return "muted";
2005
2115
  return void 0;
2006
2116
  }
@@ -2026,9 +2136,12 @@ function slotKindForLocalId(localId) {
2026
2136
  return void 0;
2027
2137
  }
2028
2138
  function structureGraphFromRunner(runner) {
2139
+ return structureGraphFromSpec(runner.getSpec().buildTimeStructure);
2140
+ }
2141
+ function structureGraphFromSpec(buildTimeStructure) {
2029
2142
  const trace = createTraceStructureRecorder();
2030
2143
  const recorder = trace.recorder;
2031
- const spec = runner.getSpec().buildTimeStructure;
2144
+ const spec = buildTimeStructure;
2032
2145
  const subflowSpecs = [];
2033
2146
  for (const item of walkSubflowSpec(spec, "", { recurse: false })) {
2034
2147
  switch (item.kind) {
@@ -2076,8 +2189,14 @@ function structureGraphFromRunner(runner) {
2076
2189
  const internal = expandSubflowInternals(subflowSpecs);
2077
2190
  const seenNodes = new Set(baseGraph.nodes.map((n) => n.id));
2078
2191
  const seenEdges = new Set(baseGraph.edges.map((e) => e.id));
2079
- const nodes = [...baseGraph.nodes, ...internal.nodes.filter((n) => !seenNodes.has(n.id))];
2080
- const edges = [...baseGraph.edges, ...internal.edges.filter((e) => !seenEdges.has(e.id))];
2192
+ const nodes = [
2193
+ ...baseGraph.nodes,
2194
+ ...internal.nodes.filter((n) => !seenNodes.has(n.id))
2195
+ ];
2196
+ const edges = [
2197
+ ...baseGraph.edges,
2198
+ ...internal.edges.filter((e) => !seenEdges.has(e.id))
2199
+ ];
2081
2200
  for (const node of nodes) {
2082
2201
  const role = stageRole(node.id);
2083
2202
  const data = node.data;
@@ -2111,7 +2230,9 @@ function expandSubflowInternals(subflows) {
2111
2230
  stageId: item.stageId,
2112
2231
  name: item.name,
2113
2232
  type: item.type,
2114
- ...item.isPausable !== void 0 && { isPausable: item.isPausable },
2233
+ ...item.isPausable !== void 0 && {
2234
+ isPausable: item.isPausable
2235
+ },
2115
2236
  spec: item.spec
2116
2237
  });
2117
2238
  break;
@@ -2143,10 +2264,19 @@ function expandSubflowInternals(subflows) {
2143
2264
  const prefix = path.endsWith("/") ? path : `${path}/`;
2144
2265
  const q = (id) => id.startsWith(prefix) ? id : `${prefix}${id}`;
2145
2266
  for (const n of sub.nodes) {
2146
- nodes.push({ ...n, id: q(n.id), data: { ...n.data, subflowOf: subflowId } });
2267
+ nodes.push({
2268
+ ...n,
2269
+ id: q(n.id),
2270
+ data: { ...n.data, subflowOf: subflowId }
2271
+ });
2147
2272
  }
2148
2273
  for (const e of sub.edges) {
2149
- edges.push({ ...e, id: `${q(e.source)}->${q(e.target)}`, source: q(e.source), target: q(e.target) });
2274
+ edges.push({
2275
+ ...e,
2276
+ id: `${q(e.source)}->${q(e.target)}`,
2277
+ source: q(e.source),
2278
+ target: q(e.target)
2279
+ });
2150
2280
  }
2151
2281
  }
2152
2282
  return { nodes, edges };
@@ -2348,6 +2478,7 @@ export {
2348
2478
  ChangeNotifier,
2349
2479
  lensSnapshotRecorder,
2350
2480
  LensSnapshotRecorder,
2481
+ DEFAULT_MAX_EVENTS,
2351
2482
  LensRecorder,
2352
2483
  lensRecorder,
2353
2484
  buildStepGraphFromSnapshot,
@@ -2368,6 +2499,7 @@ export {
2368
2499
  selectContextEngineeringInjections,
2369
2500
  selectCommentaryAt,
2370
2501
  selectCommentaryRanges,
2502
+ selectToolChoiceCall,
2371
2503
  makeRootNodeId,
2372
2504
  makeChildNodeId,
2373
2505
  translateAgent,
@@ -2381,8 +2513,9 @@ export {
2381
2513
  translateSequence,
2382
2514
  lensGroupTranslator,
2383
2515
  structureGraphFromRunner,
2516
+ structureGraphFromSpec,
2384
2517
  toReactFlow,
2385
2518
  defaultSize,
2386
2519
  layoutLensGraph
2387
2520
  };
2388
- //# sourceMappingURL=chunk-N3YJKWK5.js.map
2521
+ //# sourceMappingURL=chunk-ZJKD43L7.js.map