agentfootprint-lens 0.19.0 → 0.21.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/dist/{chunk-A2ELAEZX.js → chunk-BTVAAE66.js} +196 -37
- package/dist/chunk-BTVAAE66.js.map +1 -0
- package/dist/core.cjs +201 -43
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +151 -6
- package/dist/core.d.ts +151 -6
- package/dist/core.js +3 -1
- package/dist/index.cjs +531 -196
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -5
- package/dist/index.d.ts +111 -5
- package/dist/index.js +312 -134
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-A2ELAEZX.js.map +0 -1
|
@@ -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,10 @@ function relTime(runStartMs) {
|
|
|
302
306
|
}
|
|
303
307
|
|
|
304
308
|
// src/core/LensRecorder.ts
|
|
309
|
+
var DEFAULT_MAX_EVENTS = 5e4;
|
|
310
|
+
var KNOWN_EVENT_TYPES = new Set(ALL_EVENT_TYPES);
|
|
305
311
|
var LensRecorder = class {
|
|
306
|
-
constructor(rootLabel = "Run") {
|
|
312
|
+
constructor(rootLabel = "Run", options = {}) {
|
|
307
313
|
/** Stable id for idempotent attach. */
|
|
308
314
|
this.id = "lens";
|
|
309
315
|
/** Composition: ordered + keyed event-log storage. */
|
|
@@ -374,6 +380,26 @@ var LensRecorder = class {
|
|
|
374
380
|
* notifier. See `ChangeNotifier` JSDoc for adapter examples.
|
|
375
381
|
*/
|
|
376
382
|
this.notifier = new ChangeNotifier();
|
|
383
|
+
/** Per-type count of events outside the agentfootprint registry.
|
|
384
|
+
* Always maintained (debug only gates console output). */
|
|
385
|
+
this.unknownEventTypes = /* @__PURE__ */ new Map();
|
|
386
|
+
/** Count of `popIfKind` bracket mismatches. Always maintained. */
|
|
387
|
+
this.bracketMismatchCount = 0;
|
|
388
|
+
/** Unknown types already warned about — warn ONCE per type, not per
|
|
389
|
+
* event, so a chatty unknown emitter can't flood the console. */
|
|
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;
|
|
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;
|
|
377
403
|
this.root = {
|
|
378
404
|
id: "run-root",
|
|
379
405
|
kind: "run",
|
|
@@ -400,11 +426,50 @@ var LensRecorder = class {
|
|
|
400
426
|
this.finalStatus = "running";
|
|
401
427
|
this.runError = void 0;
|
|
402
428
|
this.lastRunId = void 0;
|
|
429
|
+
this.unknownEventTypes.clear();
|
|
430
|
+
this.bracketMismatchCount = 0;
|
|
431
|
+
this.warnedUnknownTypes.clear();
|
|
432
|
+
this.droppedEventCount = 0;
|
|
433
|
+
this.warnedEviction = false;
|
|
403
434
|
this.liveState.clear();
|
|
404
435
|
this.boundary.clear();
|
|
405
436
|
this.runtime.reset();
|
|
406
437
|
this.bumpVersion();
|
|
407
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Health counters for the observed event stream (backlog item U4).
|
|
441
|
+
* Always maintained — no debug flag needed — so UIs and tests can
|
|
442
|
+
* assert stream health without scraping the console:
|
|
443
|
+
*
|
|
444
|
+
* - `unknownEventTypes` — per-type counts of events whose `type` is
|
|
445
|
+
* not in agentfootprint's event registry (e.g. a newer
|
|
446
|
+
* agentfootprint emitting types this lens doesn't know, or a
|
|
447
|
+
* custom dispatcher leaking foreign events). These events are
|
|
448
|
+
* still attached to the current top node — counted, not dropped.
|
|
449
|
+
* - `bracketMismatches` — close events (`llm_end`, `tool_end`,
|
|
450
|
+
* `composition.exit`, ...) whose kind didn't match the top of the
|
|
451
|
+
* build stack (malformed ordering). The close is skipped; the
|
|
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.
|
|
456
|
+
*
|
|
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.
|
|
459
|
+
*/
|
|
460
|
+
getDiagnostics() {
|
|
461
|
+
return {
|
|
462
|
+
unknownEventTypes: Object.fromEntries(this.unknownEventTypes),
|
|
463
|
+
bracketMismatches: this.bracketMismatchCount,
|
|
464
|
+
droppedEvents: this.droppedEventCount
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
/** Whether diagnostic warnings go to the console: explicit option
|
|
468
|
+
* wins; otherwise follow footprintjs's global dev-mode flag
|
|
469
|
+
* (evaluated per event so `enableDevMode()` mid-run takes effect). */
|
|
470
|
+
debugEnabled() {
|
|
471
|
+
return this.debug ?? isDevMode();
|
|
472
|
+
}
|
|
408
473
|
/**
|
|
409
474
|
* The StepGraph the UI renders — agentfootprint's ReAct projection
|
|
410
475
|
* (actor-arrow steps with `iterationIndex` + `slotUpdated`, plus
|
|
@@ -555,13 +620,73 @@ var LensRecorder = class {
|
|
|
555
620
|
};
|
|
556
621
|
this.store.push(entry);
|
|
557
622
|
this.top().events.push(entry);
|
|
623
|
+
this.noteUnknownType(event.type);
|
|
558
624
|
this.dispatch(event, runOffsetMs, entry);
|
|
625
|
+
this.enforceCap();
|
|
559
626
|
this.bumpVersion();
|
|
560
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
|
+
}
|
|
561
671
|
/** Notify all subscribers + bump version. Delegated to ChangeNotifier. */
|
|
562
672
|
bumpVersion() {
|
|
563
673
|
this.notifier.notify();
|
|
564
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* U4 diagnostics — count (and, in debug, warn ONCE per type about)
|
|
677
|
+
* event types outside agentfootprint's registry. One Set lookup per
|
|
678
|
+
* event on the happy path.
|
|
679
|
+
*/
|
|
680
|
+
noteUnknownType(type) {
|
|
681
|
+
if (KNOWN_EVENT_TYPES.has(type)) return;
|
|
682
|
+
this.unknownEventTypes.set(type, (this.unknownEventTypes.get(type) ?? 0) + 1);
|
|
683
|
+
if (this.debugEnabled() && !this.warnedUnknownTypes.has(type)) {
|
|
684
|
+
this.warnedUnknownTypes.add(type);
|
|
685
|
+
console.warn(
|
|
686
|
+
`[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.)`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
565
690
|
/**
|
|
566
691
|
* Kind-specific handling. Keeps the switch exhaustive over every v2
|
|
567
692
|
* event type we structurally care about; the default branch is the
|
|
@@ -586,10 +711,14 @@ var LensRecorder = class {
|
|
|
586
711
|
}
|
|
587
712
|
if (type === "agentfootprint.composition.exit") {
|
|
588
713
|
const p = event.payload;
|
|
589
|
-
this.popIfKind(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
714
|
+
this.popIfKind(
|
|
715
|
+
"composition",
|
|
716
|
+
{
|
|
717
|
+
endOffsetMs: runOffsetMs,
|
|
718
|
+
status: p.status === "ok" ? "ok" : p.status === "budget_exhausted" ? "budget_exhausted" : "err"
|
|
719
|
+
},
|
|
720
|
+
entry.runtimeStageId
|
|
721
|
+
);
|
|
593
722
|
return;
|
|
594
723
|
}
|
|
595
724
|
if (type === "agentfootprint.composition.iteration_start") {
|
|
@@ -608,11 +737,15 @@ var LensRecorder = class {
|
|
|
608
737
|
}
|
|
609
738
|
if (type === "agentfootprint.composition.iteration_exit") {
|
|
610
739
|
const p = event.payload;
|
|
611
|
-
this.popIfKind(
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
740
|
+
this.popIfKind(
|
|
741
|
+
"iteration",
|
|
742
|
+
{
|
|
743
|
+
endOffsetMs: runOffsetMs,
|
|
744
|
+
status: p.reason === "budget" ? "budget_exhausted" : "ok",
|
|
745
|
+
iterationExit: p.reason
|
|
746
|
+
},
|
|
747
|
+
entry.runtimeStageId
|
|
748
|
+
);
|
|
616
749
|
return;
|
|
617
750
|
}
|
|
618
751
|
if (type === "agentfootprint.agent.turn_start") {
|
|
@@ -629,7 +762,7 @@ var LensRecorder = class {
|
|
|
629
762
|
return;
|
|
630
763
|
}
|
|
631
764
|
if (type === "agentfootprint.agent.turn_end") {
|
|
632
|
-
this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" });
|
|
765
|
+
this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" }, entry.runtimeStageId);
|
|
633
766
|
return;
|
|
634
767
|
}
|
|
635
768
|
if (type === "agentfootprint.agent.iteration_start") {
|
|
@@ -647,7 +780,7 @@ var LensRecorder = class {
|
|
|
647
780
|
return;
|
|
648
781
|
}
|
|
649
782
|
if (type === "agentfootprint.agent.iteration_end") {
|
|
650
|
-
this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" });
|
|
783
|
+
this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" }, entry.runtimeStageId);
|
|
651
784
|
return;
|
|
652
785
|
}
|
|
653
786
|
if (type === "agentfootprint.stream.llm_start") {
|
|
@@ -674,16 +807,20 @@ var LensRecorder = class {
|
|
|
674
807
|
}
|
|
675
808
|
if (type === "agentfootprint.stream.llm_end") {
|
|
676
809
|
const p = event.payload;
|
|
677
|
-
this.popIfKind(
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
810
|
+
this.popIfKind(
|
|
811
|
+
"llm-call",
|
|
812
|
+
{
|
|
813
|
+
endOffsetMs: runOffsetMs,
|
|
814
|
+
status: "ok",
|
|
815
|
+
llmEnd: {
|
|
816
|
+
content: p.content,
|
|
817
|
+
toolCallCount: p.toolCallCount,
|
|
818
|
+
usage: p.usage,
|
|
819
|
+
stopReason: p.stopReason
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
entry.runtimeStageId
|
|
823
|
+
);
|
|
687
824
|
return;
|
|
688
825
|
}
|
|
689
826
|
if (type === "agentfootprint.stream.tool_start") {
|
|
@@ -708,11 +845,15 @@ var LensRecorder = class {
|
|
|
708
845
|
}
|
|
709
846
|
if (type === "agentfootprint.stream.tool_end") {
|
|
710
847
|
const p = event.payload;
|
|
711
|
-
this.popIfKind(
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
848
|
+
this.popIfKind(
|
|
849
|
+
"tool-call",
|
|
850
|
+
{
|
|
851
|
+
endOffsetMs: runOffsetMs,
|
|
852
|
+
status: p.error === true ? "err" : "ok",
|
|
853
|
+
toolEnd: { result: p.result, error: p.error ?? false }
|
|
854
|
+
},
|
|
855
|
+
entry.runtimeStageId
|
|
856
|
+
);
|
|
716
857
|
return;
|
|
717
858
|
}
|
|
718
859
|
if (type === "agentfootprint.pause.request") {
|
|
@@ -748,12 +889,23 @@ var LensRecorder = class {
|
|
|
748
889
|
}
|
|
749
890
|
/**
|
|
750
891
|
* Pop the top node IF its kind matches, applying finalization fields.
|
|
751
|
-
* Mismatched kinds (indicating malformed event ordering) are
|
|
752
|
-
*
|
|
892
|
+
* Mismatched kinds (indicating malformed event ordering) are SKIPPED,
|
|
893
|
+
* never thrown — Lens prefers partial correctness to crashes. Every
|
|
894
|
+
* mismatch increments `getDiagnostics().bracketMismatches` (U4), and
|
|
895
|
+
* when debug is on (`LensRecorderOptions.debug` or footprintjs
|
|
896
|
+
* `isDevMode()`) each mismatch logs a `console.warn` with the
|
|
897
|
+
* expected vs found kind plus the closing event's `runtimeStageId`.
|
|
898
|
+
* Well-formed runs stay console-silent either way.
|
|
753
899
|
*/
|
|
754
|
-
popIfKind(kind, finalize) {
|
|
900
|
+
popIfKind(kind, finalize, runtimeStageId) {
|
|
755
901
|
const top = this.top();
|
|
756
902
|
if (top.kind !== kind) {
|
|
903
|
+
this.bracketMismatchCount += 1;
|
|
904
|
+
if (this.debugEnabled()) {
|
|
905
|
+
console.warn(
|
|
906
|
+
`[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.`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
757
909
|
return;
|
|
758
910
|
}
|
|
759
911
|
top.endOffsetMs = finalize.endOffsetMs;
|
|
@@ -814,7 +966,13 @@ var LensRecorder = class {
|
|
|
814
966
|
}
|
|
815
967
|
/** Summary stats — computed lazily via `store.aggregate()`.
|
|
816
968
|
* Single-pass fold; types derived from the AgentfootprintEvent
|
|
817
|
-
* 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. */
|
|
818
976
|
selectSummary() {
|
|
819
977
|
const init = {
|
|
820
978
|
llmCallCount: 0,
|
|
@@ -854,7 +1012,7 @@ var LensRecorder = class {
|
|
|
854
1012
|
}
|
|
855
1013
|
}, init);
|
|
856
1014
|
const entries = this.store.getAll();
|
|
857
|
-
const startedAt = entries[0]?.wallClockMs ?? 0;
|
|
1015
|
+
const startedAt = this.runStartMs ?? entries[0]?.wallClockMs ?? 0;
|
|
858
1016
|
const endedAt = entries[entries.length - 1]?.wallClockMs;
|
|
859
1017
|
return {
|
|
860
1018
|
startedAt,
|
|
@@ -909,8 +1067,8 @@ function buildDetails(n) {
|
|
|
909
1067
|
}
|
|
910
1068
|
return void 0;
|
|
911
1069
|
}
|
|
912
|
-
function lensRecorder(rootLabel) {
|
|
913
|
-
return new LensRecorder(rootLabel);
|
|
1070
|
+
function lensRecorder(rootLabel, options) {
|
|
1071
|
+
return new LensRecorder(rootLabel, options);
|
|
914
1072
|
}
|
|
915
1073
|
|
|
916
1074
|
// src/core/buildStepGraphFromSnapshot.ts
|
|
@@ -1618,7 +1776,7 @@ function makeEdge(kind, source, target, options = {}) {
|
|
|
1618
1776
|
}
|
|
1619
1777
|
|
|
1620
1778
|
// src/core/translate/helpers/mergeOutputs.ts
|
|
1621
|
-
import { isDevMode } from "footprintjs";
|
|
1779
|
+
import { isDevMode as isDevMode2 } from "footprintjs";
|
|
1622
1780
|
function mergeOutputs(outputs, rootNodeId) {
|
|
1623
1781
|
const nodes = [];
|
|
1624
1782
|
const edges = [];
|
|
@@ -1626,7 +1784,7 @@ function mergeOutputs(outputs, rootNodeId) {
|
|
|
1626
1784
|
for (const n of o.nodes) nodes.push(n);
|
|
1627
1785
|
for (const e of o.edges) edges.push(e);
|
|
1628
1786
|
}
|
|
1629
|
-
if (
|
|
1787
|
+
if (isDevMode2()) assertNoCollisions(nodes, edges);
|
|
1630
1788
|
return { nodes, edges, rootNodeId };
|
|
1631
1789
|
}
|
|
1632
1790
|
function assertNoCollisions(nodes, edges) {
|
|
@@ -2258,6 +2416,7 @@ export {
|
|
|
2258
2416
|
ChangeNotifier,
|
|
2259
2417
|
lensSnapshotRecorder,
|
|
2260
2418
|
LensSnapshotRecorder,
|
|
2419
|
+
DEFAULT_MAX_EVENTS,
|
|
2261
2420
|
LensRecorder,
|
|
2262
2421
|
lensRecorder,
|
|
2263
2422
|
buildStepGraphFromSnapshot,
|
|
@@ -2295,4 +2454,4 @@ export {
|
|
|
2295
2454
|
defaultSize,
|
|
2296
2455
|
layoutLensGraph
|
|
2297
2456
|
};
|
|
2298
|
-
//# sourceMappingURL=chunk-
|
|
2457
|
+
//# sourceMappingURL=chunk-BTVAAE66.js.map
|