agentfootprint-lens 0.19.0 → 0.20.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.
@@ -3,7 +3,11 @@ import {
3
3
  } from "./chunk-KQOLJKKM.js";
4
4
 
5
5
  // src/core/LensRecorder.ts
6
+ import { isDevMode } from "footprintjs";
6
7
  import { SequenceStore } from "footprintjs/trace";
8
+ import {
9
+ ALL_EVENT_TYPES
10
+ } from "agentfootprint";
7
11
  import { LiveStateRecorder, BoundaryRecorder } from "agentfootprint/observe";
8
12
  import {
9
13
  createTraceRuntimeOverlay
@@ -302,8 +306,9 @@ function relTime(runStartMs) {
302
306
  }
303
307
 
304
308
  // src/core/LensRecorder.ts
309
+ var KNOWN_EVENT_TYPES = new Set(ALL_EVENT_TYPES);
305
310
  var LensRecorder = class {
306
- constructor(rootLabel = "Run") {
311
+ constructor(rootLabel = "Run", options = {}) {
307
312
  /** Stable id for idempotent attach. */
308
313
  this.id = "lens";
309
314
  /** Composition: ordered + keyed event-log storage. */
@@ -374,6 +379,15 @@ var LensRecorder = class {
374
379
  * notifier. See `ChangeNotifier` JSDoc for adapter examples.
375
380
  */
376
381
  this.notifier = new ChangeNotifier();
382
+ /** Per-type count of events outside the agentfootprint registry.
383
+ * Always maintained (debug only gates console output). */
384
+ this.unknownEventTypes = /* @__PURE__ */ new Map();
385
+ /** Count of `popIfKind` bracket mismatches. Always maintained. */
386
+ this.bracketMismatchCount = 0;
387
+ /** Unknown types already warned about — warn ONCE per type, not per
388
+ * event, so a chatty unknown emitter can't flood the console. */
389
+ this.warnedUnknownTypes = /* @__PURE__ */ new Set();
390
+ this.debug = options.debug;
377
391
  this.root = {
378
392
  id: "run-root",
379
393
  kind: "run",
@@ -400,11 +414,44 @@ var LensRecorder = class {
400
414
  this.finalStatus = "running";
401
415
  this.runError = void 0;
402
416
  this.lastRunId = void 0;
417
+ this.unknownEventTypes.clear();
418
+ this.bracketMismatchCount = 0;
419
+ this.warnedUnknownTypes.clear();
403
420
  this.liveState.clear();
404
421
  this.boundary.clear();
405
422
  this.runtime.reset();
406
423
  this.bumpVersion();
407
424
  }
425
+ /**
426
+ * Health counters for the observed event stream (backlog item U4).
427
+ * Always maintained — no debug flag needed — so UIs and tests can
428
+ * assert stream health without scraping the console:
429
+ *
430
+ * - `unknownEventTypes` — per-type counts of events whose `type` is
431
+ * not in agentfootprint's event registry (e.g. a newer
432
+ * agentfootprint emitting types this lens doesn't know, or a
433
+ * custom dispatcher leaking foreign events). These events are
434
+ * still attached to the current top node — counted, not dropped.
435
+ * - `bracketMismatches` — close events (`llm_end`, `tool_end`,
436
+ * `composition.exit`, ...) whose kind didn't match the top of the
437
+ * build stack (malformed ordering). The close is skipped; the
438
+ * tree stays partially structured rather than crashing.
439
+ *
440
+ * Both are `{}` / `0` on a well-formed run. Reset by `clear()`.
441
+ * Returns a fresh snapshot object on every call.
442
+ */
443
+ getDiagnostics() {
444
+ return {
445
+ unknownEventTypes: Object.fromEntries(this.unknownEventTypes),
446
+ bracketMismatches: this.bracketMismatchCount
447
+ };
448
+ }
449
+ /** Whether diagnostic warnings go to the console: explicit option
450
+ * wins; otherwise follow footprintjs's global dev-mode flag
451
+ * (evaluated per event so `enableDevMode()` mid-run takes effect). */
452
+ debugEnabled() {
453
+ return this.debug ?? isDevMode();
454
+ }
408
455
  /**
409
456
  * The StepGraph the UI renders — agentfootprint's ReAct projection
410
457
  * (actor-arrow steps with `iterationIndex` + `slotUpdated`, plus
@@ -555,6 +602,7 @@ var LensRecorder = class {
555
602
  };
556
603
  this.store.push(entry);
557
604
  this.top().events.push(entry);
605
+ this.noteUnknownType(event.type);
558
606
  this.dispatch(event, runOffsetMs, entry);
559
607
  this.bumpVersion();
560
608
  }
@@ -562,6 +610,21 @@ var LensRecorder = class {
562
610
  bumpVersion() {
563
611
  this.notifier.notify();
564
612
  }
613
+ /**
614
+ * U4 diagnostics — count (and, in debug, warn ONCE per type about)
615
+ * event types outside agentfootprint's registry. One Set lookup per
616
+ * event on the happy path.
617
+ */
618
+ noteUnknownType(type) {
619
+ if (KNOWN_EVENT_TYPES.has(type)) return;
620
+ this.unknownEventTypes.set(type, (this.unknownEventTypes.get(type) ?? 0) + 1);
621
+ if (this.debugEnabled() && !this.warnedUnknownTypes.has(type)) {
622
+ this.warnedUnknownTypes.add(type);
623
+ console.warn(
624
+ `[lens] LensRecorder: unknown event type '${type}' \u2014 not in agentfootprint's event registry. Attached to the current node without structural handling. (Warned once per type; counts in getDiagnostics().unknownEventTypes.)`
625
+ );
626
+ }
627
+ }
565
628
  /**
566
629
  * Kind-specific handling. Keeps the switch exhaustive over every v2
567
630
  * event type we structurally care about; the default branch is the
@@ -586,10 +649,14 @@ var LensRecorder = class {
586
649
  }
587
650
  if (type === "agentfootprint.composition.exit") {
588
651
  const p = event.payload;
589
- this.popIfKind("composition", {
590
- endOffsetMs: runOffsetMs,
591
- status: p.status === "ok" ? "ok" : p.status === "budget_exhausted" ? "budget_exhausted" : "err"
592
- });
652
+ this.popIfKind(
653
+ "composition",
654
+ {
655
+ endOffsetMs: runOffsetMs,
656
+ status: p.status === "ok" ? "ok" : p.status === "budget_exhausted" ? "budget_exhausted" : "err"
657
+ },
658
+ entry.runtimeStageId
659
+ );
593
660
  return;
594
661
  }
595
662
  if (type === "agentfootprint.composition.iteration_start") {
@@ -608,11 +675,15 @@ var LensRecorder = class {
608
675
  }
609
676
  if (type === "agentfootprint.composition.iteration_exit") {
610
677
  const p = event.payload;
611
- this.popIfKind("iteration", {
612
- endOffsetMs: runOffsetMs,
613
- status: p.reason === "budget" ? "budget_exhausted" : "ok",
614
- iterationExit: p.reason
615
- });
678
+ this.popIfKind(
679
+ "iteration",
680
+ {
681
+ endOffsetMs: runOffsetMs,
682
+ status: p.reason === "budget" ? "budget_exhausted" : "ok",
683
+ iterationExit: p.reason
684
+ },
685
+ entry.runtimeStageId
686
+ );
616
687
  return;
617
688
  }
618
689
  if (type === "agentfootprint.agent.turn_start") {
@@ -629,7 +700,7 @@ var LensRecorder = class {
629
700
  return;
630
701
  }
631
702
  if (type === "agentfootprint.agent.turn_end") {
632
- this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" });
703
+ this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" }, entry.runtimeStageId);
633
704
  return;
634
705
  }
635
706
  if (type === "agentfootprint.agent.iteration_start") {
@@ -647,7 +718,7 @@ var LensRecorder = class {
647
718
  return;
648
719
  }
649
720
  if (type === "agentfootprint.agent.iteration_end") {
650
- this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" });
721
+ this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" }, entry.runtimeStageId);
651
722
  return;
652
723
  }
653
724
  if (type === "agentfootprint.stream.llm_start") {
@@ -674,16 +745,20 @@ var LensRecorder = class {
674
745
  }
675
746
  if (type === "agentfootprint.stream.llm_end") {
676
747
  const p = event.payload;
677
- this.popIfKind("llm-call", {
678
- endOffsetMs: runOffsetMs,
679
- status: "ok",
680
- llmEnd: {
681
- content: p.content,
682
- toolCallCount: p.toolCallCount,
683
- usage: p.usage,
684
- stopReason: p.stopReason
685
- }
686
- });
748
+ this.popIfKind(
749
+ "llm-call",
750
+ {
751
+ endOffsetMs: runOffsetMs,
752
+ status: "ok",
753
+ llmEnd: {
754
+ content: p.content,
755
+ toolCallCount: p.toolCallCount,
756
+ usage: p.usage,
757
+ stopReason: p.stopReason
758
+ }
759
+ },
760
+ entry.runtimeStageId
761
+ );
687
762
  return;
688
763
  }
689
764
  if (type === "agentfootprint.stream.tool_start") {
@@ -708,11 +783,15 @@ var LensRecorder = class {
708
783
  }
709
784
  if (type === "agentfootprint.stream.tool_end") {
710
785
  const p = event.payload;
711
- this.popIfKind("tool-call", {
712
- endOffsetMs: runOffsetMs,
713
- status: p.error === true ? "err" : "ok",
714
- toolEnd: { result: p.result, error: p.error ?? false }
715
- });
786
+ this.popIfKind(
787
+ "tool-call",
788
+ {
789
+ endOffsetMs: runOffsetMs,
790
+ status: p.error === true ? "err" : "ok",
791
+ toolEnd: { result: p.result, error: p.error ?? false }
792
+ },
793
+ entry.runtimeStageId
794
+ );
716
795
  return;
717
796
  }
718
797
  if (type === "agentfootprint.pause.request") {
@@ -748,12 +827,23 @@ var LensRecorder = class {
748
827
  }
749
828
  /**
750
829
  * Pop the top node IF its kind matches, applying finalization fields.
751
- * Mismatched kinds (indicating malformed event ordering) are logged
752
- * but don't throw — Lens prefers partial correctness to crashes.
830
+ * Mismatched kinds (indicating malformed event ordering) are SKIPPED,
831
+ * never thrown — Lens prefers partial correctness to crashes. Every
832
+ * mismatch increments `getDiagnostics().bracketMismatches` (U4), and
833
+ * when debug is on (`LensRecorderOptions.debug` or footprintjs
834
+ * `isDevMode()`) each mismatch logs a `console.warn` with the
835
+ * expected vs found kind plus the closing event's `runtimeStageId`.
836
+ * Well-formed runs stay console-silent either way.
753
837
  */
754
- popIfKind(kind, finalize) {
838
+ popIfKind(kind, finalize, runtimeStageId) {
755
839
  const top = this.top();
756
840
  if (top.kind !== kind) {
841
+ this.bracketMismatchCount += 1;
842
+ if (this.debugEnabled()) {
843
+ console.warn(
844
+ `[lens] LensRecorder: bracket mismatch \u2014 tried to close a '${kind}' node but the top of the stack is '${top.kind}'` + (runtimeStageId !== void 0 ? ` (runtimeStageId: ${runtimeStageId})` : "") + `. Close event skipped; the tree stays partially structured.`
845
+ );
846
+ }
757
847
  return;
758
848
  }
759
849
  top.endOffsetMs = finalize.endOffsetMs;
@@ -909,8 +999,8 @@ function buildDetails(n) {
909
999
  }
910
1000
  return void 0;
911
1001
  }
912
- function lensRecorder(rootLabel) {
913
- return new LensRecorder(rootLabel);
1002
+ function lensRecorder(rootLabel, options) {
1003
+ return new LensRecorder(rootLabel, options);
914
1004
  }
915
1005
 
916
1006
  // src/core/buildStepGraphFromSnapshot.ts
@@ -1618,7 +1708,7 @@ function makeEdge(kind, source, target, options = {}) {
1618
1708
  }
1619
1709
 
1620
1710
  // src/core/translate/helpers/mergeOutputs.ts
1621
- import { isDevMode } from "footprintjs";
1711
+ import { isDevMode as isDevMode2 } from "footprintjs";
1622
1712
  function mergeOutputs(outputs, rootNodeId) {
1623
1713
  const nodes = [];
1624
1714
  const edges = [];
@@ -1626,7 +1716,7 @@ function mergeOutputs(outputs, rootNodeId) {
1626
1716
  for (const n of o.nodes) nodes.push(n);
1627
1717
  for (const e of o.edges) edges.push(e);
1628
1718
  }
1629
- if (isDevMode()) assertNoCollisions(nodes, edges);
1719
+ if (isDevMode2()) assertNoCollisions(nodes, edges);
1630
1720
  return { nodes, edges, rootNodeId };
1631
1721
  }
1632
1722
  function assertNoCollisions(nodes, edges) {
@@ -2295,4 +2385,4 @@ export {
2295
2385
  defaultSize,
2296
2386
  layoutLensGraph
2297
2387
  };
2298
- //# sourceMappingURL=chunk-A2ELAEZX.js.map
2388
+ //# sourceMappingURL=chunk-N3YJKWK5.js.map