agentfootprint-lens 0.14.2 → 0.15.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-MXIMEL7W.js +810 -0
- package/dist/chunk-MXIMEL7W.js.map +1 -0
- package/dist/core.cjs +2 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +1 -1
- package/dist/index.cjs +46 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +45 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
// src/v2/core/LensRecorder.ts
|
|
2
|
+
import { SequenceRecorder } from "footprintjs/trace";
|
|
3
|
+
import {
|
|
4
|
+
LiveStateRecorder
|
|
5
|
+
} from "agentfootprint";
|
|
6
|
+
var LensRecorder = class extends SequenceRecorder {
|
|
7
|
+
constructor(rootLabel = "Run") {
|
|
8
|
+
super();
|
|
9
|
+
/** SequenceRecorder requires a stable id for idempotent attach. */
|
|
10
|
+
this.id = "lens";
|
|
11
|
+
this.stack = [];
|
|
12
|
+
this.seqCounter = 0;
|
|
13
|
+
this.unsubscribes = [];
|
|
14
|
+
this.finalStatus = "running";
|
|
15
|
+
/** Live transient state of the in-flight run. Subscribed in `observe()`,
|
|
16
|
+
* cleared/disposed on `detach()`. Lens reads `liveState.isLLMInFlight()`
|
|
17
|
+
* / `getPartialLLM()` / etc. for O(1) live commentary, instead of
|
|
18
|
+
* folding the event log every render. */
|
|
19
|
+
this.liveState = new LiveStateRecorder();
|
|
20
|
+
/**
|
|
21
|
+
* External-store subscribers — React (`useSyncExternalStore`) and any
|
|
22
|
+
* non-React consumer that wants push-based refresh. Called synchronously
|
|
23
|
+
* at the end of every `handleEvent`, so views re-render event-by-event
|
|
24
|
+
* without polling. Zero setInterval, zero requestAnimationFrame fallback.
|
|
25
|
+
*/
|
|
26
|
+
this.changeListeners = /* @__PURE__ */ new Set();
|
|
27
|
+
/**
|
|
28
|
+
* Bumping version is cheap (number increment) but lets
|
|
29
|
+
* `useSyncExternalStore` detect a change by identity. React compares
|
|
30
|
+
* `getSnapshot()` return values by `Object.is` — a bumped version
|
|
31
|
+
* guarantees a fresh reference each event.
|
|
32
|
+
*/
|
|
33
|
+
this.version = 0;
|
|
34
|
+
this.root = {
|
|
35
|
+
id: "run-root",
|
|
36
|
+
kind: "run",
|
|
37
|
+
label: rootLabel,
|
|
38
|
+
status: "running",
|
|
39
|
+
startOffsetMs: 0,
|
|
40
|
+
children: [],
|
|
41
|
+
events: []
|
|
42
|
+
};
|
|
43
|
+
this.stack.push(this.root);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Push-based change subscription. Every `handleEvent` pass fires every
|
|
47
|
+
* listener synchronously — React's `useSyncExternalStore` re-renders,
|
|
48
|
+
* non-React consumers refresh whatever view they're driving. Return
|
|
49
|
+
* value detaches the listener; safe to call multiple times.
|
|
50
|
+
*/
|
|
51
|
+
subscribe(listener) {
|
|
52
|
+
this.changeListeners.add(listener);
|
|
53
|
+
return () => {
|
|
54
|
+
this.changeListeners.delete(listener);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
getVersion() {
|
|
58
|
+
return this.version;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Subscribe to a v2 Runner's typed dispatcher. Call once per run.
|
|
62
|
+
* Returns an unsubscribe for the consumer — calling it detaches the
|
|
63
|
+
* recorder (useful for cleanup after post-run rendering is done).
|
|
64
|
+
*/
|
|
65
|
+
observe(runner) {
|
|
66
|
+
const offEvent = runner.on("*", (event) => {
|
|
67
|
+
this.handleEvent(event);
|
|
68
|
+
});
|
|
69
|
+
const offLive = this.liveState.subscribe(runner);
|
|
70
|
+
const composed = () => {
|
|
71
|
+
offEvent();
|
|
72
|
+
offLive();
|
|
73
|
+
};
|
|
74
|
+
this.unsubscribes.push(composed);
|
|
75
|
+
return () => {
|
|
76
|
+
const idx = this.unsubscribes.indexOf(composed);
|
|
77
|
+
if (idx >= 0) {
|
|
78
|
+
this.unsubscribes.splice(idx, 1);
|
|
79
|
+
composed();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Detach from all observed runners. Idempotent. */
|
|
84
|
+
detach() {
|
|
85
|
+
for (const off of this.unsubscribes) off();
|
|
86
|
+
this.unsubscribes.length = 0;
|
|
87
|
+
}
|
|
88
|
+
// ─── Event handling ────────────────────────────────────────────
|
|
89
|
+
handleEvent(event) {
|
|
90
|
+
const wallClockMs = event.meta.wallClockMs;
|
|
91
|
+
if (this.runStartMs === void 0) {
|
|
92
|
+
this.runStartMs = wallClockMs;
|
|
93
|
+
this.root.startOffsetMs = 0;
|
|
94
|
+
}
|
|
95
|
+
const runOffsetMs = wallClockMs - this.runStartMs;
|
|
96
|
+
const entry = {
|
|
97
|
+
seq: this.seqCounter++,
|
|
98
|
+
wallClockMs,
|
|
99
|
+
runOffsetMs,
|
|
100
|
+
event,
|
|
101
|
+
// Lift runtimeStageId onto the entry so the inherited
|
|
102
|
+
// SequenceRecorder index keys correctly — gives us O(1)
|
|
103
|
+
// `getEntriesForStep(rid)` and the per-step range index for
|
|
104
|
+
// free, no parallel data structure.
|
|
105
|
+
runtimeStageId: event.meta.runtimeStageId
|
|
106
|
+
};
|
|
107
|
+
this.emit(entry);
|
|
108
|
+
this.top().events.push(entry);
|
|
109
|
+
this.dispatch(event, runOffsetMs, entry);
|
|
110
|
+
this.version++;
|
|
111
|
+
for (const listener of this.changeListeners) {
|
|
112
|
+
try {
|
|
113
|
+
listener();
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Kind-specific handling. Keeps the switch exhaustive over every v2
|
|
120
|
+
* event type we structurally care about; the default branch is the
|
|
121
|
+
* "attach to current top, no structural change" path which has
|
|
122
|
+
* already fired above.
|
|
123
|
+
*/
|
|
124
|
+
dispatch(event, runOffsetMs, entry) {
|
|
125
|
+
const type = event.type;
|
|
126
|
+
if (type === "agentfootprint.composition.enter") {
|
|
127
|
+
const p = event.payload;
|
|
128
|
+
this.push({
|
|
129
|
+
id: `comp:${p.id}:${entry.seq}`,
|
|
130
|
+
kind: "composition",
|
|
131
|
+
label: `${p.kind}: ${p.name}`,
|
|
132
|
+
status: "running",
|
|
133
|
+
startOffsetMs: runOffsetMs,
|
|
134
|
+
children: [],
|
|
135
|
+
events: [],
|
|
136
|
+
composition: { compositionKind: p.kind, childCount: p.childCount }
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (type === "agentfootprint.composition.exit") {
|
|
141
|
+
const p = event.payload;
|
|
142
|
+
this.popIfKind("composition", {
|
|
143
|
+
endOffsetMs: runOffsetMs,
|
|
144
|
+
status: p.status === "ok" ? "ok" : p.status === "budget_exhausted" ? "budget_exhausted" : "err"
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (type === "agentfootprint.composition.iteration_start") {
|
|
149
|
+
const p = event.payload;
|
|
150
|
+
this.push({
|
|
151
|
+
id: `iter:${p.loopId}:${p.iteration}`,
|
|
152
|
+
kind: "iteration",
|
|
153
|
+
label: `Iteration ${p.iteration}`,
|
|
154
|
+
status: "running",
|
|
155
|
+
startOffsetMs: runOffsetMs,
|
|
156
|
+
children: [],
|
|
157
|
+
events: [],
|
|
158
|
+
iteration: { iteration: p.iteration }
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (type === "agentfootprint.composition.iteration_exit") {
|
|
163
|
+
const p = event.payload;
|
|
164
|
+
this.popIfKind("iteration", {
|
|
165
|
+
endOffsetMs: runOffsetMs,
|
|
166
|
+
status: p.reason === "budget" ? "budget_exhausted" : "ok",
|
|
167
|
+
iterationExit: p.reason
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (type === "agentfootprint.agent.turn_start") {
|
|
172
|
+
this.push({
|
|
173
|
+
id: `turn:${entry.seq}`,
|
|
174
|
+
kind: "iteration",
|
|
175
|
+
label: "Turn",
|
|
176
|
+
status: "running",
|
|
177
|
+
startOffsetMs: runOffsetMs,
|
|
178
|
+
children: [],
|
|
179
|
+
events: [],
|
|
180
|
+
iteration: { iteration: 0 }
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (type === "agentfootprint.agent.turn_end") {
|
|
185
|
+
this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (type === "agentfootprint.agent.iteration_start") {
|
|
189
|
+
const p = event.payload;
|
|
190
|
+
this.push({
|
|
191
|
+
id: `agent-iter:${p.iterIndex}`,
|
|
192
|
+
kind: "iteration",
|
|
193
|
+
label: `Iteration ${p.iterIndex}`,
|
|
194
|
+
status: "running",
|
|
195
|
+
startOffsetMs: runOffsetMs,
|
|
196
|
+
children: [],
|
|
197
|
+
events: [],
|
|
198
|
+
iteration: { iteration: p.iterIndex }
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (type === "agentfootprint.agent.iteration_end") {
|
|
203
|
+
this.popIfKind("iteration", { endOffsetMs: runOffsetMs, status: "ok" });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (type === "agentfootprint.stream.llm_start") {
|
|
207
|
+
const p = event.payload;
|
|
208
|
+
const node = {
|
|
209
|
+
id: `llm:${entry.seq}`,
|
|
210
|
+
kind: "llm-call",
|
|
211
|
+
label: `LLM: ${p.model}`,
|
|
212
|
+
status: "running",
|
|
213
|
+
startOffsetMs: runOffsetMs,
|
|
214
|
+
children: [],
|
|
215
|
+
events: [entry],
|
|
216
|
+
llm: {
|
|
217
|
+
provider: p.provider,
|
|
218
|
+
model: p.model,
|
|
219
|
+
systemPromptChars: p.systemPromptChars,
|
|
220
|
+
messagesCount: p.messagesCount,
|
|
221
|
+
toolsCount: p.toolsCount
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
this.top().children.push(node);
|
|
225
|
+
this.stack.push(node);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (type === "agentfootprint.stream.llm_end") {
|
|
229
|
+
const p = event.payload;
|
|
230
|
+
this.popIfKind("llm-call", {
|
|
231
|
+
endOffsetMs: runOffsetMs,
|
|
232
|
+
status: "ok",
|
|
233
|
+
llmEnd: {
|
|
234
|
+
content: p.content,
|
|
235
|
+
toolCallCount: p.toolCallCount,
|
|
236
|
+
usage: p.usage,
|
|
237
|
+
stopReason: p.stopReason
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (type === "agentfootprint.stream.tool_start") {
|
|
243
|
+
const p = event.payload;
|
|
244
|
+
const node = {
|
|
245
|
+
id: `tool:${p.toolCallId}`,
|
|
246
|
+
kind: "tool-call",
|
|
247
|
+
label: `Tool: ${p.toolName}`,
|
|
248
|
+
status: "running",
|
|
249
|
+
startOffsetMs: runOffsetMs,
|
|
250
|
+
children: [],
|
|
251
|
+
events: [entry],
|
|
252
|
+
tool: {
|
|
253
|
+
toolName: p.toolName,
|
|
254
|
+
toolCallId: p.toolCallId,
|
|
255
|
+
args: p.args
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
this.top().children.push(node);
|
|
259
|
+
this.stack.push(node);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (type === "agentfootprint.stream.tool_end") {
|
|
263
|
+
const p = event.payload;
|
|
264
|
+
this.popIfKind("tool-call", {
|
|
265
|
+
endOffsetMs: runOffsetMs,
|
|
266
|
+
status: p.error === true ? "err" : "ok",
|
|
267
|
+
toolEnd: { result: p.result, error: p.error ?? false }
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (type === "agentfootprint.pause.request") {
|
|
272
|
+
const p = event.payload;
|
|
273
|
+
const node = {
|
|
274
|
+
id: `pause:${entry.seq}`,
|
|
275
|
+
kind: "pause",
|
|
276
|
+
label: `Paused: ${p.reason}`,
|
|
277
|
+
status: "paused",
|
|
278
|
+
startOffsetMs: runOffsetMs,
|
|
279
|
+
endOffsetMs: runOffsetMs,
|
|
280
|
+
children: [],
|
|
281
|
+
events: [entry],
|
|
282
|
+
pause: { reason: p.reason, questionPayload: p.questionPayload }
|
|
283
|
+
};
|
|
284
|
+
this.top().children.push(node);
|
|
285
|
+
this.finalStatus = "paused";
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// ─── Stack helpers ────────────────────────────────────────────
|
|
290
|
+
top() {
|
|
291
|
+
return this.stack[this.stack.length - 1] ?? this.root;
|
|
292
|
+
}
|
|
293
|
+
push(node) {
|
|
294
|
+
this.top().children.push(node);
|
|
295
|
+
this.stack.push(node);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Pop the top node IF its kind matches, applying finalization fields.
|
|
299
|
+
* Mismatched kinds (indicating malformed event ordering) are logged
|
|
300
|
+
* but don't throw — Lens prefers partial correctness to crashes.
|
|
301
|
+
*/
|
|
302
|
+
popIfKind(kind, finalize) {
|
|
303
|
+
const top = this.top();
|
|
304
|
+
if (top.kind !== kind) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
top.endOffsetMs = finalize.endOffsetMs;
|
|
308
|
+
top.status = finalize.status;
|
|
309
|
+
if (finalize.iterationExit && top.iteration) {
|
|
310
|
+
top.iteration.exitReason = finalize.iterationExit;
|
|
311
|
+
}
|
|
312
|
+
if (finalize.llmEnd && top.llm) {
|
|
313
|
+
Object.assign(top.llm, finalize.llmEnd);
|
|
314
|
+
}
|
|
315
|
+
if (finalize.toolEnd && top.tool) {
|
|
316
|
+
top.tool.result = finalize.toolEnd.result;
|
|
317
|
+
top.tool.error = finalize.toolEnd.error;
|
|
318
|
+
}
|
|
319
|
+
this.stack.pop();
|
|
320
|
+
}
|
|
321
|
+
// ─── Selectors ────────────────────────────────────────────────
|
|
322
|
+
/** The complete ordered event log. Inherited storage from
|
|
323
|
+
* `SequenceRecorder<EventLogEntry>` — no parallel array. */
|
|
324
|
+
selectEventLog() {
|
|
325
|
+
return this.getEntries();
|
|
326
|
+
}
|
|
327
|
+
/** The RunTree — frozen, recursive, immutable snapshot. */
|
|
328
|
+
selectRunTree() {
|
|
329
|
+
if (this.root.endOffsetMs === void 0 && this.entryCount > 0) {
|
|
330
|
+
const last = this.getEntries()[this.entryCount - 1];
|
|
331
|
+
this.root.endOffsetMs = last.runOffsetMs;
|
|
332
|
+
this.root.status = this.finalStatus === "running" ? "ok" : this.finalStatus;
|
|
333
|
+
}
|
|
334
|
+
return freezeNode(this.root);
|
|
335
|
+
}
|
|
336
|
+
/** Summary stats — computed lazily via the inherited `.aggregate()`
|
|
337
|
+
* fold from `SequenceRecorder<EventLogEntry>`. Single-pass, types
|
|
338
|
+
* derived from the AgentfootprintEvent discriminated union. */
|
|
339
|
+
selectSummary() {
|
|
340
|
+
const init = {
|
|
341
|
+
llmCallCount: 0,
|
|
342
|
+
toolCallCount: 0,
|
|
343
|
+
iterationCount: 0,
|
|
344
|
+
totalInputTokens: 0,
|
|
345
|
+
totalOutputTokens: 0,
|
|
346
|
+
totalUsd: void 0,
|
|
347
|
+
permissionDenials: 0,
|
|
348
|
+
paused: false
|
|
349
|
+
};
|
|
350
|
+
const acc = this.aggregate((a, { event }) => {
|
|
351
|
+
switch (event.type) {
|
|
352
|
+
case "agentfootprint.stream.llm_start":
|
|
353
|
+
return { ...a, llmCallCount: a.llmCallCount + 1 };
|
|
354
|
+
case "agentfootprint.stream.tool_start":
|
|
355
|
+
return { ...a, toolCallCount: a.toolCallCount + 1 };
|
|
356
|
+
case "agentfootprint.agent.iteration_start":
|
|
357
|
+
case "agentfootprint.composition.iteration_start":
|
|
358
|
+
return { ...a, iterationCount: a.iterationCount + 1 };
|
|
359
|
+
case "agentfootprint.stream.llm_end":
|
|
360
|
+
return {
|
|
361
|
+
...a,
|
|
362
|
+
totalInputTokens: a.totalInputTokens + event.payload.usage.input,
|
|
363
|
+
totalOutputTokens: a.totalOutputTokens + event.payload.usage.output
|
|
364
|
+
};
|
|
365
|
+
case "agentfootprint.cost.tick":
|
|
366
|
+
return { ...a, totalUsd: event.payload.cumulative.estimatedUsd };
|
|
367
|
+
case "agentfootprint.permission.check":
|
|
368
|
+
return event.payload.result === "deny" ? { ...a, permissionDenials: a.permissionDenials + 1 } : a;
|
|
369
|
+
case "agentfootprint.pause.request":
|
|
370
|
+
return { ...a, paused: true };
|
|
371
|
+
case "agentfootprint.pause.resume":
|
|
372
|
+
return { ...a, paused: false };
|
|
373
|
+
default:
|
|
374
|
+
return a;
|
|
375
|
+
}
|
|
376
|
+
}, init);
|
|
377
|
+
const entries = this.getEntries();
|
|
378
|
+
const startedAt = entries[0]?.wallClockMs ?? 0;
|
|
379
|
+
const endedAt = entries[entries.length - 1]?.wallClockMs;
|
|
380
|
+
return {
|
|
381
|
+
startedAt,
|
|
382
|
+
...endedAt !== void 0 && { endedAt, durationMs: endedAt - startedAt },
|
|
383
|
+
status: acc.paused ? "paused" : this.finalStatus === "running" ? "ok" : this.finalStatus,
|
|
384
|
+
llmCallCount: acc.llmCallCount,
|
|
385
|
+
toolCallCount: acc.toolCallCount,
|
|
386
|
+
iterationCount: acc.iterationCount,
|
|
387
|
+
totalTokens: { input: acc.totalInputTokens, output: acc.totalOutputTokens },
|
|
388
|
+
...acc.totalUsd !== void 0 && { totalUsd: acc.totalUsd },
|
|
389
|
+
permissionDenials: acc.permissionDenials,
|
|
390
|
+
paused: acc.paused
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
function freezeNode(n) {
|
|
395
|
+
const children = n.children.map(freezeNode);
|
|
396
|
+
const base = {
|
|
397
|
+
id: n.id,
|
|
398
|
+
kind: n.kind,
|
|
399
|
+
label: n.label,
|
|
400
|
+
status: n.status,
|
|
401
|
+
startOffsetMs: n.startOffsetMs,
|
|
402
|
+
...n.endOffsetMs !== void 0 && {
|
|
403
|
+
durationMs: n.endOffsetMs - n.startOffsetMs
|
|
404
|
+
},
|
|
405
|
+
children,
|
|
406
|
+
events: [...n.events],
|
|
407
|
+
...buildDetails(n) && { details: buildDetails(n) }
|
|
408
|
+
};
|
|
409
|
+
return base;
|
|
410
|
+
}
|
|
411
|
+
function buildDetails(n) {
|
|
412
|
+
if (n.kind === "llm-call" && n.llm) {
|
|
413
|
+
const l = n.llm;
|
|
414
|
+
return { kind: "llm-call", llm: l };
|
|
415
|
+
}
|
|
416
|
+
if (n.kind === "tool-call" && n.tool) {
|
|
417
|
+
const t = n.tool;
|
|
418
|
+
return { kind: "tool-call", tool: t };
|
|
419
|
+
}
|
|
420
|
+
if (n.kind === "composition" && n.composition) {
|
|
421
|
+
return { kind: "composition", composition: n.composition };
|
|
422
|
+
}
|
|
423
|
+
if (n.kind === "iteration" && n.iteration) {
|
|
424
|
+
const i = n.iteration;
|
|
425
|
+
return { kind: "iteration", iteration: i };
|
|
426
|
+
}
|
|
427
|
+
if (n.kind === "pause" && n.pause) {
|
|
428
|
+
return { kind: "pause", pause: n.pause };
|
|
429
|
+
}
|
|
430
|
+
return void 0;
|
|
431
|
+
}
|
|
432
|
+
function lensRecorder(rootLabel) {
|
|
433
|
+
return new LensRecorder(rootLabel);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/v2/core/humanizer.ts
|
|
437
|
+
import {
|
|
438
|
+
defaultCommentaryTemplates,
|
|
439
|
+
extractCommentaryVars,
|
|
440
|
+
renderCommentary,
|
|
441
|
+
selectCommentaryKey
|
|
442
|
+
} from "agentfootprint";
|
|
443
|
+
var defaultHumanizer = (event) => {
|
|
444
|
+
switch (event.type) {
|
|
445
|
+
// Composition
|
|
446
|
+
case "agentfootprint.composition.enter":
|
|
447
|
+
return `Entered ${event.payload.kind} "${event.payload.name}" with ${event.payload.childCount} children.`;
|
|
448
|
+
case "agentfootprint.composition.exit":
|
|
449
|
+
return `${event.payload.kind} finished \u2014 ${event.payload.status} in ${event.payload.durationMs}ms.`;
|
|
450
|
+
case "agentfootprint.composition.fork_start":
|
|
451
|
+
return `Fanning out ${event.payload.branches.length} branches.`;
|
|
452
|
+
case "agentfootprint.composition.merge_end":
|
|
453
|
+
return `Merged ${event.payload.mergedBranchCount} branches via ${event.payload.strategy}.`;
|
|
454
|
+
case "agentfootprint.composition.route_decided":
|
|
455
|
+
return `Routed to "${event.payload.chosen}" \u2014 ${event.payload.rationale}`;
|
|
456
|
+
case "agentfootprint.composition.iteration_start":
|
|
457
|
+
return `Iteration ${event.payload.iteration} begins.`;
|
|
458
|
+
case "agentfootprint.composition.iteration_exit":
|
|
459
|
+
return `Iteration ${event.payload.iteration} ended (${event.payload.reason}).`;
|
|
460
|
+
// Agent
|
|
461
|
+
case "agentfootprint.agent.turn_start":
|
|
462
|
+
return `Agent turn begins: "${event.payload.userPrompt}".`;
|
|
463
|
+
case "agentfootprint.agent.turn_end":
|
|
464
|
+
return `Agent turn complete: "${event.payload.finalContent}" (${event.payload.iterationCount} iterations, ${event.payload.totalInputTokens}+${event.payload.totalOutputTokens} tokens).`;
|
|
465
|
+
case "agentfootprint.agent.iteration_start":
|
|
466
|
+
return `ReAct iteration ${event.payload.iterIndex}.`;
|
|
467
|
+
case "agentfootprint.agent.iteration_end":
|
|
468
|
+
return `Iteration ${event.payload.iterIndex} ended (${event.payload.toolCallCount} tool calls).`;
|
|
469
|
+
case "agentfootprint.agent.route_decided":
|
|
470
|
+
return `Agent routed to "${event.payload.chosen}" \u2014 ${event.payload.rationale}`;
|
|
471
|
+
// Stream
|
|
472
|
+
case "agentfootprint.stream.llm_start":
|
|
473
|
+
return `Calling ${event.payload.provider}/${event.payload.model} (iter ${event.payload.iteration}, ${event.payload.messagesCount} messages, ${event.payload.toolsCount} tools).`;
|
|
474
|
+
case "agentfootprint.stream.llm_end":
|
|
475
|
+
return `Model replied in ${event.payload.durationMs}ms (${event.payload.usage.input}+${event.payload.usage.output} tokens, ${event.payload.toolCallCount} tool calls, stop: ${event.payload.stopReason}).`;
|
|
476
|
+
case "agentfootprint.stream.tool_start":
|
|
477
|
+
return `Calling tool "${event.payload.toolName}" with ${JSON.stringify(event.payload.args)}.`;
|
|
478
|
+
case "agentfootprint.stream.tool_end":
|
|
479
|
+
return `Tool "${event.payload.toolCallId}" returned in ${event.payload.durationMs}ms${event.payload.error === true ? " (error)" : ""}.`;
|
|
480
|
+
case "agentfootprint.stream.token":
|
|
481
|
+
return null;
|
|
482
|
+
// too low-signal for the analyst view
|
|
483
|
+
// Context
|
|
484
|
+
case "agentfootprint.context.injected":
|
|
485
|
+
return `Injected ${event.payload.slot}: "${event.payload.contentSummary}" (from ${event.payload.source}).`;
|
|
486
|
+
case "agentfootprint.context.slot_composed":
|
|
487
|
+
return `Slot "${event.payload.slot}" composed (iter ${event.payload.iteration}, ${event.payload.budget.used}/${event.payload.budget.cap} tokens).`;
|
|
488
|
+
case "agentfootprint.context.evicted":
|
|
489
|
+
return `Evicted from "${event.payload.slot}" \u2014 ${event.payload.reason} (survived ${event.payload.survivalMs}ms).`;
|
|
490
|
+
case "agentfootprint.context.budget_pressure":
|
|
491
|
+
return `Budget pressure on "${event.payload.slot}": ${event.payload.projectedTokens}/${event.payload.capTokens} tokens \u2192 plan: ${event.payload.planAction}.`;
|
|
492
|
+
// Cost
|
|
493
|
+
case "agentfootprint.cost.tick":
|
|
494
|
+
return `Cost +$${event.payload.estimatedUsd.toFixed(6)} \u2014 cumulative $${event.payload.cumulative.estimatedUsd.toFixed(6)}.`;
|
|
495
|
+
case "agentfootprint.cost.limit_hit":
|
|
496
|
+
return `\u26A0 Cost budget ${event.payload.limit} crossed \u2014 actual ${event.payload.actual} (${event.payload.action}).`;
|
|
497
|
+
// Permission
|
|
498
|
+
case "agentfootprint.permission.check":
|
|
499
|
+
return `Permission: ${event.payload.capability} \u2192 "${event.payload.target}" = ${event.payload.result}${event.payload.rationale ? ` (${event.payload.rationale})` : ""}.`;
|
|
500
|
+
case "agentfootprint.permission.gate_opened":
|
|
501
|
+
return `Gate "${event.payload.gateId}" opened by ${event.payload.openedBy}.`;
|
|
502
|
+
case "agentfootprint.permission.gate_closed":
|
|
503
|
+
return `Gate "${event.payload.gateId}" closed \u2014 ${event.payload.reason}.`;
|
|
504
|
+
// Pause
|
|
505
|
+
case "agentfootprint.pause.request":
|
|
506
|
+
return `\u23F8 Paused \u2014 ${event.payload.reason}.`;
|
|
507
|
+
case "agentfootprint.pause.resume":
|
|
508
|
+
return `\u25B6 Resumed after ${event.payload.pausedDurationMs}ms.`;
|
|
509
|
+
// Eval / memory / skill (consumer-emitted, often domain-specific)
|
|
510
|
+
case "agentfootprint.eval.score":
|
|
511
|
+
return `Eval "${event.payload.metricId}" = ${event.payload.value}${event.payload.threshold !== void 0 ? ` (threshold ${event.payload.threshold})` : ""}.`;
|
|
512
|
+
case "agentfootprint.eval.threshold_crossed":
|
|
513
|
+
return `Eval "${event.payload.metricId}" crossed ${event.payload.threshold} ${event.payload.direction} \u2192 ${event.payload.value}.`;
|
|
514
|
+
case "agentfootprint.memory.strategy_applied":
|
|
515
|
+
return `Memory strategy "${event.payload.strategyKind}" applied \u2014 ${event.payload.reason}.`;
|
|
516
|
+
case "agentfootprint.memory.written":
|
|
517
|
+
return `Memory write: "${event.payload.memoryId}" \u2014 ${event.payload.contentSummary} (${event.payload.source}).`;
|
|
518
|
+
case "agentfootprint.skill.activated":
|
|
519
|
+
return `Skill "${event.payload.skillId}" activated \u2014 ${event.payload.reason}.`;
|
|
520
|
+
case "agentfootprint.skill.deactivated":
|
|
521
|
+
return `Skill "${event.payload.skillId}" deactivated \u2014 ${event.payload.reason}.`;
|
|
522
|
+
// Error
|
|
523
|
+
case "agentfootprint.error.fatal":
|
|
524
|
+
return `\u26D4 Fatal error in ${event.payload.stage}: ${event.payload.error}.`;
|
|
525
|
+
default:
|
|
526
|
+
return `[${event.type}]`;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
function humanizeWith(overrides) {
|
|
530
|
+
return (event) => {
|
|
531
|
+
const override = overrides[event.type];
|
|
532
|
+
if (override) {
|
|
533
|
+
const result = override(event);
|
|
534
|
+
if (result !== void 0) return result;
|
|
535
|
+
}
|
|
536
|
+
return defaultHumanizer(event);
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function makeTeachingHumanizer(options = {}) {
|
|
540
|
+
const appName = options.appName ?? "Chatbot";
|
|
541
|
+
const getToolDescription = options.getToolDescription;
|
|
542
|
+
const templates = options.commentaryTemplates ? { ...defaultCommentaryTemplates, ...options.commentaryTemplates } : defaultCommentaryTemplates;
|
|
543
|
+
const ctx = { appName, getToolDescription };
|
|
544
|
+
return (event) => {
|
|
545
|
+
const key = selectCommentaryKey(event);
|
|
546
|
+
if (key === null) return null;
|
|
547
|
+
if (key === void 0) return defaultHumanizer(event);
|
|
548
|
+
const template = templates[key];
|
|
549
|
+
if (template === void 0) return defaultHumanizer(event);
|
|
550
|
+
const vars = extractCommentaryVars(event, ctx, templates);
|
|
551
|
+
return renderCommentary(template, vars);
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
var teachingHumanizer = makeTeachingHumanizer();
|
|
555
|
+
|
|
556
|
+
// src/v2/core/selectors/selectAgentInstances.ts
|
|
557
|
+
function selectAgentInstances(graph) {
|
|
558
|
+
const boundaries = graph.nodes.filter((n) => n.isPrimitiveBoundary === true);
|
|
559
|
+
if (boundaries.length === 0) {
|
|
560
|
+
const rootSubflow = graph.nodes.find((n) => n.kind === "subflow");
|
|
561
|
+
const primitiveKind = rootSubflow?.primitiveKind;
|
|
562
|
+
return [
|
|
563
|
+
{
|
|
564
|
+
groupId: "agent-root",
|
|
565
|
+
llmId: "stage-llm-root",
|
|
566
|
+
toolId: "stage-tool-root",
|
|
567
|
+
label: primitiveKind ?? "Runner",
|
|
568
|
+
subflowPath: [],
|
|
569
|
+
...primitiveKind ? { primitiveKind } : {}
|
|
570
|
+
}
|
|
571
|
+
];
|
|
572
|
+
}
|
|
573
|
+
return boundaries.map((b) => ({
|
|
574
|
+
groupId: `agent-group-${b.id}`,
|
|
575
|
+
llmId: `stage-llm-${b.id}`,
|
|
576
|
+
toolId: `stage-tool-${b.id}`,
|
|
577
|
+
label: b.label,
|
|
578
|
+
subflowPath: b.subflowPath,
|
|
579
|
+
...b.primitiveKind ? { primitiveKind: b.primitiveKind } : {}
|
|
580
|
+
}));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/v2/core/selectors/selectTouched.ts
|
|
584
|
+
function selectTouched(visibleSteps) {
|
|
585
|
+
const touched = /* @__PURE__ */ new Set(["user"]);
|
|
586
|
+
for (const s of visibleSteps) {
|
|
587
|
+
if (s.kind === "llm->tool" || s.kind === "tool->llm") touched.add("tool");
|
|
588
|
+
if (s.kind === "user->llm" || s.kind === "tool->llm" || s.kind === "llm->user") {
|
|
589
|
+
touched.add("llm");
|
|
590
|
+
}
|
|
591
|
+
if (s.label.toLowerCase().includes("skill")) touched.add("skill");
|
|
592
|
+
}
|
|
593
|
+
return touched;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/v2/core/selectors/selectEdges.ts
|
|
597
|
+
function stepToStageEndpoints(step, agent) {
|
|
598
|
+
switch (step.kind) {
|
|
599
|
+
case "user->llm":
|
|
600
|
+
return {
|
|
601
|
+
source: "actor-user",
|
|
602
|
+
target: agent.llmId,
|
|
603
|
+
sourceHandle: "user-out",
|
|
604
|
+
targetHandle: "llm-top-in",
|
|
605
|
+
dashed: false
|
|
606
|
+
};
|
|
607
|
+
case "llm->tool":
|
|
608
|
+
return {
|
|
609
|
+
source: agent.llmId,
|
|
610
|
+
target: agent.toolId,
|
|
611
|
+
sourceHandle: "llm-right-out",
|
|
612
|
+
targetHandle: "tool-left-in",
|
|
613
|
+
dashed: false
|
|
614
|
+
};
|
|
615
|
+
case "tool->llm":
|
|
616
|
+
return {
|
|
617
|
+
source: agent.toolId,
|
|
618
|
+
target: agent.llmId,
|
|
619
|
+
sourceHandle: "tool-bottom-out",
|
|
620
|
+
targetHandle: "llm-bottom-in",
|
|
621
|
+
dashed: true
|
|
622
|
+
};
|
|
623
|
+
case "llm->user":
|
|
624
|
+
return {
|
|
625
|
+
source: agent.llmId,
|
|
626
|
+
target: "actor-user",
|
|
627
|
+
sourceHandle: "llm-top-out",
|
|
628
|
+
targetHandle: "user-in",
|
|
629
|
+
dashed: false
|
|
630
|
+
};
|
|
631
|
+
default:
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function stepEdgeLabel(step) {
|
|
636
|
+
if (step.tokens) return `${step.tokens.in}\u2192${step.tokens.out} tok`;
|
|
637
|
+
if (step.toolName) return step.toolName;
|
|
638
|
+
const dur = step.endOffsetMs !== void 0 ? step.endOffsetMs - step.startOffsetMs : 0;
|
|
639
|
+
if (dur > 0) return dur < 1e3 ? `${Math.round(dur)}ms` : `${(dur / 1e3).toFixed(2)}s`;
|
|
640
|
+
return "";
|
|
641
|
+
}
|
|
642
|
+
function selectEdges(visibleSteps, agent) {
|
|
643
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
644
|
+
visibleSteps.forEach((step, idx) => {
|
|
645
|
+
const mapping = stepToStageEndpoints(step, agent);
|
|
646
|
+
if (!mapping) return;
|
|
647
|
+
const id = `${mapping.source}->${mapping.target}`;
|
|
648
|
+
const label = stepEdgeLabel(step);
|
|
649
|
+
const existing = byKey.get(id);
|
|
650
|
+
if (existing) {
|
|
651
|
+
existing.count = existing.count + 1;
|
|
652
|
+
existing.mostRecentIdx = idx;
|
|
653
|
+
if (label) existing.label = label;
|
|
654
|
+
} else {
|
|
655
|
+
byKey.set(id, {
|
|
656
|
+
id,
|
|
657
|
+
source: mapping.source,
|
|
658
|
+
target: mapping.target,
|
|
659
|
+
sourceHandle: mapping.sourceHandle,
|
|
660
|
+
targetHandle: mapping.targetHandle,
|
|
661
|
+
kind: step.kind,
|
|
662
|
+
label,
|
|
663
|
+
count: 1,
|
|
664
|
+
mostRecentIdx: idx,
|
|
665
|
+
dashed: mapping.dashed
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
return [...byKey.values()];
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// src/v2/core/selectors/selectFocusDetail.ts
|
|
673
|
+
function selectFocusDetail(step, log) {
|
|
674
|
+
if (!step) return void 0;
|
|
675
|
+
const isLLM = step.kind === "user->llm" || step.kind === "tool->llm" || step.kind === "llm->user";
|
|
676
|
+
const isTool = step.kind === "llm->tool";
|
|
677
|
+
if (!isLLM && !isTool) return void 0;
|
|
678
|
+
const openType = isLLM ? "agentfootprint.stream.llm_start" : "agentfootprint.stream.tool_start";
|
|
679
|
+
let openIdx = -1;
|
|
680
|
+
let bestDelta = Number.POSITIVE_INFINITY;
|
|
681
|
+
for (let i = 0; i < log.length; i++) {
|
|
682
|
+
const entry = log[i];
|
|
683
|
+
if (entry.event.type !== openType) continue;
|
|
684
|
+
const delta = Math.abs(entry.runOffsetMs - step.startOffsetMs);
|
|
685
|
+
if (delta < bestDelta) {
|
|
686
|
+
bestDelta = delta;
|
|
687
|
+
openIdx = i;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (openIdx === -1) return { stepId: step.id, kind: step.kind };
|
|
691
|
+
const detail = { stepId: step.id, kind: step.kind };
|
|
692
|
+
if (isTool) {
|
|
693
|
+
const openEvent = log[openIdx].event;
|
|
694
|
+
detail.toolArgs = openEvent.payload.args;
|
|
695
|
+
for (let i = openIdx + 1; i < log.length; i++) {
|
|
696
|
+
const e = log[i].event;
|
|
697
|
+
if (e.type === "agentfootprint.stream.tool_end" && e.payload.toolCallId === openEvent.payload.toolCallId) {
|
|
698
|
+
detail.toolResult = String(e.payload.result ?? "");
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return detail;
|
|
703
|
+
}
|
|
704
|
+
for (let i = openIdx + 1; i < log.length; i++) {
|
|
705
|
+
const e = log[i].event;
|
|
706
|
+
if (e.type === "agentfootprint.stream.llm_end") {
|
|
707
|
+
const p = e.payload;
|
|
708
|
+
detail.llmReasoning = p.content;
|
|
709
|
+
detail.tokens = { in: p.usage.input, out: p.usage.output };
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
for (let i = openIdx + 1; i < Math.min(log.length, openIdx + 12); i++) {
|
|
714
|
+
const e = log[i].event;
|
|
715
|
+
if (e.type === "agentfootprint.agent.route_decided") {
|
|
716
|
+
const p = e.payload;
|
|
717
|
+
detail.llmDecision = { route: p.chosen, rationale: p.rationale };
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return detail;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// src/v2/core/selectors/selectStepView.ts
|
|
725
|
+
function selectStepView(args) {
|
|
726
|
+
const { graph, log, focusIndex, drillPath } = args;
|
|
727
|
+
const totalSteps = graph.nodes.length;
|
|
728
|
+
const clampedFocus = Math.min(
|
|
729
|
+
Math.max(0, focusIndex),
|
|
730
|
+
Math.max(0, totalSteps - 1)
|
|
731
|
+
);
|
|
732
|
+
const agents = selectAgentInstances(graph);
|
|
733
|
+
const mode = drillPath.length === 0 ? "top-level" : "drill-down";
|
|
734
|
+
const agentForEdges = drillPath.length > 0 ? agents.find((a) => a.subflowPath.join("/") === drillPath.join("/")) ?? agents[0] : agents[0];
|
|
735
|
+
const isMultiAgentTopLevel = drillPath.length === 0 && agents.length > 1;
|
|
736
|
+
const scopedSteps = drillPath.length === 0 ? isMultiAgentTopLevel ? graph.nodes.filter((s) => s.kind === "subflow" && s.isPrimitiveBoundary === true) : graph.nodes : graph.nodes.filter((s) => startsWith(s.subflowPath, drillPath));
|
|
737
|
+
const scopedFocus = Math.min(clampedFocus, Math.max(0, scopedSteps.length - 1));
|
|
738
|
+
const visibleSteps = scopedSteps.slice(0, scopedFocus + 1);
|
|
739
|
+
const currentStep = visibleSteps[visibleSteps.length - 1];
|
|
740
|
+
const touched = selectTouched(visibleSteps);
|
|
741
|
+
const edges = selectEdges(visibleSteps, agentForEdges);
|
|
742
|
+
const focusMapping = currentStep ? stepToStageEndpoints(currentStep, agentForEdges) : null;
|
|
743
|
+
const activeEdgeKey = focusMapping ? `${focusMapping.source}->${focusMapping.target}` : void 0;
|
|
744
|
+
void selectFocusDetail;
|
|
745
|
+
return {
|
|
746
|
+
mode,
|
|
747
|
+
agents: drillPath.length > 0 ? [agentForEdges] : agents,
|
|
748
|
+
visibleSteps,
|
|
749
|
+
touched,
|
|
750
|
+
edges,
|
|
751
|
+
activeEdgeKey,
|
|
752
|
+
currentStep,
|
|
753
|
+
totalSteps: scopedSteps.length,
|
|
754
|
+
breadcrumb: buildBreadcrumb(drillPath, agents),
|
|
755
|
+
graph
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function startsWith(path, prefix) {
|
|
759
|
+
if (path.length < prefix.length) return false;
|
|
760
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
761
|
+
if (path[i] !== prefix[i]) return false;
|
|
762
|
+
}
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
function buildBreadcrumb(drillPath, agents) {
|
|
766
|
+
const out = [{ id: "", label: "Run" }];
|
|
767
|
+
for (let i = 0; i < drillPath.length; i++) {
|
|
768
|
+
const partial = drillPath.slice(0, i + 1);
|
|
769
|
+
const key = partial.join("/");
|
|
770
|
+
const match = agents.find((a) => a.subflowPath.join("/") === key);
|
|
771
|
+
out.push({ id: key, label: match?.label ?? key });
|
|
772
|
+
}
|
|
773
|
+
return out;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/v2/core/selectors/selectContextEngineeringInjections.ts
|
|
777
|
+
var BASELINE_SOURCES = /* @__PURE__ */ new Set([
|
|
778
|
+
"user",
|
|
779
|
+
"tool-result",
|
|
780
|
+
"assistant",
|
|
781
|
+
"base",
|
|
782
|
+
"registry"
|
|
783
|
+
]);
|
|
784
|
+
function isContextEngineering(inj) {
|
|
785
|
+
return !BASELINE_SOURCES.has(inj.source);
|
|
786
|
+
}
|
|
787
|
+
function selectContextEngineeringInjections(injections) {
|
|
788
|
+
if (!injections || injections.length === 0) return [];
|
|
789
|
+
return injections.filter(isContextEngineering);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
export {
|
|
793
|
+
LensRecorder,
|
|
794
|
+
lensRecorder,
|
|
795
|
+
defaultHumanizer,
|
|
796
|
+
humanizeWith,
|
|
797
|
+
makeTeachingHumanizer,
|
|
798
|
+
teachingHumanizer,
|
|
799
|
+
selectAgentInstances,
|
|
800
|
+
selectTouched,
|
|
801
|
+
stepToStageEndpoints,
|
|
802
|
+
stepEdgeLabel,
|
|
803
|
+
selectEdges,
|
|
804
|
+
selectFocusDetail,
|
|
805
|
+
selectStepView,
|
|
806
|
+
BASELINE_SOURCES,
|
|
807
|
+
isContextEngineering,
|
|
808
|
+
selectContextEngineeringInjections
|
|
809
|
+
};
|
|
810
|
+
//# sourceMappingURL=chunk-MXIMEL7W.js.map
|