footprintjs 6.1.2 → 7.0.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.
Files changed (45) hide show
  1. package/CLAUDE.md +31 -26
  2. package/README.md +1 -1
  3. package/dist/advanced.js +2 -6
  4. package/dist/esm/advanced.js +1 -3
  5. package/dist/esm/lib/builder/structure/StructureRecorder.js +2 -2
  6. package/dist/esm/lib/engine/narrative/CombinedNarrativeRecorder.js +59 -24
  7. package/dist/esm/lib/recorder/InOutRecorder.js +31 -15
  8. package/dist/esm/lib/recorder/QualityRecorder.js +40 -13
  9. package/dist/esm/lib/recorder/RecorderOperation.js +3 -3
  10. package/dist/esm/lib/recorder/index.js +1 -8
  11. package/dist/esm/lib/recorder/qualityTrace.js +1 -1
  12. package/dist/esm/lib/runner/ExecutionRuntime.js +1 -1
  13. package/dist/esm/lib/scope/recorders/MetricRecorder.js +37 -9
  14. package/dist/esm/trace.js +1 -12
  15. package/dist/lib/builder/structure/StructureRecorder.js +2 -2
  16. package/dist/lib/engine/narrative/CombinedNarrativeRecorder.js +59 -24
  17. package/dist/lib/recorder/InOutRecorder.js +31 -15
  18. package/dist/lib/recorder/QualityRecorder.js +40 -13
  19. package/dist/lib/recorder/RecorderOperation.js +3 -3
  20. package/dist/lib/recorder/index.js +2 -12
  21. package/dist/lib/recorder/qualityTrace.js +1 -1
  22. package/dist/lib/runner/ExecutionRuntime.js +1 -1
  23. package/dist/lib/scope/recorders/MetricRecorder.js +37 -9
  24. package/dist/trace.js +2 -16
  25. package/dist/types/advanced.d.ts +0 -2
  26. package/dist/types/lib/builder/structure/StructureRecorder.d.ts +1 -1
  27. package/dist/types/lib/engine/narrative/CombinedNarrativeRecorder.d.ts +26 -5
  28. package/dist/types/lib/recorder/InOutRecorder.d.ts +17 -6
  29. package/dist/types/lib/recorder/QualityRecorder.d.ts +17 -3
  30. package/dist/types/lib/recorder/RecorderOperation.d.ts +2 -2
  31. package/dist/types/lib/recorder/index.d.ts +0 -3
  32. package/dist/types/lib/recorder/qualityTrace.d.ts +5 -2
  33. package/dist/types/lib/runner/ExecutionRuntime.d.ts +1 -1
  34. package/dist/types/lib/scope/recorders/MetricRecorder.d.ts +18 -3
  35. package/dist/types/trace.d.ts +0 -3
  36. package/package.json +1 -1
  37. package/dist/esm/lib/recorder/BoundaryStateTracker.js +0 -260
  38. package/dist/esm/lib/recorder/KeyedRecorder.js +0 -94
  39. package/dist/esm/lib/recorder/SequenceRecorder.js +0 -193
  40. package/dist/lib/recorder/BoundaryStateTracker.js +0 -264
  41. package/dist/lib/recorder/KeyedRecorder.js +0 -98
  42. package/dist/lib/recorder/SequenceRecorder.js +0 -197
  43. package/dist/types/lib/recorder/BoundaryStateTracker.d.ts +0 -212
  44. package/dist/types/lib/recorder/KeyedRecorder.d.ts +0 -56
  45. package/dist/types/lib/recorder/SequenceRecorder.d.ts +0 -112
