agentfootprint-lens 0.5.0 → 0.6.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/README.md +181 -148
- package/dist/{chunk-7KGWX7RU.js → chunk-3N4WQXQP.js} +151 -6
- package/dist/chunk-3N4WQXQP.js.map +1 -0
- package/dist/{chunk-2OGMN63Q.js → chunk-VTJ6OQYJ.js} +648 -144
- package/dist/chunk-VTJ6OQYJ.js.map +1 -0
- package/dist/core.cjs +150 -5
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +121 -0
- package/dist/core.d.ts +121 -0
- package/dist/core.js +1 -1
- package/dist/index.cjs +793 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/react.cjs +793 -143
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +64 -4
- package/dist/react.d.ts +64 -4
- package/dist/react.js +4 -2
- package/package.json +1 -1
- package/dist/chunk-2JKRPWPZ.js +0 -3793
- package/dist/chunk-2JKRPWPZ.js.map +0 -1
- package/dist/chunk-2OGMN63Q.js.map +0 -1
- package/dist/chunk-2ZNJKUV7.js +0 -3781
- package/dist/chunk-2ZNJKUV7.js.map +0 -1
- package/dist/chunk-4LDHJAAY.js +0 -3772
- package/dist/chunk-4LDHJAAY.js.map +0 -1
- package/dist/chunk-7KGWX7RU.js.map +0 -1
- package/dist/chunk-EFG52PIH.js +0 -3783
- package/dist/chunk-EFG52PIH.js.map +0 -1
- package/dist/chunk-FAHWCPLM.js +0 -3930
- package/dist/chunk-FAHWCPLM.js.map +0 -1
- package/dist/chunk-FH3WCJCV.js +0 -3828
- package/dist/chunk-FH3WCJCV.js.map +0 -1
- package/dist/chunk-LOKXVQL6.js +0 -3822
- package/dist/chunk-LOKXVQL6.js.map +0 -1
- package/dist/chunk-OROO4WJY.js +0 -3821
- package/dist/chunk-OROO4WJY.js.map +0 -1
- package/dist/chunk-PPSBDRYH.js +0 -3793
- package/dist/chunk-PPSBDRYH.js.map +0 -1
- package/dist/chunk-ZDXQDQI4.js +0 -3822
- package/dist/chunk-ZDXQDQI4.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,227 +1,260 @@
|
|
|
1
1
|
# agentfootprint-lens
|
|
2
2
|
|
|
3
|
-
> **See
|
|
3
|
+
> **See the context engineering as it happens.**
|
|
4
4
|
>
|
|
5
|
-
> React components for
|
|
5
|
+
> React components for watching agents built on [`agentfootprint`](https://www.npmjs.com/package/agentfootprint). Every injection into the Agent's slots (RAG, Memory, Skills, Instructions, Tools) is tagged inline — students and engineers see exactly what was put into the prompt, by whom, on which iteration. No hidden abstractions.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
### The pitch
|
|
10
|
+
|
|
11
|
+
agentfootprint = **2 primitives (LLM, Agent) + 3 compositions (Sequence, Parallel, Conditional) + N patterns (ReAct, Reflexion, Tree-of-Thoughts...) + cross-cutting context engineering.** Lens is the surface that makes the context engineering visible — not as a "RAG view" or a "Memory view," but as tagged injections inside the ONE Agent card. That's the whole pedagogy.
|
|
6
12
|
|
|
7
13
|
[](https://www.npmjs.com/package/agentfootprint-lens)
|
|
8
14
|
[](./LICENSE)
|
|
9
15
|
|
|
10
16
|
---
|
|
11
17
|
|
|
12
|
-
##
|
|
18
|
+
## 30-second quick start
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
```bash
|
|
21
|
+
npm install agentfootprint agentfootprint-lens
|
|
22
|
+
```
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
```tsx
|
|
25
|
+
import { Agent, anthropic } from 'agentfootprint';
|
|
26
|
+
import { Lens, useLens } from 'agentfootprint-lens';
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
export function App() {
|
|
29
|
+
const agent = useLens(() =>
|
|
30
|
+
Agent.create({ provider: anthropic('claude-sonnet-4') })
|
|
31
|
+
.system('You are a helpful assistant.')
|
|
32
|
+
.build()
|
|
33
|
+
);
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<button onClick={() => agent.run('Hello!')}>Run</button>
|
|
38
|
+
<Lens for={agent} />
|
|
39
|
+
</>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
25
43
|
|
|
26
|
-
|
|
27
|
-
2. **Iteration Strip** — horizontal ribbon of every LLM call in the run. Click to jump.
|
|
28
|
-
3. **Tool Call Inspector** — flat sidebar of every tool invocation across all turns.
|
|
44
|
+
That's it. Two lines — `useLens(...)` + `<Lens for={agent} />` — and you get:
|
|
29
45
|
|
|
30
|
-
|
|
46
|
+
- A live **Messages** view (everything the LLM saw and said, per turn)
|
|
47
|
+
- An **Iteration Strip** (one cell per LLM call, tool call, or decision — scrubbable)
|
|
48
|
+
- A **Tool Call Inspector** (args, result, timing for the currently selected step)
|
|
49
|
+
- A **Decision Scope Ribbon** (which skill / decision rule was active)
|
|
50
|
+
- An **Explainable Trace** tab (the full footprintjs stage-level view)
|
|
31
51
|
|
|
32
|
-
|
|
52
|
+
No event wiring, no timeline prop, no snapshot prop. Lens figures it out by watching the runner directly.
|
|
33
53
|
|
|
34
54
|
---
|
|
35
55
|
|
|
36
|
-
##
|
|
56
|
+
## What you actually see
|
|
37
57
|
|
|
38
|
-
|
|
39
|
-
npm install agentfootprint-lens footprint-explainable-ui
|
|
40
|
-
```
|
|
58
|
+
As the agent runs, the three columns of Lens fill in live:
|
|
41
59
|
|
|
42
|
-
|
|
60
|
+
| Column | Shows |
|
|
61
|
+
|---|---|
|
|
62
|
+
| **Messages** | The conversation from the agent's perspective — system prompt, user turns, assistant replies, tool results |
|
|
63
|
+
| **Iteration Strip** | One row per ReAct loop iteration. Each row lists the LLM call that ran it, the tool calls it picked, and the time each took |
|
|
64
|
+
| **Context** | Whichever iteration or tool call is selected — shows the exact prompt the LLM saw, the tools it had available, and what it returned |
|
|
65
|
+
|
|
66
|
+
When the run finishes, the second tab (**Explainable Trace**) lights up with the full stage-level flowchart — same surface `footprint-explainable-ui` ships, zero extra wiring.
|
|
43
67
|
|
|
44
68
|
---
|
|
45
69
|
|
|
46
|
-
##
|
|
70
|
+
## Multiple watchers, one agent
|
|
71
|
+
|
|
72
|
+
`Lens` doesn't own the agent. Anything can observe it — a Lens, a Datadog exporter, a custom logger, or three of them at once.
|
|
47
73
|
|
|
48
74
|
```tsx
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
const agent = useLens(() => Agent.create(...).build());
|
|
76
|
+
|
|
77
|
+
// Lens in the sidebar
|
|
78
|
+
<Lens for={agent} />
|
|
79
|
+
|
|
80
|
+
// At the same time — ship events to your telemetry backend
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const stop = agent.observe((event) => {
|
|
83
|
+
if (event.type === 'llm_end') {
|
|
84
|
+
telemetry.record('llm.tokens', event.usage?.totalTokens);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return stop; // auto-unsubscribe on unmount
|
|
88
|
+
}, [agent]);
|
|
89
|
+
```
|
|
53
90
|
|
|
54
|
-
|
|
55
|
-
const [dark, setDark] = useState(true);
|
|
56
|
-
const [snapshot, setSnapshot] = useState(null);
|
|
91
|
+
`agent.observe(handler)` is the single subscribe primitive. It returns a `() => void` unsubscribe function. Add as many observers as you want.
|
|
57
92
|
|
|
58
|
-
|
|
59
|
-
const agent = Agent.create({ provider: anthropic('claude-haiku-4-5') })
|
|
60
|
-
.system('You are a helpful agent.')
|
|
61
|
-
.build();
|
|
62
|
-
agent.run('What time is it?').then(() => setSnapshot(agent.getSnapshot()));
|
|
63
|
-
}, []);
|
|
93
|
+
Event shape:
|
|
64
94
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
95
|
+
```ts
|
|
96
|
+
type AgentEvent =
|
|
97
|
+
| { type: 'turn_start'; userMessage: string }
|
|
98
|
+
| { type: 'llm_start'; iteration: number }
|
|
99
|
+
| { type: 'llm_end'; iteration: number; content: string; toolCallCount: number; usage?: TokenUsage; latencyMs: number }
|
|
100
|
+
| { type: 'tool_start'; toolName: string; args: Record<string, unknown> }
|
|
101
|
+
| { type: 'tool_end'; toolName: string; result: { content: string }; latencyMs: number }
|
|
102
|
+
| { type: 'token'; content: string } // streaming
|
|
103
|
+
| { type: 'turn_end'; content: string; iterations: number };
|
|
73
104
|
```
|
|
74
105
|
|
|
75
|
-
That's it. Lens reads the same `FootprintTheme` context explainable-ui reads, so **the consumer owns theming** — flip `coolDark` ↔ `coolLight` at the app root and both the agent view (Lens) and any drill-in trace view (explainable-ui) follow together.
|
|
76
|
-
|
|
77
106
|
---
|
|
78
107
|
|
|
79
|
-
##
|
|
108
|
+
## Works with every agentfootprint runner
|
|
80
109
|
|
|
81
|
-
|
|
110
|
+
`<Lens for={...}>` accepts any agentfootprint runner — the same prop works for all of them, and they all light up Lens identically:
|
|
82
111
|
|
|
83
112
|
```tsx
|
|
84
|
-
|
|
85
|
-
|
|
113
|
+
// Agent — a ReAct loop
|
|
114
|
+
const agent = useLens(() => Agent.create(...).build());
|
|
86
115
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
)}
|
|
100
|
-
</>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
116
|
+
// LLMCall — a single prompt-in, response-out
|
|
117
|
+
const caller = useLens(() => LLMCall.create(...).build());
|
|
118
|
+
|
|
119
|
+
// RAG — retrieve + augment + answer
|
|
120
|
+
const rag = useLens(() => RAG.create(...).retriever(...).build());
|
|
121
|
+
|
|
122
|
+
// Swarm — LLM-routed specialists
|
|
123
|
+
const swarm = useLens(() => Swarm.create(...).build());
|
|
124
|
+
|
|
125
|
+
// ...same pattern for FlowChart, Parallel, Conditional
|
|
126
|
+
|
|
127
|
+
<Lens for={caller} /> // pick whichever
|
|
103
128
|
```
|
|
104
129
|
|
|
105
|
-
|
|
130
|
+
One mental model. The runner does the work; Lens watches.
|
|
106
131
|
|
|
107
132
|
---
|
|
108
133
|
|
|
109
|
-
##
|
|
134
|
+
## Theming
|
|
110
135
|
|
|
111
|
-
|
|
136
|
+
Lens inherits from `footprint-explainable-ui`'s theme system. Two built-in presets, or bring your own:
|
|
112
137
|
|
|
113
138
|
```tsx
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
139
|
+
import { coolDark, coolLight } from 'footprint-explainable-ui';
|
|
140
|
+
import { Lens } from 'agentfootprint-lens';
|
|
141
|
+
|
|
142
|
+
<Lens for={agent} theme={isDark ? coolDark : coolLight} />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Pass any `ThemeTokens` object. CSS vars work too — handy if your app already flips theme at the `:root` level:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
<Lens
|
|
149
|
+
for={agent}
|
|
150
|
+
theme={{
|
|
151
|
+
colors: {
|
|
152
|
+
bgPrimary: 'var(--my-bg)',
|
|
153
|
+
textPrimary: 'var(--my-fg)',
|
|
154
|
+
// …
|
|
155
|
+
},
|
|
156
|
+
}}
|
|
117
157
|
/>
|
|
118
158
|
```
|
|
119
159
|
|
|
120
|
-
|
|
121
|
-
- `runtimeSnapshot` (any | null) — raw output of `agent.getSnapshot()`. Null renders an empty state.
|
|
122
|
-
- `timeline` (`AgentTimeline`) — pre-parsed timeline, overrides `runtimeSnapshot`. Useful for sharing across multiple Lens instances.
|
|
123
|
-
- `systemPrompt` (string) — override the system-prompt preview in MessagesPanel. Auto-derived from snapshot otherwise.
|
|
124
|
-
- `onToolCallClick` ((`AgentToolInvocation`) => void) — fires when any tool-call card is clicked.
|
|
160
|
+
---
|
|
125
161
|
|
|
126
|
-
|
|
162
|
+
## Responsive
|
|
127
163
|
|
|
128
|
-
|
|
129
|
-
- `<IterationStrip timeline selectedKey onSelect />`
|
|
130
|
-
- `<ToolCallInspector timeline selectedId onSelect />`
|
|
164
|
+
Lens resizes to whatever space you give it. Below ~640px wide it stacks panels vertically (like `<ExplainableShell>` does). Drop it in a splitter, a drawer, or a full-screen tab — no config needed.
|
|
131
165
|
|
|
132
|
-
|
|
166
|
+
---
|
|
133
167
|
|
|
134
|
-
|
|
168
|
+
## Escape hatches
|
|
135
169
|
|
|
136
|
-
|
|
170
|
+
If you want to manage the timeline yourself (custom ingestion, recording to a file, replaying a stored run), the explicit path is still available:
|
|
137
171
|
|
|
138
|
-
|
|
172
|
+
```tsx
|
|
173
|
+
import { Lens, useLiveTimeline } from 'agentfootprint-lens';
|
|
174
|
+
|
|
175
|
+
const lens = useLiveTimeline();
|
|
176
|
+
|
|
177
|
+
// You control ingestion
|
|
178
|
+
for (const event of storedEvents) lens.ingest(event);
|
|
179
|
+
|
|
180
|
+
<Lens
|
|
181
|
+
timeline={lens.timeline}
|
|
182
|
+
runtimeSnapshot={storedSnapshot}
|
|
183
|
+
/>
|
|
184
|
+
```
|
|
139
185
|
|
|
140
186
|
---
|
|
141
187
|
|
|
142
|
-
##
|
|
188
|
+
## Recorder pattern (power users)
|
|
143
189
|
|
|
144
|
-
|
|
190
|
+
For advanced observability — multiple exporters, buffering, filtering before dispatch — agentfootprint's recorder system is still there:
|
|
145
191
|
|
|
146
192
|
```ts
|
|
147
|
-
|
|
148
|
-
turns: AgentTurn[]; // one per agent.run() call
|
|
149
|
-
messages: AgentMessage[]; // full flat conversation
|
|
150
|
-
tools: AgentToolInvocation[]; // every tool call, across turns
|
|
151
|
-
finalDecision: Record<string, unknown>;
|
|
152
|
-
rawSnapshot: unknown; // escape hatch
|
|
153
|
-
}
|
|
193
|
+
import { createStreamEventRecorder } from 'agentfootprint';
|
|
154
194
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
iterations: AgentIteration[];
|
|
159
|
-
finalContent: string;
|
|
160
|
-
totalInputTokens: number;
|
|
161
|
-
totalOutputTokens: number;
|
|
162
|
-
totalDurationMs: number;
|
|
163
|
-
}
|
|
195
|
+
const myRec = createStreamEventRecorder(myHandler, 'my-telemetry');
|
|
196
|
+
const agent = Agent.create(...).recorder(myRec).build();
|
|
197
|
+
```
|
|
164
198
|
|
|
165
|
-
|
|
166
|
-
index: number;
|
|
167
|
-
model?: string;
|
|
168
|
-
inputTokens?: number;
|
|
169
|
-
outputTokens?: number;
|
|
170
|
-
durationMs?: number;
|
|
171
|
-
stopReason?: string;
|
|
172
|
-
assistantContent: string;
|
|
173
|
-
toolCalls: AgentToolInvocation[];
|
|
174
|
-
decisionAtStart: Record<string, unknown>;
|
|
175
|
-
matchedInstructions?: string[];
|
|
176
|
-
visibleTools: string[]; // tool names visible to the LLM that iter
|
|
177
|
-
}
|
|
199
|
+
`<Lens for={...}>` is just sugar over this internally — the recorder you'd write for Datadog is the same shape Lens uses.
|
|
178
200
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## API reference
|
|
204
|
+
|
|
205
|
+
### `useLens(factory)`
|
|
206
|
+
|
|
207
|
+
Memoizes a runner across renders. Call `factory` exactly once on mount; reuses the same instance forever. Works for any agentfootprint runner — `Agent`, `LLMCall`, `RAG`, `Swarm`, `FlowChart`, `Parallel`, `Conditional`.
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
const agent = useLens(() => Agent.create(...).build());
|
|
211
|
+
const caller = useLens(() => LLMCall.create(...).build());
|
|
212
|
+
const rag = useLens(() => RAG.create(...).build());
|
|
190
213
|
```
|
|
191
214
|
|
|
192
|
-
|
|
215
|
+
### `<Lens for={runner} />`
|
|
193
216
|
|
|
194
|
-
|
|
217
|
+
The one-prop integration. Subscribes to the runner's events, watches its snapshot, renders both tabs.
|
|
195
218
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
219
|
+
| Prop | Type | Description |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `for` | `Runner` (any agentfootprint runner) | The agent / caller / swarm / etc. to watch. |
|
|
222
|
+
| `theme` | `ThemeTokens?` | Optional — defaults to `coolDark`. |
|
|
223
|
+
| `appName` | `string?` | Optional brand label in the tab strip. |
|
|
200
224
|
|
|
201
|
-
###
|
|
202
|
-
- **Prompt Composer** — assembled system prompt per iteration with diff-across-iterations highlighting (base + skill body + instruction injections + message window + tool list).
|
|
203
|
-
- **Decision Scope Ribbon** — horizontal pill timeline of decision scope fields; arrows back to the tool call that wrote each via `decisionUpdate`.
|
|
225
|
+
### `runner.observe(handler)`
|
|
204
226
|
|
|
205
|
-
|
|
206
|
-
- **Cost & Latency Attribution** — token burn curve, per-iter/per-tool breakdown, pluggable price table.
|
|
207
|
-
- **Skill Dock** — loaded skills, activation state, tools-per-skill.
|
|
227
|
+
Subscribe to live events. Returns `() => void` (unsubscribe).
|
|
208
228
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
229
|
+
```ts
|
|
230
|
+
const stop = agent.observe((event) => { /* ... */ });
|
|
231
|
+
// later:
|
|
232
|
+
stop();
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### `runner.getSnapshot()`, `runner.getNarrativeEntries()`, `runner.getSpec()`
|
|
236
|
+
|
|
237
|
+
The standard agentfootprint introspection methods. `<Lens for={...}>` reads these automatically. You only call them yourself if you're building a custom UI.
|
|
238
|
+
|
|
239
|
+
### `useLiveTimeline()` (escape hatch)
|
|
240
|
+
|
|
241
|
+
Returns `{ timeline, ingest, startTurn, reset, builder }`. Use when you want to feed Lens from a non-runner source (replayed logs, server-sent events, etc.).
|
|
213
242
|
|
|
214
243
|
---
|
|
215
244
|
|
|
216
|
-
##
|
|
245
|
+
## Why this design
|
|
246
|
+
|
|
247
|
+
**The runner is the single source of truth.** Agents fire events as they work. Lens subscribes to those events. Telemetry exporters subscribe to those events. CLI loggers subscribe to those events. Nobody owns the runner; everyone can watch it.
|
|
248
|
+
|
|
249
|
+
This is the observer pattern, applied consistently across every agentfootprint runner. The outcome:
|
|
217
250
|
|
|
218
|
-
- **
|
|
219
|
-
- **
|
|
220
|
-
- **
|
|
221
|
-
- **
|
|
251
|
+
- **One line to integrate** — `<Lens for={agent} />`
|
|
252
|
+
- **Zero coupling** — the agent doesn't know Lens exists
|
|
253
|
+
- **Composable** — Lens + your telemetry + your logger all watch the same agent with no conflict
|
|
254
|
+
- **Uniform** — any runner works with any observer
|
|
222
255
|
|
|
223
256
|
---
|
|
224
257
|
|
|
225
258
|
## License
|
|
226
259
|
|
|
227
|
-
MIT
|
|
260
|
+
MIT
|
|
@@ -3,10 +3,30 @@ var LiveTimelineBuilder = class {
|
|
|
3
3
|
constructor() {
|
|
4
4
|
this.turns = [];
|
|
5
5
|
this.currentTurn = null;
|
|
6
|
+
/**
|
|
7
|
+
* The most-recently-started iteration. Stays bound through the entire
|
|
8
|
+
* iteration lifecycle — llm_start opens it, llm_end ends the LLM phase
|
|
9
|
+
* but the iter stays current so subsequent `tool_start` / `tool_end`
|
|
10
|
+
* events (which fire AFTER llm_end in the agent loop) still attach to
|
|
11
|
+
* it. Cleared only on `commitCurrentTurn()` / `reset()`.
|
|
12
|
+
*/
|
|
6
13
|
this.currentIter = null;
|
|
14
|
+
/**
|
|
15
|
+
* True between an iteration's `llm_start` and its `llm_end`. Drives
|
|
16
|
+
* context-injection routing: events emitted while the LLM phase is
|
|
17
|
+
* active belong to THIS iteration's prompt; events emitted after
|
|
18
|
+
* `llm_end` belong to the NEXT iteration (they shape its context).
|
|
19
|
+
* Tool start/end ignore this flag — they always attach to the
|
|
20
|
+
* most-recent iter regardless of LLM phase.
|
|
21
|
+
*/
|
|
22
|
+
this.llmPhaseActive = false;
|
|
7
23
|
this.toolByCallId = /* @__PURE__ */ new Map();
|
|
8
24
|
this.messages = [];
|
|
9
25
|
this.finalDecision = {};
|
|
26
|
+
/** Context injections that fired BEFORE this turn's first llm_start —
|
|
27
|
+
* they shape iteration 1's context. Flushed onto iteration 1 at its
|
|
28
|
+
* llm_start. Reset at turn_start so each turn accumulates its own. */
|
|
29
|
+
this.pendingPreIterInjections = [];
|
|
10
30
|
}
|
|
11
31
|
/**
|
|
12
32
|
* Begin a new turn. Call this BEFORE `agent.run(userPrompt)` so the
|
|
@@ -25,6 +45,7 @@ var LiveTimelineBuilder = class {
|
|
|
25
45
|
totalDurationMs: 0,
|
|
26
46
|
startMs: Date.now()
|
|
27
47
|
};
|
|
48
|
+
this.pendingPreIterInjections = [];
|
|
28
49
|
this.messages.push({ role: "user", content: userPrompt });
|
|
29
50
|
}
|
|
30
51
|
/**
|
|
@@ -42,6 +63,12 @@ var LiveTimelineBuilder = class {
|
|
|
42
63
|
const e = event;
|
|
43
64
|
if (!e || typeof e.type !== "string") return;
|
|
44
65
|
switch (e.type) {
|
|
66
|
+
case "turn_start":
|
|
67
|
+
case "agentfootprint.agent.turn_start": {
|
|
68
|
+
const userMessage = e.userMessage ?? "";
|
|
69
|
+
this.startTurn(userMessage);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
45
72
|
case "llm_start":
|
|
46
73
|
case "agentfootprint.stream.llm_start":
|
|
47
74
|
this.onLLMStart(e);
|
|
@@ -63,12 +90,39 @@ var LiveTimelineBuilder = class {
|
|
|
63
90
|
this.commitCurrentTurn();
|
|
64
91
|
return;
|
|
65
92
|
default:
|
|
93
|
+
if (typeof e.type === "string" && e.type.startsWith("agentfootprint.context.")) {
|
|
94
|
+
this.onContextInjection(e.type, e);
|
|
95
|
+
}
|
|
66
96
|
return;
|
|
67
97
|
}
|
|
68
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Route a context-engineering event to the iteration it shaped.
|
|
101
|
+
*
|
|
102
|
+
* Routing rule: the `llmPhaseActive` flag (true between this iter's
|
|
103
|
+
* llm_start and llm_end) decides. While active, the event shaped THIS
|
|
104
|
+
* iter's prompt → attach directly. While inactive (between llm_end
|
|
105
|
+
* and the next llm_start, or before the first llm_start), the event
|
|
106
|
+
* is preparing context for the NEXT iter → queue on
|
|
107
|
+
* `pendingPreIterInjections`, flushed onto the next iter at its
|
|
108
|
+
* llm_start. Tool events do not gate on this flag — they bind to the
|
|
109
|
+
* most-recent iter unconditionally.
|
|
110
|
+
*/
|
|
111
|
+
onContextInjection(rawName, e) {
|
|
112
|
+
if (!this.currentTurn) return;
|
|
113
|
+
const injection = buildInjection(rawName, e);
|
|
114
|
+
if (!injection) return;
|
|
115
|
+
if (this.currentIter && this.llmPhaseActive) {
|
|
116
|
+
this.currentIter.contextInjections.push(injection);
|
|
117
|
+
} else {
|
|
118
|
+
this.pendingPreIterInjections.push(injection);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
69
121
|
onLLMStart(e) {
|
|
70
122
|
if (!this.currentTurn) return;
|
|
71
123
|
const iterNum = e.iteration ?? this.currentTurn.iterations.length + 1;
|
|
124
|
+
const carriedInjections = this.pendingPreIterInjections;
|
|
125
|
+
this.pendingPreIterInjections = [];
|
|
72
126
|
this.currentIter = {
|
|
73
127
|
index: iterNum,
|
|
74
128
|
assistantContent: "",
|
|
@@ -78,9 +132,11 @@ var LiveTimelineBuilder = class {
|
|
|
78
132
|
startMs: Date.now(),
|
|
79
133
|
// Freeze the message count here so "What Neo saw" can reproduce
|
|
80
134
|
// the context window at this exact iteration later.
|
|
81
|
-
messagesSentCount: this.messages.length
|
|
135
|
+
messagesSentCount: this.messages.length,
|
|
136
|
+
contextInjections: carriedInjections
|
|
82
137
|
};
|
|
83
138
|
this.currentTurn.iterations.push(this.currentIter);
|
|
139
|
+
this.llmPhaseActive = true;
|
|
84
140
|
}
|
|
85
141
|
onLLMEnd(e) {
|
|
86
142
|
if (!this.currentIter || !this.currentTurn) return;
|
|
@@ -99,6 +155,7 @@ var LiveTimelineBuilder = class {
|
|
|
99
155
|
if ((e.toolCallCount ?? 0) === 0) {
|
|
100
156
|
this.currentTurn.finalContent = this.currentIter.assistantContent;
|
|
101
157
|
}
|
|
158
|
+
this.llmPhaseActive = false;
|
|
102
159
|
}
|
|
103
160
|
onToolStart(e) {
|
|
104
161
|
if (!this.currentIter || !this.currentTurn) return;
|
|
@@ -132,6 +189,7 @@ var LiveTimelineBuilder = class {
|
|
|
132
189
|
this.turns.push(this.currentTurn);
|
|
133
190
|
this.currentTurn = null;
|
|
134
191
|
this.currentIter = null;
|
|
192
|
+
this.llmPhaseActive = false;
|
|
135
193
|
}
|
|
136
194
|
/**
|
|
137
195
|
* Snapshot the current state as an immutable `AgentTimeline`. Safe to
|
|
@@ -143,12 +201,44 @@ var LiveTimelineBuilder = class {
|
|
|
143
201
|
if (this.currentTurn) allTurns.push(this.currentTurn);
|
|
144
202
|
const tools = [];
|
|
145
203
|
const frozenTurns = allTurns.map((t) => {
|
|
204
|
+
const turnInjections = [];
|
|
205
|
+
const turnLedger = {};
|
|
146
206
|
const iterations = t.iterations.map((i) => {
|
|
147
207
|
const tcs = i.toolCalls.map((tc) => ({ ...tc }));
|
|
148
208
|
tools.push(...tcs);
|
|
149
|
-
|
|
209
|
+
const contextInjections = i.contextInjections.map(
|
|
210
|
+
(ci) => ({ ...ci })
|
|
211
|
+
);
|
|
212
|
+
const contextLedger = {};
|
|
213
|
+
for (const ci of contextInjections) {
|
|
214
|
+
const d = ci.deltaCount;
|
|
215
|
+
if (!d) continue;
|
|
216
|
+
for (const [key, val] of Object.entries(d)) {
|
|
217
|
+
if (typeof val === "number") {
|
|
218
|
+
const prev = typeof contextLedger[key] === "number" ? contextLedger[key] : 0;
|
|
219
|
+
contextLedger[key] = prev + val;
|
|
220
|
+
const prevTurn = typeof turnLedger[key] === "number" ? turnLedger[key] : 0;
|
|
221
|
+
turnLedger[key] = prevTurn + val;
|
|
222
|
+
} else if (typeof val === "boolean") {
|
|
223
|
+
contextLedger[key] = contextLedger[key] === true || val;
|
|
224
|
+
turnLedger[key] = turnLedger[key] === true || val;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
turnInjections.push(...contextInjections);
|
|
229
|
+
return {
|
|
230
|
+
...i,
|
|
231
|
+
toolCalls: tcs,
|
|
232
|
+
contextInjections,
|
|
233
|
+
contextLedger
|
|
234
|
+
};
|
|
150
235
|
});
|
|
151
|
-
return {
|
|
236
|
+
return {
|
|
237
|
+
...t,
|
|
238
|
+
iterations,
|
|
239
|
+
contextInjections: turnInjections,
|
|
240
|
+
contextLedger: turnLedger
|
|
241
|
+
};
|
|
152
242
|
});
|
|
153
243
|
return {
|
|
154
244
|
turns: frozenTurns,
|
|
@@ -175,11 +265,56 @@ var LiveTimelineBuilder = class {
|
|
|
175
265
|
this.turns = [];
|
|
176
266
|
this.currentTurn = null;
|
|
177
267
|
this.currentIter = null;
|
|
268
|
+
this.llmPhaseActive = false;
|
|
178
269
|
this.toolByCallId.clear();
|
|
179
270
|
this.messages = [];
|
|
180
271
|
this.finalDecision = {};
|
|
272
|
+
this.pendingPreIterInjections = [];
|
|
181
273
|
}
|
|
182
274
|
};
|
|
275
|
+
function buildInjection(name, e) {
|
|
276
|
+
const suffix = name.slice("agentfootprint.context.".length);
|
|
277
|
+
const payload = e.payload;
|
|
278
|
+
const data = payload && typeof payload === "object" ? payload : e ?? {};
|
|
279
|
+
const role = typeof data.role === "string" ? data.role : void 0;
|
|
280
|
+
const targetIndex = typeof data.targetIndex === "number" ? data.targetIndex : void 0;
|
|
281
|
+
const deltaCount = data.deltaCount && typeof data.deltaCount === "object" ? data.deltaCount : void 0;
|
|
282
|
+
const enrich = (base) => ({
|
|
283
|
+
...base,
|
|
284
|
+
...role !== void 0 && { role },
|
|
285
|
+
...targetIndex !== void 0 && { targetIndex },
|
|
286
|
+
...deltaCount !== void 0 && { deltaCount },
|
|
287
|
+
payload: data
|
|
288
|
+
});
|
|
289
|
+
switch (suffix) {
|
|
290
|
+
case "rag.chunks": {
|
|
291
|
+
const chunkCount = Number(data.chunkCount ?? 0);
|
|
292
|
+
const topScore = typeof data.topScore === "number" ? data.topScore : void 0;
|
|
293
|
+
const label = chunkCount > 0 ? `${chunkCount} chunk${chunkCount === 1 ? "" : "s"}${topScore !== void 0 ? ` \xB7 top ${topScore.toFixed(2)}` : ""}` : "0 chunks";
|
|
294
|
+
return enrich({ source: "rag", slot: "messages", label });
|
|
295
|
+
}
|
|
296
|
+
case "skill.activated": {
|
|
297
|
+
const skillId = String(data.skillId ?? "skill");
|
|
298
|
+
return enrich({ source: "skill", slot: "system-prompt", label: skillId });
|
|
299
|
+
}
|
|
300
|
+
case "memory.injected": {
|
|
301
|
+
const count = Number(data.count ?? 0);
|
|
302
|
+
const label = count > 0 ? `memory \xB7 ${count} msg${count === 1 ? "" : "s"}` : "memory";
|
|
303
|
+
return enrich({ source: "memory", slot: "messages", label });
|
|
304
|
+
}
|
|
305
|
+
case "instructions.fired": {
|
|
306
|
+
const count = Number(data.count ?? (Array.isArray(data.ids) ? data.ids.length : 1));
|
|
307
|
+
const label = `${count} instruction${count === 1 ? "" : "s"}`;
|
|
308
|
+
return enrich({ source: "instructions", slot: "system-prompt", label });
|
|
309
|
+
}
|
|
310
|
+
default:
|
|
311
|
+
return enrich({
|
|
312
|
+
source: suffix.split(".")[0] || "context",
|
|
313
|
+
slot: "messages",
|
|
314
|
+
label: suffix
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
183
318
|
|
|
184
319
|
// src/core/fromAgentSnapshot.ts
|
|
185
320
|
function fromAgentSnapshot(runtime) {
|
|
@@ -353,7 +488,12 @@ function assembleTurns(messages, llmCalls, toolExecs, instructionEvals, toolReso
|
|
|
353
488
|
decisionAtStart: {},
|
|
354
489
|
// TODO(phase-2): derive from pre-iter commit
|
|
355
490
|
...instr?.matchedInstructions && { matchedInstructions: instr.matchedInstructions },
|
|
356
|
-
visibleTools: visible?.visibleTools ?? []
|
|
491
|
+
visibleTools: visible?.visibleTools ?? [],
|
|
492
|
+
// Post-process path (snapshot-import — no live emit stream) has no
|
|
493
|
+
// way to reconstruct context-injection timing. Leave empty; the
|
|
494
|
+
// live path via LiveTimelineBuilder fills these in naturally.
|
|
495
|
+
contextInjections: [],
|
|
496
|
+
contextLedger: {}
|
|
357
497
|
};
|
|
358
498
|
currentTurn.iterations.push(iteration);
|
|
359
499
|
if (!toolCalls.length) currentTurn.finalContent = iteration.assistantContent;
|
|
@@ -382,7 +522,12 @@ function finalizeTurn(t) {
|
|
|
382
522
|
finalContent: t.finalContent,
|
|
383
523
|
totalInputTokens,
|
|
384
524
|
totalOutputTokens,
|
|
385
|
-
totalDurationMs
|
|
525
|
+
totalDurationMs,
|
|
526
|
+
// Post-process snapshot path has no live emit timing, so the
|
|
527
|
+
// turn-level context fields stay empty here too — the live
|
|
528
|
+
// LiveTimelineBuilder path is the one that captures injections.
|
|
529
|
+
contextInjections: [],
|
|
530
|
+
contextLedger: {}
|
|
386
531
|
};
|
|
387
532
|
}
|
|
388
533
|
|
|
@@ -544,4 +689,4 @@ export {
|
|
|
544
689
|
fromAgentSnapshot,
|
|
545
690
|
deriveStages
|
|
546
691
|
};
|
|
547
|
-
//# sourceMappingURL=chunk-
|
|
692
|
+
//# sourceMappingURL=chunk-3N4WQXQP.js.map
|