@@ -1,212 +0,0 @@
1
- /**
2
- * BoundaryStateTracker<TState> — third storage primitive on the recorder
3
- * shelf, alongside `SequenceRecorder<T>` and `KeyedRecorder<T>`.
4
- *
5
- * **Mental model — observers vs. bookkeepers:**
6
- *
7
- * `ScopeRecorder` / `FlowRecorder` / `EmitRecorder` / `CombinedRecorder`
8
- * are OBSERVER interfaces — they describe how a recorder hears
9
- * events from the executor.
10
- *
11
- * `SequenceRecorder<T>` / `KeyedRecorder<T>` / `BoundaryStateTracker<TState>`
12
- * are STORAGE primitives — three different bookkeeping shelves with
13
- * different durability and indexing properties. A real recorder
14
- * class typically picks ONE observer interface AND ONE storage
15
- * shelf, combining them via `extends + implements`.
16
- *
17
- * **What this primitive answers:** "At any moment during the run, what
18
- * is the LIVE transient state of every currently-active boundary?"
19
- *
20
- * A "boundary" is a matched event pair `[start, stop]` bracketing an
21
- * interval — for example, `(llm_start, llm_end)` for an LLM call,
22
- * `(tool_start, tool_end)` for tool execution, `(turn_start, turn_end)`
23
- * for an agent turn. Between the brackets, intermediate events evolve
24
- * the boundary's state (token chunks accumulating into a partial
25
- * answer, args streaming in, etc.). On `stop`, the state clears.
26
- *
27
- * Algorithmically this is the **DFS bracket-sequence pattern** —
28
- * stack-frame state during a graph-traversal interval. Same shape used
29
- * by Tarjan's SCC algorithm, tree decomposition, and push-down
30
- * automata. The `active` map is the open-brackets stack at any moment.
31
- *
32
- * **Comparison with the other storage primitives:**
33
- *
34
- * | Primitive | Stores | Time scope | Memory |
35
- * |---------------------------------|--------------------------------------|---------------------|-------------|
36
- * | `SequenceRecorder<T>` | append-only ordered + keyed entries | durable across run | O(N events) |
37
- * | `KeyedRecorder<T>` | 1:1 entry per `runtimeStageId` | durable across run | O(N steps) |
38
- * | `BoundaryStateTracker<TState>` | transient bracket-scoped state | live; clears on stop | O(K active) |
39
- *
40
- * **When to pick which:**
41
- *
42
- * - "I need to keep a permanent log of every event for time-travel"
43
- * → `SequenceRecorder<T>`
44
- * - "I want one durable record per stage execution (e.g., metrics)"
45
- * → `KeyedRecorder<T>`
46
- * - "I need to know what's happening RIGHT NOW inside an in-flight
47
- * boundary (e.g., partial LLM stream, partial tool args)"
48
- * → `BoundaryStateTracker<TState>` (this class)
49
- *
50
- * **What this is NOT for:**
51
- *
52
- * - Time-travel queries ("what was the state at past slider step N?")
53
- * — transient state clears on stop. For time-travel, snapshot the
54
- * state at each emit into a separate `SequenceRecorder<TState>`,
55
- * or wait for a future `BoundarySnapshotRecorder<TState>` primitive.
56
- *
57
- * - Aggregations across the whole run (totals, counts) — those are
58
- * `SequenceRecorder.aggregate()` / `KeyedRecorder.aggregate()`.
59
- *
60
- * - Stage-level concerns — those use `ScopeRecorder.onStageStart` /
61
- * `ScopeRecorder.onStageEnd`. This primitive operates at finer
62
- * granularity (events emitted DURING a stage execution).
63
- *
64
- * **Lifecycle contract — STRICT:**
65
- *
66
- * Every `startBoundary(key, ...)` call MUST be paired with a
67
- * `stopBoundary(key)` call. Failure to wire the stop side produces a
68
- * memory leak: the active map grows without bound, and `getAllActive()`
69
- * returns stale entries that look in-flight but aren't. Common cause:
70
- * subclass wires `start` to one event handler and forgets to wire
71
- * `stop`. **Always wire both at the same time.**
72
- *
73
- * Dev mode (`enableDevMode()`) detects leaks at `clear()` time —
74
- * warning includes the leaked keys so you can find the missing wiring.
75
- *
76
- * **Concurrency / nesting:**
77
- *
78
- * - Concurrent boundaries (parallel branches with two LLM calls
79
- * active at once) work correctly: each is keyed independently in
80
- * the active map.
81
- * - Nested boundaries of DIFFERENT KINDS (Agent boundary contains
82
- * LLM boundary) require SEPARATE tracker instances — one per kind.
83
- * The base class tracks one boundary kind per instance.
84
- *
85
- * **Key convention:**
86
- *
87
- * The `key: string` is whatever your subclass picks. Convention:
88
- * use `runtimeStageId` when the boundary maps 1:1 to a stage
89
- * execution — this gives free interop with `SequenceRecorder
90
- * .getEntriesForStep`, `KeyedRecorder.getByKey`, `findCommit` /
91
- * `findLastWriter`, and the rest of the trace ecosystem. Use a more
92
- * granular key (e.g., `toolCallId`) only when there are multiple
93
- * concurrent boundaries WITHIN one stage execution.
94
- *
95
- * @example Build a live LLM tracker (combining storage + observer):
96
- *
97
- * ```typescript
98
- * import { BoundaryStateTracker } from 'footprintjs/trace';
99
- * import { type CombinedRecorder, type EmitEvent } from 'footprintjs';
100
- *
101
- * interface LLMLiveState {
102
- * readonly partial: string;
103
- * readonly tokens: number;
104
- * }
105
- *
106
- * class LiveLLMTracker
107
- * extends BoundaryStateTracker<LLMLiveState> // STORAGE shelf
108
- * implements CombinedRecorder // OBSERVER interface
109
- * {
110
- * readonly id = 'live-llm';
111
- *
112
- * // Observer half — translate events into bracket mutations.
113
- * onEmit(event: EmitEvent): void {
114
- * if (event.name === 'agentfootprint.stream.llm_start') {
115
- * this.startBoundary(event.runtimeStageId, { partial: '', tokens: 0 });
116
- * } else if (event.name === 'agentfootprint.stream.llm_end') {
117
- * this.stopBoundary(event.runtimeStageId);
118
- * } else if (event.name === 'agentfootprint.stream.token') {
119
- * this.updateBoundary(event.runtimeStageId, (s) => ({
120
- * partial: s.partial + (event.payload as { content: string }).content,
121
- * tokens: s.tokens + 1,
122
- * }));
123
- * }
124
- * }
125
- *
126
- * // Public read API — O(1) at any moment.
127
- * isInFlight(): boolean { return this.hasActive; }
128
- * getPartial(stageId: string): string {
129
- * return this.getActive(stageId)?.partial ?? '';
130
- * }
131
- * }
132
- *
133
- * // Attached the same way as any other CombinedRecorder.
134
- * const tracker = new LiveLLMTracker();
135
- * executor.attachCombinedRecorder(tracker);
136
- * await executor.run({ input });
137
- *
138
- * // Read live state at any time during or after the run.
139
- * tracker.isInFlight();
140
- * tracker.getPartial(rid);
141
- * ```
142
- */
143
- export declare abstract class BoundaryStateTracker<TState> {
144
- /** Stable id — same idempotency contract as other recorders. */
145
- abstract readonly id: string;
146
- /** Open-brackets stack: key → current transient state. */
147
- private readonly active;
148
- /** Per-key count of `updateBoundary` calls that landed without a
149
- * matching active boundary. Drives rate-limited dev-mode warnings
150
- * so a stuck loop doesn't spam the console. */
151
- private readonly missedUpdates;
152
- /**
153
- * Open a new boundary with initial transient state.
154
- *
155
- * If a boundary with the same `key` is already active, the prior
156
- * state is overwritten (last-writer-wins). In dev mode, a warning
157
- * is logged because re-starting an active key usually indicates a
158
- * missed `stopBoundary` call upstream.
159
- *
160
- * @param key Boundary identifier — by convention, `runtimeStageId`
161
- * for 1:1-with-stage boundaries, or a more granular id
162
- * (e.g., `toolCallId`) when multiple concurrent boundaries
163
- * run within one stage execution.
164
- * @param initial Initial state — typed by `TState`.
165
- */
166
- protected startBoundary(key: string, initial: TState): void;
167
- /**
168
- * Evolve the transient state of an active boundary using an updater
169
- * function. Silent no-op if no boundary is active for `key`
170
- * (defensive against out-of-order events). In dev mode, a rate-limited
171
- * warning is logged on the 1st, 10th, and 100th missed update per key.
172
- *
173
- * @param key Boundary identifier (must match a prior `startBoundary`).
174
- * @param updater Pure function: previous state → next state.
175
- */
176
- protected updateBoundary(key: string, updater: (prev: TState) => TState): void;
177
- /**
178
- * Close the boundary identified by `key` and return its FINAL
179
- * transient state (for any cleanup the subclass wants — e.g., emit a
180
- * snapshot to a SequenceRecorder for durable storage).
181
- *
182
- * @param key Boundary identifier.
183
- * @returns The final state, or `undefined` if no boundary was active.
184
- */
185
- protected stopBoundary(key: string): TState | undefined;
186
- /** Current transient state of ONE active boundary. `undefined` if no
187
- * boundary is active for `key`. */
188
- getActive(key: string): TState | undefined;
189
- /**
190
- * All currently-active boundaries.
191
- *
192
- * **Type-only readonly:** the returned reference IS the internal Map.
193
- * TypeScript prevents mutation through the `ReadonlyMap` type, but a
194
- * runtime cast or non-TS consumer can mutate it and corrupt internal
195
- * state. **Do not mutate.**
196
- */
197
- getAllActive(): ReadonlyMap<string, TState>;
198
- /** True if any boundary is currently active. O(1). */
199
- get hasActive(): boolean;
200
- /** Number of currently-active boundaries. O(1). */
201
- get activeCount(): number;
202
- /**
203
- * Reset all transient state. Called by executors before each `run()`
204
- * so consumers get a clean slate per run — same lifecycle contract
205
- * as `SequenceRecorder.clear()`.
206
- *
207
- * In dev mode, warns if any boundaries are still active when called
208
- * — likely indicates a missed `stopBoundary` upstream. The leaked
209
- * keys are listed (truncated to 10) so the wiring bug is findable.
210
- */
211
- clear(): void;
212
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * KeyedRecorder<T> — base class for Map-based recorders keyed by runtimeStageId.
3
- *
4
- * Provides typed key-value storage with O(1) lookup, insertion-ordered iteration,
5
- * and three standard operations on auto-collected traversal data:
6
- *
7
- * - **Translate** (raw): `getByKey(id)` — per-step value
8
- * - **Accumulate** (progressive): `accumulate(fn, initial, keys?)` — running total up to a point
9
- * - **Aggregate** (summary): `aggregate(fn, initial)` — reduce all entries
10
- *
11
- * Data is automatically collected during the single DFS traversal.
12
- * The consumer chooses the operation at read time.
13
- *
14
- * @example
15
- * ```typescript
16
- * class TokenRecorder extends KeyedRecorder<LLMCallEntry> {
17
- * readonly id = 'tokens';
18
- * onLLMCall(event) { this.store(event.runtimeStageId, { tokens: event.usage }); }
19
- *
20
- * // Translate: per-step
21
- * getForStep(id: string) { return this.getByKey(id); }
22
- *
23
- * // Aggregate: total
24
- * getTotalTokens() { return this.aggregate((sum, e) => sum + e.tokens, 0); }
25
- *
26
- * // Accumulate: progressive up to slider position
27
- * getTokensUpTo(keys: Set<string>) { return this.accumulate((sum, e) => sum + e.tokens, 0, keys); }
28
- * }
29
- * ```
30
- */
31
- export declare abstract class KeyedRecorder<T> {
32
- abstract readonly id: string;
33
- private readonly data;
34
- /** Store an entry keyed by runtimeStageId. */
35
- protected store(runtimeStageId: string, entry: T): void;
36
- /** O(1) lookup by runtimeStageId. */
37
- getByKey(runtimeStageId: string): T | undefined;
38
- /** All entries as a read-only Map (insertion-ordered). */
39
- getMap(): ReadonlyMap<string, T>;
40
- /** All entries as an array (insertion-ordered). */
41
- values(): T[];
42
- /** Number of entries stored. */
43
- get size(): number;
44
- /** Reduce ALL entries to a single value. For dashboards, totals, summaries. */
45
- aggregate<R>(fn: (acc: R, entry: T, key: string) => R, initial: R): R;
46
- /**
47
- * Reduce entries, optionally filtered by a set of keys.
48
- * For time-travel progressive view: pass the runtimeStageIds visible at the current slider position.
49
- * Without keys, reduces all entries (same as aggregate).
50
- */
51
- accumulate<R>(fn: (acc: R, entry: T, key: string) => R, initial: R, keys?: ReadonlySet<string>): R;
52
- /** Return entries whose keys are in the set, preserving insertion order. */
53
- filterByKeys(keys: ReadonlySet<string>): T[];
54
- /** Clear all stored data. Called by executor before each run(). */
55
- clear(): void;
56
- }
@@ -1,112 +0,0 @@
1
- /**
2
- * SequenceRecorder<T> — base class for ordered sequence recorders with keyed index.
3
- *
4
- * Provides dual-indexed storage: a flat array preserving insertion order plus a
5
- * Map<runtimeStageId, T[]> for O(1) per-step lookup. One entry type, multiple entries
6
- * per step. Designed for recorders that implement BOTH ScopeRecorder and FlowRecorder
7
- * (merging data ops and control flow into a single interleaved sequence).
8
- *
9
- * **Contrast with KeyedRecorder<T>:**
10
- * - `KeyedRecorder<T>` — 1:1 Map. One entry per runtimeStageId. For single-value recorders
11
- * like MetricRecorder (one StepMetrics per stage execution).
12
- * - `SequenceRecorder<T>` — 1:N sequence + Map. Multiple entries per runtimeStageId,
13
- * plus ordering matters. For recorders that produce a prose narrative or event log
14
- * where a single step generates stage + data + decision entries.
15
- *
16
- * **How to choose:**
17
- * - If each step produces exactly one record → extend `KeyedRecorder<T>`
18
- * - If each step produces multiple records or ordering matters → extend `SequenceRecorder<T>`
19
- *
20
- * @example
21
- * ```typescript
22
- * import { SequenceRecorder } from 'footprintjs/trace';
23
- *
24
- * interface AuditEntry {
25
- * runtimeStageId?: string;
26
- * type: 'read' | 'write' | 'decision';
27
- * detail: string;
28
- * }
29
- *
30
- * class AuditRecorder extends SequenceRecorder<AuditEntry> {
31
- * readonly id = 'audit';
32
- *
33
- * // Scope hooks (fires during stage execution)
34
- * onRead(event: ReadEvent) {
35
- * this.emit({ runtimeStageId: event.runtimeStageId, type: 'read', detail: event.key });
36
- * }
37
- * onWrite(event: WriteEvent) {
38
- * this.emit({ runtimeStageId: event.runtimeStageId, type: 'write', detail: event.key });
39
- * }
40
- *
41
- * // Flow hooks (fires after stage execution)
42
- * onDecision(event: FlowDecisionEvent) {
43
- * this.emit({
44
- * runtimeStageId: event.traversalContext?.runtimeStageId,
45
- * type: 'decision',
46
- * detail: `${event.decider} chose ${event.chosen}`,
47
- * });
48
- * }
49
- *
50
- * // Time-travel: entries up to slider position
51
- * getAuditUpTo(visibleIds: ReadonlySet<string>) {
52
- * return this.getEntriesUpTo(visibleIds);
53
- * }
54
- * }
55
- * ```
56
- */
57
- export declare abstract class SequenceRecorder<T extends {
58
- runtimeStageId?: string;
59
- }> {
60
- abstract readonly id: string;
61
- /** Ordered sequence of all entries (insertion order). */
62
- private readonly entries;
63
- /** Per-step index: runtimeStageId → entries for that step. Same objects as entries[]. */
64
- private readonly byRuntimeStageId;
65
- /** Per-step range index: runtimeStageId → [firstIdx, endIdx) in entries array.
66
- * endIdx includes trailing keyless entries (structural markers). Maintained during emit(). */
67
- private readonly entryRanges;
68
- /** The runtimeStageId of the most recently emitted keyed entry. Used to extend ranges for trailing markers. */
69
- private lastEmittedId;
70
- /**
71
- * Append an entry to both the ordered sequence, keyed index, and range index.
72
- * All reference the same entry object — no duplication.
73
- */
74
- protected emit(entry: T): void;
75
- /** All entries in insertion order (returns a shallow copy — entry objects are shared). */
76
- getEntries(): T[];
77
- /** Number of entries in the sequence. */
78
- get entryCount(): number;
79
- /** Zero-copy iteration for subclass rendering methods (avoids getEntries() spread). */
80
- protected forEachEntry(fn: (entry: T) => void): void;
81
- /** O(1) lookup: all entries for a specific execution step (returns a copy). */
82
- getEntriesForStep(runtimeStageId: string): T[];
83
- /** Number of unique execution steps that have entries. */
84
- get stepCount(): number;
85
- /**
86
- * Pre-built range index: runtimeStageId → half-open range [firstIdx, endIdx) in entries array.
87
- * Maintained during emit() — no rebuild needed. Use for O(1) per-step lookups during time-travel.
88
- * endIdx includes trailing keyless entries (structural markers following a step).
89
- */
90
- getEntryRanges(): ReadonlyMap<string, {
91
- readonly firstIdx: number;
92
- readonly endIdx: number;
93
- }>;
94
- /** Reduce ALL entries to a single value. For dashboards, totals, summaries. */
95
- aggregate<R>(fn: (acc: R, entry: T) => R, initial: R): R;
96
- /**
97
- * Reduce entries, optionally filtered by a set of runtimeStageIds.
98
- * For time-travel progressive view: pass the runtimeStageIds visible at the current slider position.
99
- * Entries without runtimeStageId (structural markers) are excluded when keys are provided.
100
- * Without keys, reduces all entries (same as aggregate).
101
- */
102
- accumulate<R>(fn: (acc: R, entry: T) => R, initial: R, keys?: ReadonlySet<string>): R;
103
- /**
104
- * Progressive reveal: entries whose runtimeStageId is in the visible set.
105
- * Preserves insertion order. Entries without runtimeStageId (structural markers)
106
- * are buffered and included only when surrounded by visible steps on both sides —
107
- * trailing markers after the last visible step are discarded.
108
- */
109
- getEntriesUpTo(visibleIds: ReadonlySet<string>): T[];
110
- /** Clear all stored data. Called by executor before each run(). */
111
- clear(): void;
112
- }