agentfootprint-lens 0.6.0 → 0.7.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.
@@ -0,0 +1,4973 @@
1
+ import {
2
+ LiveTimelineBuilder,
3
+ deriveStages,
4
+ fromAgentSnapshot
5
+ } from "./chunk-3N4WQXQP.js";
6
+
7
+ // src/react/Lens.tsx
8
+ import { useEffect as useEffect5, useReducer, useState as useState7 } from "react";
9
+ import { ExplainableShell, FootprintTheme } from "footprint-explainable-ui";
10
+
11
+ // src/react/AgentLens.tsx
12
+ import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState4 } from "react";
13
+
14
+ // src/react/panels/MessagesPanel.tsx
15
+ import React, { useEffect, useRef, useState } from "react";
16
+
17
+ // src/react/theme/useLensTheme.ts
18
+ import { useFootprintTheme, coolDark } from "footprint-explainable-ui";
19
+ function useLensTheme() {
20
+ const ctx = useFootprintTheme();
21
+ return resolve(ctx);
22
+ }
23
+ function resolve(tokens) {
24
+ const t = tokens ?? {};
25
+ const c = t.colors ?? coolDark.colors ?? {};
26
+ const fallback = coolDark.colors ?? {};
27
+ return {
28
+ bg: c.bgPrimary ?? fallback.bgPrimary ?? "#0f172a",
29
+ bgElev: c.bgSecondary ?? fallback.bgSecondary ?? "#1e293b",
30
+ bgHover: c.bgTertiary ?? fallback.bgTertiary ?? "#334155",
31
+ border: c.border ?? fallback.border ?? "#334155",
32
+ borderStrong: c.bgTertiary ?? fallback.bgTertiary ?? "#334155",
33
+ text: c.textPrimary ?? fallback.textPrimary ?? "#f8fafc",
34
+ textMuted: c.textSecondary ?? fallback.textSecondary ?? "#94a3b8",
35
+ textSubtle: c.textMuted ?? fallback.textMuted ?? "#64748b",
36
+ accent: c.primary ?? fallback.primary ?? "#6366f1",
37
+ success: c.success ?? fallback.success ?? "#22c55e",
38
+ warning: c.warning ?? fallback.warning ?? "#f59e0b",
39
+ error: c.error ?? fallback.error ?? "#ef4444",
40
+ fontSans: t.fontFamily?.sans ?? coolDark.fontFamily?.sans ?? "system-ui, sans-serif",
41
+ fontMono: t.fontFamily?.mono ?? coolDark.fontFamily?.mono ?? "ui-monospace, monospace",
42
+ radius: t.radius ?? coolDark.radius ?? "8px"
43
+ };
44
+ }
45
+
46
+ // src/react/panels/MessagesPanel.tsx
47
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
48
+ function MessagesPanel({
49
+ timeline,
50
+ onToolCallClick,
51
+ systemPrompt,
52
+ selectedIterKey,
53
+ stages,
54
+ focusIndex,
55
+ onFocusChange,
56
+ isLive
57
+ }) {
58
+ const t = useLensTheme();
59
+ const scrollRef = useRef(null);
60
+ const iterRanges = useIterationStageRanges(stages);
61
+ useEffect(() => {
62
+ if (!scrollRef.current) {
63
+ console.log("[Lens scroll] selectedIter effect skipped: no scrollRef");
64
+ return;
65
+ }
66
+ if (selectedIterKey) {
67
+ const target = scrollRef.current.querySelector(
68
+ `[data-iter-key="${CSS.escape(selectedIterKey)}"]`
69
+ );
70
+ console.log("[Lens scroll] selectedIterKey path", {
71
+ selectedIterKey,
72
+ foundTarget: !!target
73
+ });
74
+ if (target) {
75
+ target.scrollIntoView({ block: "start", behavior: "smooth" });
76
+ target.setAttribute("data-iter-selected", "true");
77
+ const h = window.setTimeout(() => target.removeAttribute("data-iter-selected"), 1200);
78
+ return () => window.clearTimeout(h);
79
+ }
80
+ }
81
+ }, [selectedIterKey]);
82
+ useEffect(() => {
83
+ const ctx = {
84
+ focusIndex,
85
+ isLive,
86
+ stagesLen: stages?.length ?? 0,
87
+ iterRangesSize: iterRanges.size,
88
+ hasScrollRef: !!scrollRef.current
89
+ };
90
+ if (!scrollRef.current || focusIndex === void 0 || !stages?.length) {
91
+ console.log("[Lens scroll] focus effect SKIPPED", ctx);
92
+ return;
93
+ }
94
+ if (isLive) {
95
+ const el = scrollRef.current;
96
+ const beforeTop2 = el.scrollTop;
97
+ const sh = el.scrollHeight;
98
+ const ch = el.clientHeight;
99
+ const canScroll = sh > ch;
100
+ const alreadyAtBottom = Math.abs(sh - ch - beforeTop2) < 2;
101
+ el.scrollTo({ top: sh, behavior: "auto" });
102
+ const afterTop = el.scrollTop;
103
+ const moved = afterTop !== beforeTop2;
104
+ console.log(
105
+ `[Lens scroll] LIVE tail \u2192 ${moved ? "moved" : canScroll ? "NO-OP (scrollTo returned same top)" : alreadyAtBottom ? "already at bottom" : "CONTAINER NOT SCROLLABLE (scrollHeight<=clientHeight)"} \xB7 sh=${sh} ch=${ch} before=${beforeTop2} after=${afterTop}`,
106
+ { ...ctx, scrollHeight: sh, clientHeight: ch, beforeScrollTop: beforeTop2, afterScrollTop: afterTop }
107
+ );
108
+ return;
109
+ }
110
+ const key = keyForStage(iterRanges, focusIndex);
111
+ if (!key) {
112
+ console.log("[Lens scroll] focus effect: no iterKey for focusIndex", ctx);
113
+ return;
114
+ }
115
+ const target = scrollRef.current.querySelector(
116
+ `[data-iter-key="${CSS.escape(key)}"]`
117
+ );
118
+ if (!target) {
119
+ console.log("[Lens scroll] focus effect: DOM node missing for key", {
120
+ ...ctx,
121
+ key
122
+ });
123
+ return;
124
+ }
125
+ const container = scrollRef.current;
126
+ const rect = target.getBoundingClientRect();
127
+ const cRect = container.getBoundingClientRect();
128
+ const relTop = rect.top - cRect.top;
129
+ const beforeTop = container.scrollTop;
130
+ target.scrollIntoView({ block: "start", behavior: "smooth" });
131
+ console.log(
132
+ `[Lens scroll] SCRUB key=${key} \xB7 relTop=${Math.round(relTop)} sh=${container.scrollHeight} ch=${container.clientHeight} before=${beforeTop}`,
133
+ { ...ctx, key }
134
+ );
135
+ }, [focusIndex, isLive, iterRanges, stages?.length]);
136
+ return /* @__PURE__ */ jsxs(
137
+ "div",
138
+ {
139
+ ref: scrollRef,
140
+ "data-fp-lens": "messages-panel",
141
+ style: {
142
+ // Absolute + inset:0 inside a `position: relative` wrapper
143
+ // forces the panel to be EXACTLY the size of its wrapper cell,
144
+ // regardless of flex/grid height resolution quirks upstream.
145
+ // Without this, percentage heights and 1fr rows can fail to
146
+ // resolve and the panel grows to its content → no scroll.
147
+ // The grid-area wrapper in AgentLens sets `position: relative`
148
+ // to make this the containing block.
149
+ position: "absolute",
150
+ inset: 0,
151
+ display: "flex",
152
+ flexDirection: "column",
153
+ gap: 16,
154
+ padding: "16px 24px",
155
+ background: t.bg,
156
+ color: t.text,
157
+ fontFamily: t.fontSans,
158
+ fontSize: 14,
159
+ lineHeight: 1.55,
160
+ minHeight: 0,
161
+ overflow: "auto"
162
+ },
163
+ children: [
164
+ systemPrompt && /* @__PURE__ */ jsx(SystemBubble, { text: systemPrompt }),
165
+ timeline.turns.map((turn) => /* @__PURE__ */ jsx(
166
+ TurnBlock,
167
+ {
168
+ turn,
169
+ allMessages: timeline.messages,
170
+ onToolCallClick,
171
+ iterRanges,
172
+ focusIndex,
173
+ stages,
174
+ onFocusChange
175
+ },
176
+ turn.index
177
+ ))
178
+ ]
179
+ }
180
+ );
181
+ }
182
+ function SystemBubble({ text }) {
183
+ const t = useLensTheme();
184
+ const [open, setOpen] = useState(false);
185
+ const preview = text.slice(0, 140).replace(/\s+/g, " ") + (text.length > 140 ? "\u2026" : "");
186
+ return /* @__PURE__ */ jsxs(
187
+ "div",
188
+ {
189
+ style: {
190
+ alignSelf: "center",
191
+ width: "100%",
192
+ maxWidth: 880,
193
+ border: `1px dashed ${t.border}`,
194
+ borderRadius: t.radius,
195
+ padding: "8px 12px",
196
+ background: t.bgElev,
197
+ fontSize: 12,
198
+ color: t.textMuted
199
+ },
200
+ children: [
201
+ /* @__PURE__ */ jsxs(
202
+ "button",
203
+ {
204
+ onClick: () => setOpen((v) => !v),
205
+ style: {
206
+ background: "transparent",
207
+ border: "none",
208
+ color: t.textMuted,
209
+ cursor: "pointer",
210
+ padding: 0,
211
+ font: "inherit"
212
+ },
213
+ children: [
214
+ /* @__PURE__ */ jsx("strong", { children: "How Neo is configured" }),
215
+ " ",
216
+ open ? "\u25BE" : "\u25B8",
217
+ " ",
218
+ open ? "" : preview
219
+ ]
220
+ }
221
+ ),
222
+ open && /* @__PURE__ */ jsx(
223
+ "pre",
224
+ {
225
+ style: {
226
+ marginTop: 8,
227
+ whiteSpace: "pre-wrap",
228
+ fontFamily: t.fontMono,
229
+ fontSize: 11,
230
+ color: t.text
231
+ },
232
+ children: text
233
+ }
234
+ )
235
+ ]
236
+ }
237
+ );
238
+ }
239
+ function TurnBlock({
240
+ turn,
241
+ allMessages,
242
+ onToolCallClick,
243
+ iterRanges,
244
+ focusIndex,
245
+ stages,
246
+ onFocusChange
247
+ }) {
248
+ const t = useLensTheme();
249
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
250
+ /* @__PURE__ */ jsx(TurnHeader, { turn }),
251
+ /* @__PURE__ */ jsx(UserBubble, { text: turn.userPrompt }),
252
+ turn.iterations.map((iter, i) => {
253
+ const key = `${turn.index}.${iter.index}`;
254
+ const range = iterRanges?.get(key);
255
+ let state = "future";
256
+ if (range && focusIndex !== void 0) {
257
+ if (focusIndex < range.firstStageIndex) state = "future";
258
+ else if (focusIndex > range.lastStageIndex) state = "past";
259
+ else state = "active";
260
+ } else if (!range && focusIndex === void 0) {
261
+ state = "active";
262
+ }
263
+ return /* @__PURE__ */ jsx(
264
+ IterationBlock,
265
+ {
266
+ iter,
267
+ iterPositionInTurn: i + 1,
268
+ turnIndex: turn.index,
269
+ allMessages,
270
+ onToolCallClick,
271
+ state,
272
+ stages,
273
+ range,
274
+ focusIndex,
275
+ ...range && onFocusChange ? { onClick: () => onFocusChange(range.lastStageIndex) } : {}
276
+ },
277
+ iter.index
278
+ );
279
+ }),
280
+ turn.finalContent && turn.iterations.length > 0 && /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: t.textSubtle, textAlign: "center" }, children: [
281
+ "Answer compiled \xB7 ",
282
+ turn.iterations.length,
283
+ " step",
284
+ turn.iterations.length === 1 ? "" : "s",
285
+ " \xB7 ",
286
+ turn.totalInputTokens,
287
+ "\u2192",
288
+ turn.totalOutputTokens,
289
+ " tokens \xB7 ",
290
+ (turn.totalDurationMs / 1e3).toFixed(1),
291
+ "s"
292
+ ] })
293
+ ] });
294
+ }
295
+ function TurnHeader({ turn }) {
296
+ const t = useLensTheme();
297
+ return /* @__PURE__ */ jsxs(
298
+ "div",
299
+ {
300
+ style: {
301
+ display: "flex",
302
+ alignItems: "center",
303
+ gap: 8,
304
+ color: t.textSubtle,
305
+ fontSize: 11,
306
+ textTransform: "uppercase",
307
+ letterSpacing: "0.08em",
308
+ fontWeight: 600
309
+ },
310
+ children: [
311
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: t.border } }),
312
+ /* @__PURE__ */ jsxs("span", { children: [
313
+ "Your question ",
314
+ turn.index + 1
315
+ ] }),
316
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: t.border } })
317
+ ]
318
+ }
319
+ );
320
+ }
321
+ function UserBubble({ text }) {
322
+ const t = useLensTheme();
323
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ jsx(
324
+ "div",
325
+ {
326
+ style: {
327
+ background: `color-mix(in srgb, ${t.accent} 18%, ${t.bgElev})`,
328
+ border: `1px solid ${t.border}`,
329
+ color: t.text,
330
+ maxWidth: 720,
331
+ padding: "10px 14px",
332
+ borderRadius: `${t.radius} ${t.radius} 2px ${t.radius}`,
333
+ whiteSpace: "pre-wrap"
334
+ },
335
+ children: text
336
+ }
337
+ ) });
338
+ }
339
+ function iterationHeadline(iter) {
340
+ if (iter.toolCalls.length === 0) {
341
+ return "Neo is ready to answer";
342
+ }
343
+ if (iter.toolCalls.length === 1) {
344
+ return singleToolHeadline(iter.toolCalls[0]);
345
+ }
346
+ const names = iter.toolCalls.map((tc) => tc.name);
347
+ if (names.length <= 3) {
348
+ return `Neo called ${names.length} tools in parallel (${names.join(", ")})`;
349
+ }
350
+ return `Neo gathered data from ${names.length} tools in parallel`;
351
+ }
352
+ function singleToolHeadline(tc) {
353
+ if (tc.name === "list_skills") return "Neo is looking up available skills";
354
+ if (tc.name === "read_skill") {
355
+ const id = tc.arguments?.id ?? "?";
356
+ return `Neo activated the "${id}" skill`;
357
+ }
358
+ if (tc.name === "ask_human" || tc.name === "ask_user") {
359
+ return "Neo asked the user for clarification";
360
+ }
361
+ return `Neo called tool (${tc.name})`;
362
+ }
363
+ function IterationBlock({
364
+ iter,
365
+ iterPositionInTurn,
366
+ turnIndex,
367
+ allMessages,
368
+ onToolCallClick,
369
+ state = "active",
370
+ stages,
371
+ range,
372
+ focusIndex,
373
+ onClick
374
+ }) {
375
+ const t = useLensTheme();
376
+ const [showContext, setShowContext] = useState(false);
377
+ const key = `${turnIndex}.${iter.index}`;
378
+ const headline = iterationHeadline(iter);
379
+ const contextMessages = allMessages.slice(0, iter.messagesSentCount);
380
+ let revealIdx = Number.POSITIVE_INFINITY;
381
+ if (range && focusIndex !== void 0) {
382
+ if (state === "future") revealIdx = -1;
383
+ else if (state === "past") revealIdx = range.lastStageIndex;
384
+ else revealIdx = focusIndex;
385
+ }
386
+ const roundHasStarted = revealIdx >= (range?.firstStageIndex ?? 0);
387
+ const revealedMutations = (() => {
388
+ if (!stages || !range) return void 0;
389
+ const agg = {
390
+ systemPrompt: false,
391
+ tools: false,
392
+ systemPromptDeltaChars: 0,
393
+ toolsAdded: 0,
394
+ toolsRemoved: 0,
395
+ systemPromptAdded: "",
396
+ toolsAddedList: []
397
+ };
398
+ const stop = Math.min(revealIdx, range.lastStageIndex);
399
+ for (let i = range.firstStageIndex; i <= stop; i++) {
400
+ const m = stages[i]?.mutations;
401
+ if (!m) continue;
402
+ if (m.systemPrompt) agg.systemPrompt = true;
403
+ if (m.tools) agg.tools = true;
404
+ if (m.systemPromptDeltaChars) agg.systemPromptDeltaChars += m.systemPromptDeltaChars;
405
+ if (m.toolsAdded) agg.toolsAdded += m.toolsAdded;
406
+ if (m.toolsRemoved) agg.toolsRemoved += m.toolsRemoved;
407
+ if (m.systemPromptAdded) {
408
+ agg.systemPromptAdded = agg.systemPromptAdded ? `${agg.systemPromptAdded}
409
+
410
+ ${m.systemPromptAdded}` : m.systemPromptAdded;
411
+ }
412
+ if (m.activatedSkillId && !agg.activatedSkillId) {
413
+ agg.activatedSkillId = m.activatedSkillId;
414
+ }
415
+ if (m.toolsAddedList?.length) {
416
+ for (const name of m.toolsAddedList) {
417
+ if (!agg.toolsAddedList.includes(name)) agg.toolsAddedList.push(name);
418
+ }
419
+ }
420
+ }
421
+ return agg;
422
+ })();
423
+ const mutations = revealedMutations;
424
+ const toolStageIdx = /* @__PURE__ */ new Map();
425
+ if (stages) {
426
+ for (const tc of iter.toolCalls) {
427
+ let outIdx = -1;
428
+ let retIdx = -1;
429
+ for (let i = 0; i < stages.length; i++) {
430
+ const s = stages[i];
431
+ if (s.turnIndex !== turnIndex || s.iterIndex !== iter.index) continue;
432
+ if (s.toolName !== tc.name) continue;
433
+ if (s.from === "agent" && s.to === "tool" && outIdx < 0) outIdx = i;
434
+ else if (s.from === "tool" && s.to === "agent" && retIdx < 0) retIdx = i;
435
+ }
436
+ toolStageIdx.set(tc.id, { outIdx, retIdx });
437
+ }
438
+ }
439
+ const opacity = state === "future" ? 0.4 : state === "past" ? 0.85 : 1;
440
+ const isActiveRound = state === "active";
441
+ const background = isActiveRound ? `color-mix(in srgb, ${t.accent} 10%, ${t.bg})` : "transparent";
442
+ const boxShadow = isActiveRound ? `0 0 0 1px ${t.accent}, 0 0 24px color-mix(in srgb, ${t.accent} 22%, transparent)` : "none";
443
+ return /* @__PURE__ */ jsxs(
444
+ "div",
445
+ {
446
+ "data-iter-key": key,
447
+ "data-turn-index": turnIndex,
448
+ "data-iter-index": iter.index,
449
+ style: {
450
+ position: "relative",
451
+ display: "flex",
452
+ flexDirection: "column",
453
+ gap: 6,
454
+ padding: 12,
455
+ margin: isActiveRound ? "2px -12px" : "-8px",
456
+ borderRadius: 8,
457
+ background,
458
+ borderLeft: isActiveRound ? `3px solid ${t.accent}` : "3px solid transparent",
459
+ boxShadow,
460
+ opacity,
461
+ transition: "background 220ms ease, box-shadow 220ms ease, opacity 220ms ease, border-left-color 220ms ease, margin 220ms ease"
462
+ },
463
+ children: [
464
+ isActiveRound && /* @__PURE__ */ jsx(
465
+ "span",
466
+ {
467
+ "aria-hidden": "true",
468
+ style: {
469
+ position: "absolute",
470
+ left: -3,
471
+ top: 8,
472
+ bottom: 8,
473
+ width: 3,
474
+ background: t.accent,
475
+ boxShadow: `0 0 8px ${t.accent}`,
476
+ pointerEvents: "none"
477
+ }
478
+ }
479
+ ),
480
+ /* @__PURE__ */ jsxs(
481
+ "div",
482
+ {
483
+ style: {
484
+ display: "flex",
485
+ alignItems: "baseline",
486
+ gap: 8,
487
+ fontSize: 13,
488
+ color: t.textMuted
489
+ },
490
+ children: [
491
+ /* @__PURE__ */ jsxs(
492
+ "button",
493
+ {
494
+ onClick,
495
+ disabled: !onClick,
496
+ title: onClick ? "Jump the slider to this round" : void 0,
497
+ style: {
498
+ background: "transparent",
499
+ border: "none",
500
+ padding: 0,
501
+ margin: 0,
502
+ display: "flex",
503
+ alignItems: "baseline",
504
+ gap: 8,
505
+ cursor: onClick ? "pointer" : "default",
506
+ color: "inherit",
507
+ font: "inherit",
508
+ width: "auto",
509
+ textAlign: "left"
510
+ },
511
+ children: [
512
+ /* @__PURE__ */ jsxs("span", { style: { color: t.accent, fontWeight: 600 }, children: [
513
+ "Round ",
514
+ iterPositionInTurn,
515
+ ":"
516
+ ] }),
517
+ /* @__PURE__ */ jsx("span", { style: { color: t.text }, children: headline })
518
+ ]
519
+ }
520
+ ),
521
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
522
+ /* @__PURE__ */ jsxs(
523
+ "span",
524
+ {
525
+ style: {
526
+ fontSize: 10,
527
+ color: t.textSubtle,
528
+ fontFamily: t.fontMono
529
+ },
530
+ children: [
531
+ iter.inputTokens !== void 0 && `${iter.inputTokens}\u2192${iter.outputTokens ?? "?"} tok \xB7 `,
532
+ iter.durationMs !== void 0 && `${(iter.durationMs / 1e3).toFixed(2)}s`
533
+ ]
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsxs(
537
+ "button",
538
+ {
539
+ onClick: () => setShowContext((v) => !v),
540
+ title: "See exactly what Neo saw when deciding this step",
541
+ style: {
542
+ fontSize: 11,
543
+ color: t.textMuted,
544
+ background: "transparent",
545
+ border: `1px solid ${t.border}`,
546
+ borderRadius: 4,
547
+ padding: "2px 8px",
548
+ cursor: "pointer",
549
+ fontWeight: 400,
550
+ width: "auto"
551
+ },
552
+ children: [
553
+ showContext ? "Hide" : "Show",
554
+ " what Neo saw"
555
+ ]
556
+ }
557
+ )
558
+ ]
559
+ }
560
+ ),
561
+ roundHasStarted && (iter.model || iter.stopReason || (iter.matchedInstructions?.length ?? 0) > 0) && /* @__PURE__ */ jsxs(
562
+ "div",
563
+ {
564
+ style: {
565
+ display: "flex",
566
+ flexWrap: "wrap",
567
+ gap: 6,
568
+ fontSize: 10,
569
+ color: t.textSubtle,
570
+ fontFamily: t.fontMono,
571
+ paddingLeft: 12
572
+ },
573
+ children: [
574
+ iter.model && /* @__PURE__ */ jsx(MetaPill, { t, title: "Model the LLM call was routed to", children: iter.model }),
575
+ iter.stopReason && /* @__PURE__ */ jsxs(MetaPill, { t, title: "Why the LLM stopped producing tokens", children: [
576
+ "stop: ",
577
+ iter.stopReason
578
+ ] }),
579
+ iter.matchedInstructions?.map((id) => /* @__PURE__ */ jsxs(
580
+ MetaPill,
581
+ {
582
+ t,
583
+ title: "Instruction injected into this round",
584
+ accent: true,
585
+ children: [
586
+ "\u25B8 ",
587
+ id
588
+ ]
589
+ },
590
+ id
591
+ ))
592
+ ]
593
+ }
594
+ ),
595
+ roundHasStarted && iter.assistantContent && /* @__PURE__ */ jsx(ReasoningBubble, { text: iter.assistantContent }),
596
+ roundHasStarted && iter.toolCalls.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6, paddingLeft: 12 }, children: iter.toolCalls.map((tc) => {
597
+ const idx = toolStageIdx.get(tc.id);
598
+ const cardRevealed = !idx || idx.outIdx < 0 || revealIdx >= idx.outIdx;
599
+ if (!cardRevealed) return null;
600
+ const resultRevealed = !idx || idx.retIdx < 0 || revealIdx >= idx.retIdx;
601
+ return /* @__PURE__ */ jsx(
602
+ ToolCallCard,
603
+ {
604
+ invocation: tc,
605
+ onClick: onToolCallClick,
606
+ resultRevealed
607
+ },
608
+ tc.id
609
+ );
610
+ }) }),
611
+ mutations && (mutations.systemPrompt || mutations.tools) && /* @__PURE__ */ jsx(MutationStrip, { mutations, iter }),
612
+ showContext && /* @__PURE__ */ jsx(
613
+ ContextDrawer,
614
+ {
615
+ messagesSentCount: iter.messagesSentCount,
616
+ contextMessages,
617
+ iter
618
+ }
619
+ )
620
+ ]
621
+ }
622
+ );
623
+ }
624
+ function ReasoningBubble({ text }) {
625
+ const t = useLensTheme();
626
+ const [open, setOpen] = useState(false);
627
+ const PREVIEW_LEN = 140;
628
+ const flat = text.replace(/\s+/g, " ").trim();
629
+ const needsToggle = flat.length > PREVIEW_LEN;
630
+ const preview = needsToggle ? flat.slice(0, PREVIEW_LEN - 1) + "\u2026" : flat;
631
+ return /* @__PURE__ */ jsx(
632
+ "div",
633
+ {
634
+ style: {
635
+ background: t.bgElev,
636
+ border: `1px solid ${t.border}`,
637
+ borderRadius: `2px ${t.radius} ${t.radius} ${t.radius}`,
638
+ padding: "10px 14px",
639
+ maxWidth: 820,
640
+ whiteSpace: "pre-wrap",
641
+ fontSize: 13,
642
+ lineHeight: 1.55
643
+ },
644
+ children: needsToggle && !open ? /* @__PURE__ */ jsxs(Fragment, { children: [
645
+ /* @__PURE__ */ jsx("span", { children: preview }),
646
+ " ",
647
+ /* @__PURE__ */ jsx(
648
+ "button",
649
+ {
650
+ onClick: () => setOpen(true),
651
+ style: {
652
+ background: "transparent",
653
+ border: "none",
654
+ padding: 0,
655
+ color: t.accent,
656
+ cursor: "pointer",
657
+ fontSize: 12,
658
+ fontWeight: 600,
659
+ width: "auto"
660
+ },
661
+ children: "\u25B8 Show full reasoning"
662
+ }
663
+ )
664
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
665
+ text,
666
+ needsToggle && /* @__PURE__ */ jsx("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ jsx(
667
+ "button",
668
+ {
669
+ onClick: () => setOpen(false),
670
+ style: {
671
+ background: "transparent",
672
+ border: "none",
673
+ padding: 0,
674
+ color: t.textMuted,
675
+ cursor: "pointer",
676
+ fontSize: 12,
677
+ fontWeight: 500,
678
+ width: "auto"
679
+ },
680
+ children: "\u25BE Collapse"
681
+ }
682
+ ) })
683
+ ] })
684
+ }
685
+ );
686
+ }
687
+ function MetaPill({
688
+ t,
689
+ children,
690
+ title,
691
+ accent
692
+ }) {
693
+ return /* @__PURE__ */ jsx(
694
+ "span",
695
+ {
696
+ title,
697
+ style: {
698
+ padding: "1px 6px",
699
+ borderRadius: 3,
700
+ background: accent ? `color-mix(in srgb, ${t.warning} 18%, transparent)` : t.bgElev,
701
+ color: accent ? t.warning : t.textSubtle,
702
+ fontWeight: accent ? 600 : 500,
703
+ letterSpacing: "0.02em",
704
+ whiteSpace: "nowrap"
705
+ },
706
+ children
707
+ }
708
+ );
709
+ }
710
+ function MutationStrip({
711
+ mutations,
712
+ iter
713
+ }) {
714
+ const t = useLensTheme();
715
+ const [open, setOpen] = useState(false);
716
+ const fallbackReadSkill = mutations.systemPromptAdded ? void 0 : iter.toolCalls.find((tc) => tc.name === "read_skill");
717
+ const skillId = mutations.activatedSkillId || (fallbackReadSkill?.arguments?.id ?? "");
718
+ const skillBody = mutations.systemPromptAdded || fallbackReadSkill?.result || "";
719
+ return /* @__PURE__ */ jsxs(
720
+ "div",
721
+ {
722
+ style: {
723
+ display: "flex",
724
+ flexDirection: "column",
725
+ gap: 6,
726
+ paddingLeft: 12,
727
+ fontSize: 11,
728
+ color: t.textMuted,
729
+ fontFamily: t.fontSans
730
+ },
731
+ children: [
732
+ /* @__PURE__ */ jsxs(
733
+ "button",
734
+ {
735
+ onClick: () => setOpen((v) => !v),
736
+ style: {
737
+ display: "flex",
738
+ flexWrap: "wrap",
739
+ alignItems: "center",
740
+ gap: 6,
741
+ background: "transparent",
742
+ border: "none",
743
+ padding: 0,
744
+ margin: 0,
745
+ cursor: "pointer",
746
+ color: "inherit",
747
+ font: "inherit",
748
+ width: "auto",
749
+ textAlign: "left"
750
+ },
751
+ title: "Click to see the actual diff",
752
+ children: [
753
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10, color: t.textSubtle }, children: open ? "\u25BE" : "\u25B8" }),
754
+ /* @__PURE__ */ jsx(
755
+ "span",
756
+ {
757
+ style: {
758
+ fontWeight: 700,
759
+ color: t.accent,
760
+ textTransform: "uppercase",
761
+ letterSpacing: "0.06em",
762
+ fontSize: 10
763
+ },
764
+ children: "\u270E Changed"
765
+ }
766
+ ),
767
+ /* @__PURE__ */ jsx("span", { children: "in Neo's input:" }),
768
+ mutations.systemPrompt && /* @__PURE__ */ jsxs(
769
+ "span",
770
+ {
771
+ style: {
772
+ padding: "1px 7px",
773
+ borderRadius: 3,
774
+ background: `color-mix(in srgb, ${t.accent} 18%, transparent)`,
775
+ color: t.accent,
776
+ fontWeight: 600,
777
+ letterSpacing: "0.02em"
778
+ },
779
+ children: [
780
+ "System Prompt",
781
+ mutations.systemPromptDeltaChars > 0 && /* @__PURE__ */ jsxs("span", { style: { fontWeight: 400, marginLeft: 4 }, children: [
782
+ "+",
783
+ mutations.systemPromptDeltaChars.toLocaleString(),
784
+ " chars"
785
+ ] })
786
+ ]
787
+ }
788
+ ),
789
+ mutations.tools && /* @__PURE__ */ jsxs(
790
+ "span",
791
+ {
792
+ style: {
793
+ padding: "1px 7px",
794
+ borderRadius: 3,
795
+ background: `color-mix(in srgb, ${t.accent} 18%, transparent)`,
796
+ color: t.accent,
797
+ fontWeight: 600,
798
+ letterSpacing: "0.02em"
799
+ },
800
+ children: [
801
+ "Tools",
802
+ (mutations.toolsAdded > 0 || mutations.toolsRemoved > 0) && /* @__PURE__ */ jsxs("span", { style: { fontWeight: 400, marginLeft: 4 }, children: [
803
+ mutations.toolsAdded > 0 ? `+${mutations.toolsAdded}` : "",
804
+ mutations.toolsRemoved > 0 ? ` -${mutations.toolsRemoved}` : ""
805
+ ] })
806
+ ]
807
+ }
808
+ )
809
+ ]
810
+ }
811
+ ),
812
+ open && /* @__PURE__ */ jsx(
813
+ MutationDiffModal,
814
+ {
815
+ mutations,
816
+ skillId,
817
+ skillBody,
818
+ visibleTools: iter.visibleTools,
819
+ onClose: () => setOpen(false)
820
+ }
821
+ )
822
+ ]
823
+ }
824
+ );
825
+ }
826
+ function MutationDiffModal({
827
+ mutations,
828
+ skillId,
829
+ skillBody,
830
+ visibleTools,
831
+ onClose
832
+ }) {
833
+ const t = useLensTheme();
834
+ useEffect(() => {
835
+ const onKey = (e) => {
836
+ if (e.key === "Escape") onClose();
837
+ };
838
+ window.addEventListener("keydown", onKey);
839
+ return () => window.removeEventListener("keydown", onKey);
840
+ }, [onClose]);
841
+ const toolsToShow = mutations.toolsAddedList.length > 0 ? mutations.toolsAddedList : visibleTools;
842
+ return /* @__PURE__ */ jsx(
843
+ "div",
844
+ {
845
+ onClick: onClose,
846
+ role: "dialog",
847
+ "aria-modal": "true",
848
+ style: {
849
+ position: "fixed",
850
+ inset: 0,
851
+ background: "rgba(0, 0, 0, 0.55)",
852
+ backdropFilter: "blur(4px)",
853
+ WebkitBackdropFilter: "blur(4px)",
854
+ display: "flex",
855
+ alignItems: "center",
856
+ justifyContent: "center",
857
+ zIndex: 1e3,
858
+ padding: 24
859
+ },
860
+ children: /* @__PURE__ */ jsxs(
861
+ "div",
862
+ {
863
+ onClick: (e) => e.stopPropagation(),
864
+ style: {
865
+ background: t.bg,
866
+ color: t.text,
867
+ border: `1px solid ${t.border}`,
868
+ borderRadius: 12,
869
+ boxShadow: "0 24px 64px rgba(0, 0, 0, 0.45)",
870
+ width: "min(880px, 100%)",
871
+ maxHeight: "min(80dvh, 800px)",
872
+ display: "flex",
873
+ flexDirection: "column",
874
+ fontFamily: t.fontSans
875
+ },
876
+ children: [
877
+ /* @__PURE__ */ jsxs(
878
+ "div",
879
+ {
880
+ style: {
881
+ display: "flex",
882
+ alignItems: "center",
883
+ padding: "14px 18px",
884
+ borderBottom: `1px solid ${t.border}`
885
+ },
886
+ children: [
887
+ /* @__PURE__ */ jsxs("div", { children: [
888
+ /* @__PURE__ */ jsx(
889
+ "div",
890
+ {
891
+ style: {
892
+ fontSize: 10,
893
+ color: t.textSubtle,
894
+ textTransform: "uppercase",
895
+ letterSpacing: "0.08em",
896
+ fontWeight: 600
897
+ },
898
+ children: "\u270E Changed in Neo's input"
899
+ }
900
+ ),
901
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 15, fontWeight: 600, marginTop: 2 }, children: [
902
+ "Round diff",
903
+ skillId && /* @__PURE__ */ jsxs(Fragment, { children: [
904
+ " \xB7 ",
905
+ /* @__PURE__ */ jsx(
906
+ "code",
907
+ {
908
+ style: {
909
+ fontFamily: t.fontMono,
910
+ color: t.accent,
911
+ fontWeight: 600
912
+ },
913
+ children: skillId
914
+ }
915
+ )
916
+ ] })
917
+ ] })
918
+ ] }),
919
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
920
+ /* @__PURE__ */ jsx(
921
+ "button",
922
+ {
923
+ onClick: onClose,
924
+ title: "Close (Esc)",
925
+ style: {
926
+ background: "transparent",
927
+ border: `1px solid ${t.border}`,
928
+ color: t.textMuted,
929
+ padding: "4px 10px",
930
+ borderRadius: 6,
931
+ cursor: "pointer",
932
+ fontSize: 12,
933
+ width: "auto"
934
+ },
935
+ children: "Esc"
936
+ }
937
+ )
938
+ ]
939
+ }
940
+ ),
941
+ /* @__PURE__ */ jsxs(
942
+ "div",
943
+ {
944
+ style: {
945
+ padding: 18,
946
+ overflow: "auto",
947
+ display: "flex",
948
+ flexDirection: "column",
949
+ gap: 16
950
+ },
951
+ children: [
952
+ mutations.systemPrompt && /* @__PURE__ */ jsxs("section", { children: [
953
+ /* @__PURE__ */ jsxs(
954
+ "div",
955
+ {
956
+ style: {
957
+ fontSize: 11,
958
+ color: t.textSubtle,
959
+ textTransform: "uppercase",
960
+ letterSpacing: "0.08em",
961
+ fontWeight: 600,
962
+ marginBottom: 8
963
+ },
964
+ children: [
965
+ "System Prompt",
966
+ mutations.systemPromptDeltaChars > 0 && ` \xB7 +${mutations.systemPromptDeltaChars.toLocaleString()} chars`
967
+ ]
968
+ }
969
+ ),
970
+ skillBody ? /* @__PURE__ */ jsx(
971
+ "pre",
972
+ {
973
+ style: {
974
+ margin: 0,
975
+ padding: "12px 14px",
976
+ background: t.bgElev,
977
+ border: `1px solid ${t.border}`,
978
+ borderLeft: `3px solid ${t.accent}`,
979
+ borderRadius: 6,
980
+ fontSize: 12,
981
+ lineHeight: 1.55,
982
+ fontFamily: t.fontMono,
983
+ color: t.text,
984
+ whiteSpace: "pre-wrap"
985
+ },
986
+ children: skillBody
987
+ }
988
+ ) : /* @__PURE__ */ jsxs(
989
+ "div",
990
+ {
991
+ style: {
992
+ padding: "10px 12px",
993
+ border: `1px dashed ${t.border}`,
994
+ borderRadius: 6,
995
+ color: t.textSubtle,
996
+ fontStyle: "italic",
997
+ fontSize: 12
998
+ },
999
+ children: [
1000
+ "System Prompt grew by ",
1001
+ mutations.systemPromptDeltaChars.toLocaleString(),
1002
+ " ",
1003
+ "chars but the source text isn't flowing through the adapter for this round."
1004
+ ]
1005
+ }
1006
+ )
1007
+ ] }),
1008
+ mutations.tools && /* @__PURE__ */ jsxs("section", { children: [
1009
+ /* @__PURE__ */ jsxs(
1010
+ "div",
1011
+ {
1012
+ style: {
1013
+ fontSize: 11,
1014
+ color: t.textSubtle,
1015
+ textTransform: "uppercase",
1016
+ letterSpacing: "0.08em",
1017
+ fontWeight: 600,
1018
+ marginBottom: 8
1019
+ },
1020
+ children: [
1021
+ "Tools",
1022
+ mutations.toolsAdded > 0 && ` \xB7 +${mutations.toolsAdded} added`,
1023
+ mutations.toolsRemoved > 0 && ` \xB7 -${mutations.toolsRemoved} removed`
1024
+ ]
1025
+ }
1026
+ ),
1027
+ toolsToShow.length > 0 ? /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: toolsToShow.map((name) => /* @__PURE__ */ jsx(
1028
+ "span",
1029
+ {
1030
+ style: {
1031
+ padding: "3px 9px",
1032
+ borderRadius: 4,
1033
+ background: t.bgElev,
1034
+ border: `1px solid ${t.border}`,
1035
+ color: t.text,
1036
+ fontFamily: t.fontMono,
1037
+ fontSize: 11
1038
+ },
1039
+ children: name
1040
+ },
1041
+ name
1042
+ )) }) : /* @__PURE__ */ jsx(
1043
+ "div",
1044
+ {
1045
+ style: {
1046
+ padding: "10px 12px",
1047
+ border: `1px dashed ${t.border}`,
1048
+ borderRadius: 6,
1049
+ color: t.textSubtle,
1050
+ fontStyle: "italic",
1051
+ fontSize: 12
1052
+ },
1053
+ children: "The tool list that came online in this round isn't flowing through the adapter yet \u2014 counts are available, names aren't."
1054
+ }
1055
+ )
1056
+ ] })
1057
+ ]
1058
+ }
1059
+ )
1060
+ ]
1061
+ }
1062
+ )
1063
+ }
1064
+ );
1065
+ }
1066
+ function ContextDrawer({
1067
+ messagesSentCount,
1068
+ contextMessages,
1069
+ iter
1070
+ }) {
1071
+ const t = useLensTheme();
1072
+ return /* @__PURE__ */ jsxs(
1073
+ "div",
1074
+ {
1075
+ style: {
1076
+ border: `1px dashed ${t.border}`,
1077
+ borderRadius: t.radius,
1078
+ padding: "10px 12px",
1079
+ background: t.bg,
1080
+ fontSize: 12,
1081
+ color: t.textMuted
1082
+ },
1083
+ children: [
1084
+ /* @__PURE__ */ jsx(
1085
+ "div",
1086
+ {
1087
+ style: {
1088
+ fontSize: 10,
1089
+ color: t.textSubtle,
1090
+ textTransform: "uppercase",
1091
+ letterSpacing: "0.08em",
1092
+ fontWeight: 600,
1093
+ marginBottom: 8
1094
+ },
1095
+ children: "What Neo saw before this step"
1096
+ }
1097
+ ),
1098
+ /* @__PURE__ */ jsxs("div", { style: { color: t.text, marginBottom: 8 }, children: [
1099
+ /* @__PURE__ */ jsx("strong", { children: messagesSentCount }),
1100
+ " message",
1101
+ messagesSentCount === 1 ? "" : "s",
1102
+ " in context",
1103
+ iter.model ? ` \xB7 sent to ${iter.model}` : "",
1104
+ iter.inputTokens !== void 0 && ` \xB7 ${iter.inputTokens} input tokens`
1105
+ ] }),
1106
+ contextMessages.length === 0 ? /* @__PURE__ */ jsx("div", { style: { color: t.textSubtle, fontStyle: "italic" }, children: "Just the system configuration \u2014 this is the first call of the conversation." }) : /* @__PURE__ */ jsx("ol", { style: { margin: 0, paddingLeft: 18, display: "flex", flexDirection: "column", gap: 6 }, children: contextMessages.map((m, i) => /* @__PURE__ */ jsxs("li", { style: { fontSize: 12 }, children: [
1107
+ /* @__PURE__ */ jsx(
1108
+ "span",
1109
+ {
1110
+ style: {
1111
+ display: "inline-block",
1112
+ padding: "1px 6px",
1113
+ borderRadius: 3,
1114
+ background: m.role === "user" ? `color-mix(in srgb, ${t.accent} 20%, transparent)` : m.role === "assistant" ? t.bgElev : m.role === "tool" ? `color-mix(in srgb, ${t.success} 18%, transparent)` : t.bgElev,
1115
+ color: m.role === "user" ? t.accent : m.role === "tool" ? t.success : t.text,
1116
+ fontFamily: t.fontMono,
1117
+ fontSize: 10,
1118
+ fontWeight: 600,
1119
+ textTransform: "uppercase",
1120
+ marginRight: 6
1121
+ },
1122
+ children: m.role
1123
+ }
1124
+ ),
1125
+ /* @__PURE__ */ jsx("span", { style: { color: t.textMuted }, children: summarizeMessage(m) })
1126
+ ] }, i)) }),
1127
+ /* @__PURE__ */ jsx(
1128
+ "div",
1129
+ {
1130
+ style: {
1131
+ marginTop: 10,
1132
+ fontSize: 11,
1133
+ color: t.textSubtle,
1134
+ fontStyle: "italic"
1135
+ },
1136
+ children: "Plus the system configuration (see top of conversation) and the tools Neo had access to."
1137
+ }
1138
+ )
1139
+ ]
1140
+ }
1141
+ );
1142
+ }
1143
+ function summarizeMessage(m) {
1144
+ if (m.role === "tool") {
1145
+ return `tool result (${m.content.length.toLocaleString()} chars)`;
1146
+ }
1147
+ const t = m.content.replace(/\s+/g, " ").trim();
1148
+ return t.length > 120 ? t.slice(0, 120) + "\u2026" : t;
1149
+ }
1150
+ function ToolCallCard({
1151
+ invocation,
1152
+ onClick,
1153
+ resultRevealed = true
1154
+ }) {
1155
+ const t = useLensTheme();
1156
+ const [open, setOpen] = useState(false);
1157
+ const preview = shortArgs(invocation.arguments);
1158
+ const errored = invocation.error === true;
1159
+ const friendlyVerb = toolVerb(invocation);
1160
+ const borderStyle = resultRevealed ? "solid" : "dashed";
1161
+ return /* @__PURE__ */ jsxs(
1162
+ "div",
1163
+ {
1164
+ style: {
1165
+ border: `1px ${borderStyle} ${errored ? t.error : t.border}`,
1166
+ borderLeft: `3px ${borderStyle} ${errored ? t.error : t.accent}`,
1167
+ borderRadius: 6,
1168
+ background: t.bg,
1169
+ overflow: "hidden"
1170
+ },
1171
+ children: [
1172
+ /* @__PURE__ */ jsxs(
1173
+ "div",
1174
+ {
1175
+ onClick: () => {
1176
+ setOpen((v) => !v);
1177
+ onClick?.(invocation);
1178
+ },
1179
+ style: {
1180
+ padding: "8px 12px",
1181
+ cursor: "pointer",
1182
+ display: "flex",
1183
+ alignItems: "center",
1184
+ gap: 10,
1185
+ fontSize: 12
1186
+ },
1187
+ children: [
1188
+ /* @__PURE__ */ jsx("span", { style: { color: t.textMuted, fontFamily: t.fontSans }, children: friendlyVerb }),
1189
+ /* @__PURE__ */ jsx("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600, fontFamily: t.fontMono }, children: invocation.name }),
1190
+ preview && /* @__PURE__ */ jsxs("span", { style: { color: t.textMuted, fontFamily: t.fontMono }, children: [
1191
+ "(",
1192
+ preview,
1193
+ ")"
1194
+ ] }),
1195
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
1196
+ !resultRevealed && /* @__PURE__ */ jsx(
1197
+ "span",
1198
+ {
1199
+ style: {
1200
+ fontSize: 10,
1201
+ padding: "1px 6px",
1202
+ borderRadius: 3,
1203
+ background: `color-mix(in srgb, ${t.accent} 18%, transparent)`,
1204
+ color: t.accent,
1205
+ fontWeight: 600,
1206
+ textTransform: "uppercase",
1207
+ letterSpacing: "0.04em"
1208
+ },
1209
+ title: "Args sent; waiting for the tool to return",
1210
+ children: "in flight"
1211
+ }
1212
+ ),
1213
+ invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ jsx(
1214
+ "span",
1215
+ {
1216
+ style: {
1217
+ fontSize: 10,
1218
+ padding: "1px 6px",
1219
+ borderRadius: 3,
1220
+ background: `color-mix(in srgb, ${t.warning} 20%, transparent)`,
1221
+ color: t.warning,
1222
+ fontWeight: 600,
1223
+ textTransform: "uppercase"
1224
+ },
1225
+ title: "This tool changed what skill is active",
1226
+ children: "skill change"
1227
+ }
1228
+ ),
1229
+ /* @__PURE__ */ jsx("span", { style: { color: t.textSubtle }, children: open ? "\u25BE" : "\u25B8" })
1230
+ ]
1231
+ }
1232
+ ),
1233
+ open && /* @__PURE__ */ jsxs("div", { style: { padding: "8px 12px", borderTop: `1px solid ${t.border}` }, children: [
1234
+ /* @__PURE__ */ jsx(Label, { t, children: "What Neo asked for" }),
1235
+ /* @__PURE__ */ jsx(JsonBlock, { value: invocation.arguments }),
1236
+ resultRevealed && invocation.result && /* @__PURE__ */ jsxs(Fragment, { children: [
1237
+ /* @__PURE__ */ jsx(Label, { t, style: { marginTop: 10 }, children: "What the tool returned" }),
1238
+ /* @__PURE__ */ jsx(
1239
+ "pre",
1240
+ {
1241
+ style: {
1242
+ margin: 0,
1243
+ padding: "8px 10px",
1244
+ background: t.bgElev,
1245
+ borderRadius: 4,
1246
+ fontSize: 11,
1247
+ fontFamily: t.fontMono,
1248
+ color: errored ? t.error : t.text,
1249
+ maxHeight: 280,
1250
+ overflow: "auto",
1251
+ whiteSpace: "pre-wrap"
1252
+ },
1253
+ children: invocation.result
1254
+ }
1255
+ )
1256
+ ] }),
1257
+ !resultRevealed && /* @__PURE__ */ jsx(
1258
+ "div",
1259
+ {
1260
+ style: {
1261
+ marginTop: 10,
1262
+ padding: "10px 12px",
1263
+ border: `1px dashed ${t.border}`,
1264
+ borderRadius: 4,
1265
+ fontSize: 12,
1266
+ color: t.textSubtle,
1267
+ fontStyle: "italic"
1268
+ },
1269
+ children: "Neo has sent the args; advance the slider to see the result this tool returned."
1270
+ }
1271
+ ),
1272
+ resultRevealed && invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1273
+ /* @__PURE__ */ jsx(Label, { t, style: { marginTop: 10 }, children: "What changed in Neo's state" }),
1274
+ /* @__PURE__ */ jsx(JsonBlock, { value: invocation.decisionUpdate })
1275
+ ] })
1276
+ ] })
1277
+ ]
1278
+ }
1279
+ );
1280
+ }
1281
+ function toolVerb(inv) {
1282
+ if (inv.name === "list_skills") return "Asked for";
1283
+ if (inv.name === "read_skill") return "Activated";
1284
+ if (inv.name === "ask_human" || inv.name === "ask_user") return "Asked user for";
1285
+ return "Called tool";
1286
+ }
1287
+ function Label({
1288
+ t,
1289
+ children,
1290
+ style
1291
+ }) {
1292
+ return /* @__PURE__ */ jsx(
1293
+ "div",
1294
+ {
1295
+ style: {
1296
+ fontSize: 10,
1297
+ color: t.textSubtle,
1298
+ textTransform: "uppercase",
1299
+ letterSpacing: "0.08em",
1300
+ fontWeight: 600,
1301
+ marginBottom: 4,
1302
+ ...style
1303
+ },
1304
+ children
1305
+ }
1306
+ );
1307
+ }
1308
+ function JsonBlock({ value }) {
1309
+ const t = useLensTheme();
1310
+ return /* @__PURE__ */ jsx(
1311
+ "pre",
1312
+ {
1313
+ style: {
1314
+ margin: 0,
1315
+ padding: "8px 10px",
1316
+ background: t.bgElev,
1317
+ borderRadius: 4,
1318
+ fontSize: 11,
1319
+ fontFamily: t.fontMono,
1320
+ color: t.text,
1321
+ maxHeight: 200,
1322
+ overflow: "auto"
1323
+ },
1324
+ children: JSON.stringify(value, null, 2)
1325
+ }
1326
+ );
1327
+ }
1328
+ function shortArgs(args) {
1329
+ const keys = Object.keys(args);
1330
+ if (keys.length === 0) return "";
1331
+ if (keys.length === 1) {
1332
+ const v = args[keys[0]];
1333
+ if (typeof v === "string" && v.length < 40) return `${keys[0]}: "${v}"`;
1334
+ }
1335
+ return keys.join(", ");
1336
+ }
1337
+ function useIterationStageRanges(stages) {
1338
+ return React.useMemo(() => {
1339
+ const map = /* @__PURE__ */ new Map();
1340
+ if (!stages?.length) return map;
1341
+ const turnIters = /* @__PURE__ */ new Map();
1342
+ for (const s of stages) {
1343
+ if (s.iterIndex === void 0) continue;
1344
+ const list = turnIters.get(s.turnIndex) ?? [];
1345
+ if (!list.includes(s.iterIndex)) list.push(s.iterIndex);
1346
+ turnIters.set(s.turnIndex, list);
1347
+ }
1348
+ stages.forEach((s, idx) => {
1349
+ let iter = s.iterIndex;
1350
+ if (iter === void 0) {
1351
+ const iters = turnIters.get(s.turnIndex);
1352
+ if (!iters?.length) return;
1353
+ iter = s.from === "user" ? iters[0] : iters[iters.length - 1];
1354
+ }
1355
+ const key = `${s.turnIndex}.${iter}`;
1356
+ const prev = map.get(key);
1357
+ if (!prev) {
1358
+ map.set(key, { firstStageIndex: idx, lastStageIndex: idx });
1359
+ } else {
1360
+ map.set(key, {
1361
+ firstStageIndex: Math.min(prev.firstStageIndex, idx),
1362
+ lastStageIndex: Math.max(prev.lastStageIndex, idx)
1363
+ });
1364
+ }
1365
+ });
1366
+ return map;
1367
+ }, [stages]);
1368
+ }
1369
+ function keyForStage(ranges, focusIndex) {
1370
+ for (const [key, r] of ranges) {
1371
+ if (focusIndex >= r.firstStageIndex && focusIndex <= r.lastStageIndex) return key;
1372
+ }
1373
+ return null;
1374
+ }
1375
+
1376
+ // src/react/panels/SkillsPanel.tsx
1377
+ import { useState as useState2 } from "react";
1378
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1379
+ function SkillsPanel({ skills, onClose, activeSkillId }) {
1380
+ const t = useLensTheme();
1381
+ const [selectedId, setSelectedId] = useState2(
1382
+ activeSkillId ?? skills[0]?.id ?? null
1383
+ );
1384
+ const [mode, setMode] = useState2("formatted");
1385
+ const selected = skills.find((s) => s.id === selectedId) ?? null;
1386
+ return /* @__PURE__ */ jsx2(
1387
+ "div",
1388
+ {
1389
+ onClick: onClose,
1390
+ style: {
1391
+ position: "absolute",
1392
+ inset: 0,
1393
+ background: "rgba(0, 0, 0, 0.5)",
1394
+ zIndex: 100,
1395
+ display: "flex",
1396
+ alignItems: "stretch",
1397
+ justifyContent: "stretch"
1398
+ },
1399
+ children: /* @__PURE__ */ jsxs2(
1400
+ "div",
1401
+ {
1402
+ onClick: (e) => e.stopPropagation(),
1403
+ style: {
1404
+ display: "grid",
1405
+ gridTemplateColumns: "minmax(220px, 280px) 1fr",
1406
+ gridTemplateRows: "auto 1fr",
1407
+ gridTemplateAreas: '"header header" "list detail"',
1408
+ width: "100%",
1409
+ height: "100%",
1410
+ background: t.bg,
1411
+ color: t.text,
1412
+ fontFamily: t.fontSans,
1413
+ border: `1px solid ${t.border}`
1414
+ },
1415
+ children: [
1416
+ /* @__PURE__ */ jsxs2(
1417
+ "div",
1418
+ {
1419
+ style: {
1420
+ gridArea: "header",
1421
+ padding: "10px 14px",
1422
+ borderBottom: `1px solid ${t.border}`,
1423
+ background: t.bgElev,
1424
+ display: "flex",
1425
+ alignItems: "center",
1426
+ gap: 10
1427
+ },
1428
+ children: [
1429
+ /* @__PURE__ */ jsx2("strong", { style: { color: t.text, fontSize: 13 }, children: "Skills registered with Neo" }),
1430
+ /* @__PURE__ */ jsxs2("span", { style: { color: t.textMuted, fontSize: 12 }, children: [
1431
+ skills.length,
1432
+ " total"
1433
+ ] }),
1434
+ /* @__PURE__ */ jsx2("span", { style: { flex: 1 } }),
1435
+ /* @__PURE__ */ jsx2(
1436
+ "button",
1437
+ {
1438
+ onClick: onClose,
1439
+ style: {
1440
+ background: "transparent",
1441
+ border: `1px solid ${t.border}`,
1442
+ color: t.textMuted,
1443
+ borderRadius: 4,
1444
+ padding: "2px 10px",
1445
+ cursor: "pointer",
1446
+ fontSize: 14,
1447
+ width: "auto",
1448
+ fontWeight: 400
1449
+ },
1450
+ title: "Close (Esc)",
1451
+ children: "\u2715"
1452
+ }
1453
+ )
1454
+ ]
1455
+ }
1456
+ ),
1457
+ /* @__PURE__ */ jsxs2(
1458
+ "div",
1459
+ {
1460
+ style: {
1461
+ gridArea: "list",
1462
+ borderRight: `1px solid ${t.border}`,
1463
+ overflow: "auto",
1464
+ background: t.bg
1465
+ },
1466
+ children: [
1467
+ skills.length === 0 && /* @__PURE__ */ jsx2("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "No skills registered." }),
1468
+ skills.map((s) => {
1469
+ const isActive = s.id === selectedId;
1470
+ const isAgentActive = s.id === activeSkillId;
1471
+ return /* @__PURE__ */ jsxs2(
1472
+ "button",
1473
+ {
1474
+ onClick: () => setSelectedId(s.id),
1475
+ style: {
1476
+ display: "block",
1477
+ width: "100%",
1478
+ textAlign: "left",
1479
+ padding: "10px 14px",
1480
+ background: isActive ? t.bgHover : "transparent",
1481
+ border: "none",
1482
+ borderLeft: `3px solid ${isActive ? t.accent : isAgentActive ? t.success : "transparent"}`,
1483
+ borderBottom: `1px solid ${t.border}`,
1484
+ color: t.text,
1485
+ cursor: "pointer",
1486
+ fontFamily: "inherit"
1487
+ },
1488
+ children: [
1489
+ /* @__PURE__ */ jsxs2(
1490
+ "div",
1491
+ {
1492
+ style: {
1493
+ display: "flex",
1494
+ alignItems: "baseline",
1495
+ gap: 6,
1496
+ fontSize: 13
1497
+ },
1498
+ children: [
1499
+ /* @__PURE__ */ jsx2("span", { style: { fontWeight: 600 }, children: s.title ?? s.id }),
1500
+ isAgentActive && /* @__PURE__ */ jsx2(
1501
+ "span",
1502
+ {
1503
+ style: {
1504
+ fontSize: 9,
1505
+ padding: "1px 5px",
1506
+ borderRadius: 3,
1507
+ background: `color-mix(in srgb, ${t.success} 25%, transparent)`,
1508
+ color: t.success,
1509
+ fontWeight: 600,
1510
+ textTransform: "uppercase"
1511
+ },
1512
+ children: "active"
1513
+ }
1514
+ )
1515
+ ]
1516
+ }
1517
+ ),
1518
+ /* @__PURE__ */ jsxs2(
1519
+ "div",
1520
+ {
1521
+ style: {
1522
+ fontSize: 10,
1523
+ color: t.textSubtle,
1524
+ fontFamily: t.fontMono,
1525
+ marginTop: 1
1526
+ },
1527
+ children: [
1528
+ s.id,
1529
+ s.version && ` \xB7 v${s.version}`
1530
+ ]
1531
+ }
1532
+ ),
1533
+ s.description && /* @__PURE__ */ jsx2(
1534
+ "div",
1535
+ {
1536
+ style: {
1537
+ fontSize: 11,
1538
+ color: t.textMuted,
1539
+ marginTop: 4,
1540
+ lineHeight: 1.4,
1541
+ display: "-webkit-box",
1542
+ WebkitLineClamp: 3,
1543
+ WebkitBoxOrient: "vertical",
1544
+ overflow: "hidden"
1545
+ },
1546
+ children: s.description
1547
+ }
1548
+ )
1549
+ ]
1550
+ },
1551
+ s.id
1552
+ );
1553
+ })
1554
+ ]
1555
+ }
1556
+ ),
1557
+ /* @__PURE__ */ jsx2(
1558
+ "div",
1559
+ {
1560
+ style: {
1561
+ gridArea: "detail",
1562
+ overflow: "auto",
1563
+ padding: "14px 18px",
1564
+ fontSize: 13,
1565
+ lineHeight: 1.6
1566
+ },
1567
+ children: !selected ? /* @__PURE__ */ jsx2("div", { style: { color: t.textSubtle }, children: "Select a skill to see details." }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1568
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 10 }, children: [
1569
+ /* @__PURE__ */ jsx2("h2", { style: { margin: 0, fontSize: 18, color: t.text }, children: selected.title ?? selected.id }),
1570
+ /* @__PURE__ */ jsxs2("span", { style: { color: t.textSubtle, fontFamily: t.fontMono, fontSize: 11 }, children: [
1571
+ selected.id,
1572
+ selected.version && ` \xB7 v${selected.version}`
1573
+ ] }),
1574
+ /* @__PURE__ */ jsx2("span", { style: { flex: 1 } }),
1575
+ /* @__PURE__ */ jsxs2(
1576
+ "div",
1577
+ {
1578
+ role: "tablist",
1579
+ style: {
1580
+ display: "flex",
1581
+ gap: 1,
1582
+ border: `1px solid ${t.border}`,
1583
+ borderRadius: 4,
1584
+ overflow: "hidden"
1585
+ },
1586
+ children: [
1587
+ /* @__PURE__ */ jsx2(
1588
+ "button",
1589
+ {
1590
+ onClick: () => setMode("formatted"),
1591
+ style: {
1592
+ padding: "3px 10px",
1593
+ fontSize: 11,
1594
+ background: mode === "formatted" ? t.accent : "transparent",
1595
+ color: mode === "formatted" ? "#fff" : t.textMuted,
1596
+ border: "none",
1597
+ cursor: "pointer",
1598
+ width: "auto",
1599
+ fontWeight: 400
1600
+ },
1601
+ children: "Formatted"
1602
+ }
1603
+ ),
1604
+ /* @__PURE__ */ jsx2(
1605
+ "button",
1606
+ {
1607
+ onClick: () => setMode("json"),
1608
+ style: {
1609
+ padding: "3px 10px",
1610
+ fontSize: 11,
1611
+ background: mode === "json" ? t.accent : "transparent",
1612
+ color: mode === "json" ? "#fff" : t.textMuted,
1613
+ border: "none",
1614
+ cursor: "pointer",
1615
+ width: "auto",
1616
+ fontWeight: 400
1617
+ },
1618
+ children: "Raw JSON"
1619
+ }
1620
+ )
1621
+ ]
1622
+ }
1623
+ )
1624
+ ] }),
1625
+ mode === "formatted" ? /* @__PURE__ */ jsx2(SkillFormatted, { skill: selected }) : /* @__PURE__ */ jsx2(SkillJson, { skill: selected })
1626
+ ] })
1627
+ }
1628
+ )
1629
+ ]
1630
+ }
1631
+ )
1632
+ }
1633
+ );
1634
+ }
1635
+ function SkillFormatted({ skill }) {
1636
+ const t = useLensTheme();
1637
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
1638
+ skill.description && /* @__PURE__ */ jsxs2("section", { children: [
1639
+ /* @__PURE__ */ jsx2(Label2, { t, children: "Description" }),
1640
+ /* @__PURE__ */ jsx2("div", { style: { color: t.text }, children: skill.description })
1641
+ ] }),
1642
+ skill.scope && skill.scope.length > 0 && /* @__PURE__ */ jsxs2("section", { children: [
1643
+ /* @__PURE__ */ jsx2(Label2, { t, children: "Scope" }),
1644
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: skill.scope.map((s) => /* @__PURE__ */ jsx2(
1645
+ "span",
1646
+ {
1647
+ style: {
1648
+ padding: "2px 8px",
1649
+ background: t.bgElev,
1650
+ border: `1px solid ${t.border}`,
1651
+ borderRadius: 3,
1652
+ fontSize: 11,
1653
+ fontFamily: t.fontMono,
1654
+ color: t.textMuted
1655
+ },
1656
+ children: s
1657
+ },
1658
+ s
1659
+ )) })
1660
+ ] }),
1661
+ skill.tools && skill.tools.length > 0 && /* @__PURE__ */ jsxs2("section", { children: [
1662
+ /* @__PURE__ */ jsxs2(Label2, { t, children: [
1663
+ "Tools this skill exposes \xB7 ",
1664
+ skill.tools.length
1665
+ ] }),
1666
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: skill.tools.map((id) => /* @__PURE__ */ jsx2(
1667
+ "span",
1668
+ {
1669
+ style: {
1670
+ padding: "2px 8px",
1671
+ background: `color-mix(in srgb, ${t.accent} 15%, transparent)`,
1672
+ border: `1px solid ${t.border}`,
1673
+ borderRadius: 3,
1674
+ fontSize: 11,
1675
+ fontFamily: t.fontMono,
1676
+ color: t.accent
1677
+ },
1678
+ children: id
1679
+ },
1680
+ id
1681
+ )) }),
1682
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: 11, color: t.textSubtle, marginTop: 4, fontStyle: "italic" }, children: "Only these tools reach the LLM while this skill is active (autoActivate)." })
1683
+ ] }),
1684
+ skill.body && /* @__PURE__ */ jsxs2("section", { children: [
1685
+ /* @__PURE__ */ jsx2(Label2, { t, children: "Body (sent to LLM on read_skill)" }),
1686
+ /* @__PURE__ */ jsx2(
1687
+ "pre",
1688
+ {
1689
+ style: {
1690
+ margin: 0,
1691
+ padding: "10px 12px",
1692
+ background: t.bgElev,
1693
+ border: `1px solid ${t.border}`,
1694
+ borderRadius: 4,
1695
+ fontSize: 12,
1696
+ lineHeight: 1.55,
1697
+ fontFamily: t.fontMono,
1698
+ whiteSpace: "pre-wrap",
1699
+ color: t.text,
1700
+ maxHeight: 480,
1701
+ overflow: "auto"
1702
+ },
1703
+ children: skill.body
1704
+ }
1705
+ )
1706
+ ] })
1707
+ ] });
1708
+ }
1709
+ function SkillJson({ skill }) {
1710
+ const t = useLensTheme();
1711
+ return /* @__PURE__ */ jsx2(
1712
+ "pre",
1713
+ {
1714
+ style: {
1715
+ margin: 0,
1716
+ padding: "10px 12px",
1717
+ background: t.bgElev,
1718
+ border: `1px solid ${t.border}`,
1719
+ borderRadius: 4,
1720
+ fontSize: 12,
1721
+ lineHeight: 1.55,
1722
+ fontFamily: t.fontMono,
1723
+ whiteSpace: "pre-wrap",
1724
+ color: t.text,
1725
+ maxHeight: "calc(100vh - 200px)",
1726
+ overflow: "auto"
1727
+ },
1728
+ children: safeJsonStringify(skill)
1729
+ }
1730
+ );
1731
+ }
1732
+ function safeJsonStringify(value) {
1733
+ const seen = /* @__PURE__ */ new WeakSet();
1734
+ try {
1735
+ return JSON.stringify(
1736
+ value,
1737
+ (_k, v) => {
1738
+ if (typeof v === "object" && v !== null) {
1739
+ if (seen.has(v)) return "[Circular]";
1740
+ seen.add(v);
1741
+ }
1742
+ if (typeof v === "function") return `[function ${v.name || "anonymous"}]`;
1743
+ return v;
1744
+ },
1745
+ 2
1746
+ );
1747
+ } catch (err) {
1748
+ return `[stringify error: ${err instanceof Error ? err.message : String(err)}]`;
1749
+ }
1750
+ }
1751
+ function Label2({
1752
+ t,
1753
+ children
1754
+ }) {
1755
+ return /* @__PURE__ */ jsx2(
1756
+ "div",
1757
+ {
1758
+ style: {
1759
+ fontSize: 10,
1760
+ color: t.textSubtle,
1761
+ textTransform: "uppercase",
1762
+ letterSpacing: "0.08em",
1763
+ fontWeight: 600,
1764
+ marginBottom: 4
1765
+ },
1766
+ children
1767
+ }
1768
+ );
1769
+ }
1770
+
1771
+ // src/react/panels/StageFlow.tsx
1772
+ import { useEffect as useEffect2, useMemo, useRef as useRef2 } from "react";
1773
+ import {
1774
+ ReactFlow,
1775
+ Background,
1776
+ BackgroundVariant,
1777
+ Handle,
1778
+ Position,
1779
+ useReactFlow,
1780
+ BaseEdge,
1781
+ getSmoothStepPath
1782
+ } from "@xyflow/react";
1783
+ import "@xyflow/react/dist/style.css";
1784
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1785
+ var NODE_POSITIONS = {
1786
+ user: { x: 100, y: 20 },
1787
+ agent: { x: 100, y: 120 },
1788
+ tool: { x: 100, y: 360 },
1789
+ skill: { x: 320, y: 360 }
1790
+ };
1791
+ var CONTEXT_NODE_POSITION = { x: 325, y: 120 };
1792
+ var TOOLS_NODE_POSITION = { x: -150, y: 120 };
1793
+ function pickHandles(from, to) {
1794
+ if (from === "user" && to === "agent") return { sourceHandle: "b-out", targetHandle: "t-in" };
1795
+ if (from === "agent" && to === "user") return { sourceHandle: "t-out", targetHandle: "b-in" };
1796
+ if (from === "agent" && to === "tool") return { sourceHandle: "b-out", targetHandle: "t-in" };
1797
+ if (from === "tool" && to === "agent") return { sourceHandle: "l-out", targetHandle: "l-in" };
1798
+ if (from === "agent" && to === "skill") return { sourceHandle: "r-out", targetHandle: "l-in" };
1799
+ if (from === "skill" && to === "agent") return { sourceHandle: "r-out", targetHandle: "r-in" };
1800
+ return { sourceHandle: "r-out", targetHandle: "l-in" };
1801
+ }
1802
+ var NODE_LABELS = {
1803
+ user: "User",
1804
+ agent: "Agent",
1805
+ tool: "Tool",
1806
+ skill: "Skill"
1807
+ };
1808
+ var NODE_SUBLABELS = {
1809
+ user: "You",
1810
+ agent: "The LLM",
1811
+ tool: "Data source / action",
1812
+ // "Adds context + tools" is what the user actually observes when a
1813
+ // skill activates — the skill body lands in System Prompt and its
1814
+ // tool list surfaces in Tools. That's the whole effect; no need for
1815
+ // jargon like "bracket over a primitive."
1816
+ skill: "Adds context + tools"
1817
+ };
1818
+ function StageFlow({
1819
+ stages,
1820
+ focusIndex,
1821
+ onEdgeClick,
1822
+ height = 460,
1823
+ activeSkillId,
1824
+ timeline
1825
+ }) {
1826
+ const t = useLensTheme();
1827
+ const focus = focusIndex !== void 0 && focusIndex >= 0 ? focusIndex : stages.length - 1;
1828
+ const visible = useMemo(() => stages.slice(0, focus + 1), [stages, focus]);
1829
+ const activeStage = visible[visible.length - 1];
1830
+ const { activeInjectionsBySlot, activeLedger } = useMemo(() => {
1831
+ const bySlot = /* @__PURE__ */ new Map();
1832
+ if (!timeline || !activeStage) return { activeInjectionsBySlot: bySlot, activeLedger: {} };
1833
+ const turn = timeline.turns[activeStage.turnIndex];
1834
+ const iter = activeStage.iterIndex !== void 0 ? turn?.iterations.find((it) => it.index === activeStage.iterIndex) : void 0;
1835
+ const injections = iter && iter.contextInjections.length > 0 ? iter.contextInjections : turn?.contextInjections ?? [];
1836
+ for (const ci of injections) {
1837
+ const bucket = bySlot.get(ci.slot) ?? [];
1838
+ bucket.push(ci);
1839
+ bySlot.set(ci.slot, bucket);
1840
+ }
1841
+ const ledger = iter && iter.contextInjections.length > 0 ? iter.contextLedger : turn?.contextLedger ?? {};
1842
+ return { activeInjectionsBySlot: bySlot, activeLedger: ledger };
1843
+ }, [timeline, activeStage]);
1844
+ const touched = useMemo(() => {
1845
+ const set = /* @__PURE__ */ new Set();
1846
+ for (const s of visible) {
1847
+ set.add(s.from);
1848
+ set.add(s.to);
1849
+ if (s.alsoLights) set.add(s.alsoLights);
1850
+ }
1851
+ return set;
1852
+ }, [visible]);
1853
+ const activeNodes = useMemo(() => {
1854
+ const s = /* @__PURE__ */ new Set();
1855
+ if (activeStage) {
1856
+ s.add(activeStage.to);
1857
+ if (activeStage.alsoLights) s.add(activeStage.alsoLights);
1858
+ }
1859
+ return s;
1860
+ }, [activeStage]);
1861
+ const edges = useMemo(() => {
1862
+ const byKey = /* @__PURE__ */ new Map();
1863
+ visible.forEach((s) => {
1864
+ byKey.set(`${s.from}\u2192${s.to}`, { from: s.from, to: s.to, lastStage: s });
1865
+ });
1866
+ return [...byKey.values()].map(({ from, to, lastStage }) => {
1867
+ const isActive = activeStage !== void 0 && from === activeStage.from && to === activeStage.to;
1868
+ const { sourceHandle, targetHandle } = pickHandles(from, to);
1869
+ const isLoop = to === "agent" && (from === "tool" || from === "skill");
1870
+ return {
1871
+ id: `${from}\u2192${to}`,
1872
+ source: from,
1873
+ target: to,
1874
+ sourceHandle,
1875
+ targetHandle,
1876
+ type: "labelled",
1877
+ // Marching-ants only when this loop edge is the active stage.
1878
+ animated: isLoop && isActive,
1879
+ data: {
1880
+ primitive: lastStage.primitive,
1881
+ active: isActive,
1882
+ isLoop,
1883
+ stage: lastStage
1884
+ }
1885
+ };
1886
+ });
1887
+ }, [visible, activeStage]);
1888
+ const toolsRoster = useMemo(() => {
1889
+ if (!timeline || !activeStage || activeStage.iterIndex === void 0) return null;
1890
+ const turn = timeline.turns[activeStage.turnIndex];
1891
+ if (!turn) return null;
1892
+ const iterIdx = turn.iterations.findIndex((it) => it.index === activeStage.iterIndex);
1893
+ if (iterIdx < 0) return null;
1894
+ const iter = turn.iterations[iterIdx];
1895
+ const names = iter.visibleTools;
1896
+ if (!names || names.length === 0) return null;
1897
+ const prevIter = iterIdx > 0 ? turn.iterations[iterIdx - 1] : void 0;
1898
+ const prevCount = prevIter?.visibleTools?.length ?? 0;
1899
+ const delta = names.length - prevCount;
1900
+ const skillInjection = iter.contextInjections.find((ci) => ci.source === "skill");
1901
+ return {
1902
+ names,
1903
+ delta,
1904
+ ...delta > 0 && skillInjection ? { deltaSource: "skill" } : {}
1905
+ };
1906
+ }, [timeline, activeStage]);
1907
+ const contextSummary = useMemo(() => {
1908
+ const bySource = /* @__PURE__ */ new Map();
1909
+ for (const list of activeInjectionsBySlot.values()) {
1910
+ for (const ci of list) {
1911
+ const entry = bySource.get(ci.source) ?? {
1912
+ source: ci.source,
1913
+ slots: /* @__PURE__ */ new Set(),
1914
+ labels: [],
1915
+ count: 0,
1916
+ ledger: {}
1917
+ };
1918
+ entry.slots.add(ci.slot);
1919
+ entry.labels.push(ci.label);
1920
+ entry.count += 1;
1921
+ const d = ci.deltaCount;
1922
+ if (d) {
1923
+ for (const [key, val] of Object.entries(d)) {
1924
+ if (typeof val === "number") {
1925
+ const prev = typeof entry.ledger[key] === "number" ? entry.ledger[key] : 0;
1926
+ entry.ledger[key] = prev + val;
1927
+ } else if (typeof val === "boolean") {
1928
+ entry.ledger[key] = entry.ledger[key] === true || val;
1929
+ }
1930
+ }
1931
+ }
1932
+ bySource.set(ci.source, entry);
1933
+ }
1934
+ }
1935
+ return [...bySource.values()];
1936
+ }, [activeInjectionsBySlot]);
1937
+ const nodes = useMemo(() => {
1938
+ const base = Object.keys(NODE_POSITIONS).filter((id) => {
1939
+ if (id === "skill") return touched.has("skill");
1940
+ return true;
1941
+ }).map((id) => ({
1942
+ id,
1943
+ type: "lens",
1944
+ position: NODE_POSITIONS[id],
1945
+ data: {
1946
+ id,
1947
+ active: activeNodes.has(id),
1948
+ touched: touched.has(id),
1949
+ // Which of the Agent's three ports actually MUTATED this step.
1950
+ // Multiple can be true at once (read_skill touches all three).
1951
+ ...id === "agent" && activeStage ? { activeMutations: activeStage.mutations } : {},
1952
+ ...id === "agent" ? { activeInjectionsBySlot, activeLedger } : {},
1953
+ // Skill annotation on the Agent node — tells the user which
1954
+ // skill is governing the current System Prompt + Tools. Pure
1955
+ // context signal; doesn't affect layout.
1956
+ ...id === "agent" && activeSkillId ? { activeSkillId } : {},
1957
+ // Tool node shows the SPECIFIC tool name that's currently
1958
+ // being called — debugging without this is guesswork ("we
1959
+ // called a tool — but which one?"). Only surfaces when the
1960
+ // active stage actually references a tool name.
1961
+ ...id === "tool" && activeStage?.toolName && (activeStage.from === "tool" || activeStage.to === "tool") ? {
1962
+ activeLabel: activeStage.toolName,
1963
+ ...activeStage.parallelCount ? { parallelCount: activeStage.parallelCount } : {}
1964
+ } : {}
1965
+ },
1966
+ draggable: false
1967
+ }));
1968
+ if (contextSummary.length > 0) {
1969
+ base.push({
1970
+ id: "context",
1971
+ type: "context",
1972
+ position: CONTEXT_NODE_POSITION,
1973
+ data: { sources: contextSummary },
1974
+ draggable: false
1975
+ });
1976
+ }
1977
+ if (toolsRoster) {
1978
+ base.push({
1979
+ id: "tools-list",
1980
+ type: "tools-list",
1981
+ position: TOOLS_NODE_POSITION,
1982
+ data: toolsRoster,
1983
+ draggable: false
1984
+ });
1985
+ }
1986
+ return base;
1987
+ }, [
1988
+ activeNodes,
1989
+ touched,
1990
+ activeStage,
1991
+ activeSkillId,
1992
+ activeInjectionsBySlot,
1993
+ activeLedger,
1994
+ contextSummary,
1995
+ toolsRoster
1996
+ ]);
1997
+ const nodeTypes = useMemo(
1998
+ () => ({ lens: LensNode, context: ContextNode, "tools-list": ToolsListNode }),
1999
+ []
2000
+ );
2001
+ const edgeTypes = useMemo(() => ({ labelled: LensEdge(onEdgeClick) }), [onEdgeClick]);
2002
+ const fitKey = useMemo(
2003
+ () => nodes.map((n) => n.id).sort().join("|"),
2004
+ [nodes]
2005
+ );
2006
+ return /* @__PURE__ */ jsxs3(
2007
+ "div",
2008
+ {
2009
+ "data-fp-lens": "stage-flow",
2010
+ style: {
2011
+ height,
2012
+ background: t.bg,
2013
+ borderBottom: `1px solid ${t.border}`,
2014
+ // ResizeObserver target — FitViewOnResize watches this element
2015
+ // (not the window) so the graph refits when the host panel
2016
+ // changes width via splitter drag, not just window resize.
2017
+ position: "relative"
2018
+ },
2019
+ children: [
2020
+ /* @__PURE__ */ jsx3(EdgeMarkerDefs, {}),
2021
+ /* @__PURE__ */ jsxs3(
2022
+ ReactFlow,
2023
+ {
2024
+ nodes,
2025
+ edges,
2026
+ nodeTypes,
2027
+ edgeTypes,
2028
+ fitView: true,
2029
+ fitViewOptions: { padding: 0.2, maxZoom: 1 },
2030
+ proOptions: { hideAttribution: true },
2031
+ nodesDraggable: false,
2032
+ nodesConnectable: false,
2033
+ elementsSelectable: false,
2034
+ panOnDrag: false,
2035
+ zoomOnScroll: false,
2036
+ zoomOnPinch: false,
2037
+ zoomOnDoubleClick: false,
2038
+ children: [
2039
+ /* @__PURE__ */ jsx3(FitViewOnResize, { fitKey }),
2040
+ /* @__PURE__ */ jsx3(
2041
+ Background,
2042
+ {
2043
+ variant: BackgroundVariant.Dots,
2044
+ gap: 18,
2045
+ size: 1,
2046
+ color: t.border
2047
+ }
2048
+ )
2049
+ ]
2050
+ }
2051
+ )
2052
+ ]
2053
+ }
2054
+ );
2055
+ }
2056
+ function FitViewOnResize({ fitKey }) {
2057
+ const { fitView } = useReactFlow();
2058
+ const lastKeyRef = useRef2("");
2059
+ useEffect2(() => {
2060
+ if (fitKey !== lastKeyRef.current) {
2061
+ lastKeyRef.current = fitKey;
2062
+ const t = setTimeout(
2063
+ () => requestAnimationFrame(() => fitView({ padding: 0.2, maxZoom: 1 })),
2064
+ 50
2065
+ );
2066
+ return () => clearTimeout(t);
2067
+ }
2068
+ }, [fitKey, fitView]);
2069
+ useEffect2(() => {
2070
+ const root = document.querySelector('[data-fp-lens="stage-flow"]');
2071
+ if (!root) return;
2072
+ const refit = () => requestAnimationFrame(() => fitView({ padding: 0.2, maxZoom: 1 }));
2073
+ const ro = new ResizeObserver(refit);
2074
+ ro.observe(root);
2075
+ window.addEventListener("resize", refit);
2076
+ return () => {
2077
+ ro.disconnect();
2078
+ window.removeEventListener("resize", refit);
2079
+ };
2080
+ }, [fitView]);
2081
+ return null;
2082
+ }
2083
+ function EdgeMarkerDefs() {
2084
+ const t = useLensTheme();
2085
+ const active = t.accent;
2086
+ const loop = `color-mix(in srgb, ${t.accent} 55%, ${t.border})`;
2087
+ const dim = t.border;
2088
+ return /* @__PURE__ */ jsx3(
2089
+ "svg",
2090
+ {
2091
+ "aria-hidden": "true",
2092
+ style: { position: "absolute", width: 0, height: 0, pointerEvents: "none" },
2093
+ children: /* @__PURE__ */ jsx3("defs", { children: [
2094
+ { id: "lens-arrow-active", fill: active },
2095
+ { id: "lens-arrow-loop", fill: loop },
2096
+ { id: "lens-arrow-dim", fill: dim }
2097
+ ].map((m) => /* @__PURE__ */ jsx3(
2098
+ "marker",
2099
+ {
2100
+ id: m.id,
2101
+ viewBox: "0 0 10 10",
2102
+ refX: "9",
2103
+ refY: "5",
2104
+ markerWidth: "6",
2105
+ markerHeight: "6",
2106
+ orient: "auto-start-reverse",
2107
+ children: /* @__PURE__ */ jsx3("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: m.fill })
2108
+ },
2109
+ m.id
2110
+ )) })
2111
+ }
2112
+ );
2113
+ }
2114
+ var NODE_KEYFRAMES_ID = "fp-lens-node-keyframes";
2115
+ var NODE_KEYFRAMES_CSS = `
2116
+ @media (prefers-reduced-motion: no-preference) {
2117
+ @keyframes fp-lens-pulse {
2118
+ 0%, 100% { opacity: 0.4; transform: scale(1); }
2119
+ 50% { opacity: 0.12; transform: scale(1.08); }
2120
+ }
2121
+ }
2122
+ @media (prefers-reduced-motion: reduce) {
2123
+ @keyframes fp-lens-pulse { 0%, 100% { opacity: 0.3; } }
2124
+ }
2125
+ `;
2126
+ function injectNodeKeyframes() {
2127
+ if (typeof document === "undefined") return;
2128
+ if (document.getElementById(NODE_KEYFRAMES_ID)) return;
2129
+ const el = document.createElement("style");
2130
+ el.id = NODE_KEYFRAMES_ID;
2131
+ el.textContent = NODE_KEYFRAMES_CSS;
2132
+ document.head.appendChild(el);
2133
+ }
2134
+ function LensNode({ data }) {
2135
+ const t = useLensTheme();
2136
+ const d = data;
2137
+ injectNodeKeyframes();
2138
+ const isActive = d.active;
2139
+ const isDone = !d.active && d.touched;
2140
+ const bg = isActive ? t.accent : isDone ? t.bgElev : t.bg;
2141
+ const border = isActive ? t.accent : isDone ? t.border : t.border;
2142
+ const textColor = isActive ? "#ffffff" : isDone ? t.text : t.textSubtle;
2143
+ const shadow = isActive ? `0 0 18px color-mix(in srgb, ${t.accent} 42%, transparent)` : isDone ? `0 2px 8px rgba(0,0,0,0.18)` : `0 1px 3px rgba(0,0,0,0.08)`;
2144
+ const label = NODE_LABELS[d.id];
2145
+ const sub = isActive && d.activeLabel ? d.activeLabel : NODE_SUBLABELS[d.id];
2146
+ const isAgent = d.id === "agent";
2147
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative", display: "inline-block" }, children: [
2148
+ isActive && /* @__PURE__ */ jsx3(
2149
+ "div",
2150
+ {
2151
+ style: {
2152
+ position: "absolute",
2153
+ inset: -6,
2154
+ borderRadius: 14,
2155
+ border: `2px solid ${t.accent}`,
2156
+ opacity: 0.35,
2157
+ pointerEvents: "none",
2158
+ animation: "fp-lens-pulse 1.6s ease-out infinite"
2159
+ }
2160
+ }
2161
+ ),
2162
+ /* @__PURE__ */ jsxs3(
2163
+ "div",
2164
+ {
2165
+ style: {
2166
+ width: isAgent ? 200 : 150,
2167
+ padding: "12px 16px",
2168
+ borderRadius: 10,
2169
+ background: bg,
2170
+ border: `2px solid ${border}`,
2171
+ color: textColor,
2172
+ fontFamily: t.fontSans,
2173
+ textAlign: "center",
2174
+ boxShadow: shadow,
2175
+ transition: "background 220ms ease, border-color 220ms ease, box-shadow 220ms ease, color 220ms ease"
2176
+ },
2177
+ children: [
2178
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }, children: [
2179
+ /* @__PURE__ */ jsx3(NodeIcon, { id: d.id, color: textColor }),
2180
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 13, fontWeight: 600 }, children: label })
2181
+ ] }),
2182
+ /* @__PURE__ */ jsx3(
2183
+ "div",
2184
+ {
2185
+ style: {
2186
+ fontSize: isActive && d.activeLabel ? 11 : 10,
2187
+ color: isActive ? "rgba(255,255,255,0.95)" : t.textSubtle,
2188
+ marginTop: 2,
2189
+ fontWeight: isActive && d.activeLabel ? 600 : 400,
2190
+ fontFamily: isActive && d.activeLabel ? t.fontMono : t.fontSans,
2191
+ maxWidth: isAgent ? 180 : 130,
2192
+ overflow: "hidden",
2193
+ textOverflow: "ellipsis",
2194
+ whiteSpace: "nowrap",
2195
+ margin: "2px auto 0"
2196
+ },
2197
+ title: sub,
2198
+ children: sub
2199
+ }
2200
+ ),
2201
+ d.id === "tool" && isActive && (d.parallelCount ?? 0) > 1 && /* @__PURE__ */ jsxs3(
2202
+ "div",
2203
+ {
2204
+ title: `This tool is one of ${d.parallelCount} called in parallel this round`,
2205
+ style: {
2206
+ marginTop: 4,
2207
+ alignSelf: "center",
2208
+ display: "inline-block",
2209
+ padding: "1px 7px",
2210
+ borderRadius: 999,
2211
+ background: "rgba(255,255,255,0.25)",
2212
+ color: "#ffffff",
2213
+ fontSize: 9,
2214
+ fontWeight: 700,
2215
+ letterSpacing: "0.08em",
2216
+ textTransform: "uppercase"
2217
+ },
2218
+ children: [
2219
+ "\u26A1 Parallel \xB7 ",
2220
+ d.parallelCount
2221
+ ]
2222
+ }
2223
+ ),
2224
+ isAgent && /* @__PURE__ */ jsxs3(Fragment3, { children: [
2225
+ d.activeSkillId && /* @__PURE__ */ jsxs3(
2226
+ "div",
2227
+ {
2228
+ title: `System Prompt + Tools are currently governed by the ${d.activeSkillId} skill`,
2229
+ style: {
2230
+ marginTop: 8,
2231
+ padding: "3px 9px",
2232
+ borderRadius: 999,
2233
+ background: isActive ? "rgba(255,255,255,0.18)" : t.bgElev,
2234
+ border: `1px solid ${isActive ? "rgba(255,255,255,0.35)" : t.border}`,
2235
+ color: isActive ? "#ffffff" : t.accent,
2236
+ fontSize: 10,
2237
+ fontFamily: t.fontMono,
2238
+ fontWeight: 600,
2239
+ whiteSpace: "nowrap",
2240
+ maxWidth: 180,
2241
+ overflow: "hidden",
2242
+ textOverflow: "ellipsis",
2243
+ display: "inline-block"
2244
+ },
2245
+ children: [
2246
+ "\u{1F4DA} ",
2247
+ d.activeSkillId
2248
+ ]
2249
+ }
2250
+ ),
2251
+ /* @__PURE__ */ jsx3(
2252
+ AgentPorts,
2253
+ {
2254
+ active: d.active,
2255
+ mutations: d.activeMutations,
2256
+ filledCard: isActive,
2257
+ injectionsBySlot: d.activeInjectionsBySlot,
2258
+ ledger: d.activeLedger
2259
+ }
2260
+ )
2261
+ ] }),
2262
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: Position.Top, id: "t-out", style: handleStyle(-14, 0) }),
2263
+ /* @__PURE__ */ jsx3(Handle, { type: "target", position: Position.Top, id: "t-in", style: handleStyle(14, 0) }),
2264
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: Position.Bottom, id: "b-out", style: handleStyle(14, 0) }),
2265
+ /* @__PURE__ */ jsx3(Handle, { type: "target", position: Position.Bottom, id: "b-in", style: handleStyle(-14, 0) }),
2266
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: Position.Left, id: "l-out", style: handleStyle(0, 10) }),
2267
+ /* @__PURE__ */ jsx3(Handle, { type: "target", position: Position.Left, id: "l-in", style: handleStyle(0, -10) }),
2268
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: Position.Right, id: "r-out", style: handleStyle(0, -10) }),
2269
+ /* @__PURE__ */ jsx3(Handle, { type: "target", position: Position.Right, id: "r-in", style: handleStyle(0, 10) })
2270
+ ]
2271
+ }
2272
+ )
2273
+ ] });
2274
+ }
2275
+ function ContextNode({ data }) {
2276
+ const t = useLensTheme();
2277
+ const d = data;
2278
+ if (!d.sources || d.sources.length === 0) return null;
2279
+ return /* @__PURE__ */ jsxs3(
2280
+ "div",
2281
+ {
2282
+ style: {
2283
+ width: 200,
2284
+ padding: "8px 10px",
2285
+ borderRadius: 8,
2286
+ background: "transparent",
2287
+ border: `1.5px dashed ${t.accent}`,
2288
+ color: t.text,
2289
+ fontFamily: t.fontSans
2290
+ },
2291
+ children: [
2292
+ /* @__PURE__ */ jsx3(
2293
+ "div",
2294
+ {
2295
+ style: {
2296
+ fontSize: 9,
2297
+ color: t.textSubtle,
2298
+ textTransform: "uppercase",
2299
+ letterSpacing: "0.08em",
2300
+ fontWeight: 600,
2301
+ marginBottom: 6
2302
+ },
2303
+ children: "Context engineered"
2304
+ }
2305
+ ),
2306
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: d.sources.map((s) => {
2307
+ const slot = [...s.slots][0];
2308
+ const deltas = describeSourceDeltas(s.ledger, s.labels);
2309
+ return /* @__PURE__ */ jsxs3(
2310
+ "div",
2311
+ {
2312
+ title: s.labels.join(" \xB7 "),
2313
+ style: {
2314
+ display: "flex",
2315
+ flexDirection: "column",
2316
+ gap: 2
2317
+ },
2318
+ children: [
2319
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
2320
+ /* @__PURE__ */ jsxs3(
2321
+ "span",
2322
+ {
2323
+ style: {
2324
+ fontFamily: t.fontMono,
2325
+ fontWeight: 700,
2326
+ letterSpacing: "0.06em",
2327
+ textTransform: "uppercase",
2328
+ color: t.accent,
2329
+ fontSize: 9,
2330
+ padding: "1px 5px",
2331
+ border: `1px solid ${t.accent}`,
2332
+ borderRadius: 3
2333
+ },
2334
+ children: [
2335
+ s.source,
2336
+ s.count > 1 ? ` \xD7${s.count}` : ""
2337
+ ]
2338
+ }
2339
+ ),
2340
+ /* @__PURE__ */ jsx3("span", { style: { color: t.textSubtle, fontSize: 9 }, children: "\u2192" }),
2341
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 9, color: t.textMuted }, children: slotShort(slot) })
2342
+ ] }),
2343
+ deltas.length > 0 && /* @__PURE__ */ jsx3(
2344
+ "div",
2345
+ {
2346
+ style: {
2347
+ paddingLeft: 8,
2348
+ display: "flex",
2349
+ flexWrap: "wrap",
2350
+ gap: 4
2351
+ },
2352
+ children: deltas.map((dlabel) => /* @__PURE__ */ jsx3(
2353
+ "span",
2354
+ {
2355
+ style: {
2356
+ fontSize: 9,
2357
+ padding: "0 4px",
2358
+ borderRadius: 3,
2359
+ fontFamily: t.fontMono,
2360
+ color: t.text,
2361
+ background: `color-mix(in srgb, ${t.accent} 12%, transparent)`,
2362
+ border: `1px dashed ${t.accent}`
2363
+ },
2364
+ children: dlabel
2365
+ },
2366
+ dlabel
2367
+ ))
2368
+ }
2369
+ )
2370
+ ]
2371
+ },
2372
+ s.source
2373
+ );
2374
+ }) })
2375
+ ]
2376
+ }
2377
+ );
2378
+ }
2379
+ function describeSourceDeltas(ledger, labels) {
2380
+ const out = [];
2381
+ const n = (k) => typeof ledger[k] === "number" ? ledger[k] : 0;
2382
+ const chars = n("systemPromptChars");
2383
+ if (chars > 0) {
2384
+ out.push(chars >= 1e3 ? `+${(chars / 1e3).toFixed(1)}k chars` : `+${chars} chars`);
2385
+ }
2386
+ const tools = n("tools");
2387
+ if (tools > 0) out.push(`+${tools} tools`);
2388
+ if (ledger["toolsFromSkill"] === true && tools === 0) out.push("+? tools");
2389
+ const sys = n("system");
2390
+ const tool = n("tool");
2391
+ const user = n("user");
2392
+ if (sys > 0) out.push(`+${sys} sys msg${sys === 1 ? "" : "s"}`);
2393
+ if (tool > 0) out.push(`+${tool} tool msg${tool === 1 ? "" : "s"}`);
2394
+ if (user > 0) out.push(`+${user} user msg${user === 1 ? "" : "s"}`);
2395
+ if (out.length === 0 && labels.length > 0) out.push(labels[0]);
2396
+ return out;
2397
+ }
2398
+ function ToolsListNode({ data }) {
2399
+ const t = useLensTheme();
2400
+ const d = data;
2401
+ if (!d.names || d.names.length === 0) return null;
2402
+ const MAX_VISIBLE = 6;
2403
+ const visible = d.names.slice(0, MAX_VISIBLE);
2404
+ const overflow = d.names.length - visible.length;
2405
+ return /* @__PURE__ */ jsxs3(
2406
+ "div",
2407
+ {
2408
+ style: {
2409
+ width: 130,
2410
+ padding: "8px 10px",
2411
+ borderRadius: 8,
2412
+ background: "transparent",
2413
+ border: `1.5px dashed ${t.accent}`,
2414
+ color: t.text,
2415
+ fontFamily: t.fontSans
2416
+ },
2417
+ children: [
2418
+ /* @__PURE__ */ jsxs3(
2419
+ "div",
2420
+ {
2421
+ style: {
2422
+ display: "flex",
2423
+ alignItems: "baseline",
2424
+ justifyContent: "space-between",
2425
+ marginBottom: 6
2426
+ },
2427
+ children: [
2428
+ /* @__PURE__ */ jsxs3(
2429
+ "span",
2430
+ {
2431
+ style: {
2432
+ fontSize: 9,
2433
+ color: t.textSubtle,
2434
+ textTransform: "uppercase",
2435
+ letterSpacing: "0.08em",
2436
+ fontWeight: 600
2437
+ },
2438
+ children: [
2439
+ "Tools \xB7 ",
2440
+ d.names.length
2441
+ ]
2442
+ }
2443
+ ),
2444
+ d.delta > 0 && /* @__PURE__ */ jsxs3(
2445
+ "span",
2446
+ {
2447
+ title: d.deltaSource ? `+${d.delta} added by ${d.deltaSource} this iteration` : `+${d.delta} since previous iteration`,
2448
+ style: {
2449
+ fontSize: 9,
2450
+ padding: "0 4px",
2451
+ borderRadius: 3,
2452
+ fontFamily: t.fontMono,
2453
+ fontWeight: 700,
2454
+ color: t.accent,
2455
+ border: `1px dashed ${t.accent}`
2456
+ },
2457
+ children: [
2458
+ "+",
2459
+ d.delta,
2460
+ d.deltaSource ? ` \xB7 ${d.deltaSource}` : ""
2461
+ ]
2462
+ }
2463
+ )
2464
+ ]
2465
+ }
2466
+ ),
2467
+ /* @__PURE__ */ jsxs3(
2468
+ "div",
2469
+ {
2470
+ style: {
2471
+ display: "flex",
2472
+ flexDirection: "column",
2473
+ gap: 2,
2474
+ fontFamily: t.fontMono,
2475
+ fontSize: 9,
2476
+ color: t.textMuted
2477
+ },
2478
+ children: [
2479
+ visible.map((name) => /* @__PURE__ */ jsx3(
2480
+ "div",
2481
+ {
2482
+ title: name,
2483
+ style: {
2484
+ overflow: "hidden",
2485
+ textOverflow: "ellipsis",
2486
+ whiteSpace: "nowrap"
2487
+ },
2488
+ children: name
2489
+ },
2490
+ name
2491
+ )),
2492
+ overflow > 0 && /* @__PURE__ */ jsxs3("div", { style: { color: t.textSubtle, fontStyle: "italic" }, children: [
2493
+ "+",
2494
+ overflow,
2495
+ " more"
2496
+ ] })
2497
+ ]
2498
+ }
2499
+ )
2500
+ ]
2501
+ }
2502
+ );
2503
+ }
2504
+ function slotShort(slot) {
2505
+ if (slot === "system-prompt") return "sys prompt";
2506
+ if (slot === "tools") return "tools";
2507
+ return "messages";
2508
+ }
2509
+ function NodeIcon({ id, color }) {
2510
+ const size = 16;
2511
+ const props = {
2512
+ width: size,
2513
+ height: size,
2514
+ viewBox: `0 0 ${size} ${size}`,
2515
+ fill: "none",
2516
+ style: { flexShrink: 0 }
2517
+ };
2518
+ if (id === "user") {
2519
+ return /* @__PURE__ */ jsxs3("svg", { ...props, children: [
2520
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "5", r: "2.5", stroke: color, strokeWidth: "1.5" }),
2521
+ /* @__PURE__ */ jsx3("path", { d: "M3.5 14C3.5 11 5.5 9 8 9S12.5 11 12.5 14", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" })
2522
+ ] });
2523
+ }
2524
+ if (id === "agent") {
2525
+ return /* @__PURE__ */ jsxs3("svg", { ...props, children: [
2526
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "6", stroke: color, strokeWidth: "1.5" }),
2527
+ /* @__PURE__ */ jsx3("path", { d: "M5.5 8C5.5 6.5 6.5 5 8 5S10.5 6.5 10.5 8", stroke: color, strokeWidth: "1.2", strokeLinecap: "round" }),
2528
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "9.5", r: "1", fill: color }),
2529
+ /* @__PURE__ */ jsx3("line", { x1: "8", y1: "2", x2: "8", y2: "3.5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
2530
+ /* @__PURE__ */ jsx3("line", { x1: "12.5", y1: "4", x2: "11.2", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
2531
+ /* @__PURE__ */ jsx3("line", { x1: "3.5", y1: "4", x2: "4.8", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
2532
+ ] });
2533
+ }
2534
+ if (id === "tool") {
2535
+ return /* @__PURE__ */ jsxs3("svg", { ...props, children: [
2536
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "3", stroke: color, strokeWidth: "1.5" }),
2537
+ [0, 45, 90, 135, 180, 225, 270, 315].map((angle) => {
2538
+ const rad = angle * Math.PI / 180;
2539
+ const x1 = 8 + Math.cos(rad) * 4.5;
2540
+ const y1 = 8 + Math.sin(rad) * 4.5;
2541
+ const x2 = 8 + Math.cos(rad) * 6;
2542
+ const y2 = 8 + Math.sin(rad) * 6;
2543
+ return /* @__PURE__ */ jsx3(
2544
+ "line",
2545
+ {
2546
+ x1,
2547
+ y1,
2548
+ x2,
2549
+ y2,
2550
+ stroke: color,
2551
+ strokeWidth: "1.5",
2552
+ strokeLinecap: "round"
2553
+ },
2554
+ angle
2555
+ );
2556
+ })
2557
+ ] });
2558
+ }
2559
+ return /* @__PURE__ */ jsxs3("svg", { ...props, children: [
2560
+ /* @__PURE__ */ jsx3("path", { d: "M3 4h7a2 2 0 0 1 2 2v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4z", stroke: color, strokeWidth: "1.4", strokeLinejoin: "round" }),
2561
+ /* @__PURE__ */ jsx3("path", { d: "M5.5 7h4M5.5 9.5h4", stroke: color, strokeWidth: "1.2", strokeLinecap: "round" })
2562
+ ] });
2563
+ }
2564
+ function handleStyle(dx, dy) {
2565
+ return {
2566
+ ...HANDLE_STYLE,
2567
+ transform: `translate(${dx}px, ${dy}px)`
2568
+ };
2569
+ }
2570
+ function AgentPorts({
2571
+ active,
2572
+ mutations,
2573
+ filledCard,
2574
+ injectionsBySlot,
2575
+ ledger
2576
+ }) {
2577
+ const t = useLensTheme();
2578
+ const litSP = active && mutations?.systemPrompt === true;
2579
+ const litMsg = active && mutations?.messages === true;
2580
+ const litTools = active && mutations?.tools === true;
2581
+ const ports = [
2582
+ {
2583
+ key: "system-prompt",
2584
+ label: "System Prompt",
2585
+ hint: "Instructions Neo runs on",
2586
+ lit: litSP
2587
+ },
2588
+ {
2589
+ key: "message",
2590
+ label: "Messages",
2591
+ hint: "Conversation so far",
2592
+ lit: litMsg
2593
+ },
2594
+ {
2595
+ key: "tool",
2596
+ label: "Tools",
2597
+ hint: "What Neo can call",
2598
+ lit: litTools
2599
+ }
2600
+ ];
2601
+ const boxBg = filledCard ? "rgba(255,255,255,0.12)" : t.bg;
2602
+ const boxBorder = filledCard ? "rgba(255,255,255,0.25)" : t.border;
2603
+ const portIdle = filledCard ? "rgba(255,255,255,0.7)" : t.textMuted;
2604
+ const portLitBg = filledCard ? "rgba(255,255,255,0.25)" : `color-mix(in srgb, ${t.accent} 30%, transparent)`;
2605
+ const portLitColor = filledCard ? "#ffffff" : t.accent;
2606
+ const portLitBorder = filledCard ? "#ffffff" : t.accent;
2607
+ const portIdleBorder = filledCard ? "rgba(255,255,255,0.3)" : t.border;
2608
+ return /* @__PURE__ */ jsx3(
2609
+ "div",
2610
+ {
2611
+ style: {
2612
+ display: "flex",
2613
+ flexDirection: "column",
2614
+ gap: 3,
2615
+ marginTop: 8,
2616
+ padding: 4,
2617
+ background: boxBg,
2618
+ border: `1px solid ${boxBorder}`,
2619
+ borderRadius: 6
2620
+ },
2621
+ children: ports.map((p) => {
2622
+ const injectionSlotKey = p.key === "message" ? "messages" : p.key === "tool" ? "tools" : "system-prompt";
2623
+ const injections = injectionsBySlot?.get(injectionSlotKey) ?? [];
2624
+ const slotDelta = computeSlotDelta(p.key, ledger);
2625
+ const augmented = injections.length > 0 || slotDelta !== null;
2626
+ const baseBorderColor = p.lit ? portLitBorder : portIdleBorder;
2627
+ const borderStyle = augmented && !p.lit ? "dashed" : "solid";
2628
+ return /* @__PURE__ */ jsxs3(
2629
+ "div",
2630
+ {
2631
+ title: p.hint,
2632
+ style: {
2633
+ padding: "2px 8px",
2634
+ borderRadius: 3,
2635
+ background: p.lit ? portLitBg : "transparent",
2636
+ color: p.lit ? portLitColor : portIdle,
2637
+ fontSize: 10,
2638
+ fontWeight: p.lit ? 600 : 500,
2639
+ letterSpacing: "0.02em",
2640
+ textAlign: "left",
2641
+ fontFamily: t.fontSans,
2642
+ display: "flex",
2643
+ alignItems: "center",
2644
+ gap: 6,
2645
+ borderLeft: `2px ${borderStyle} ${baseBorderColor}`,
2646
+ // Whole-row dotted ring when augmented but not currently
2647
+ // lit — makes the "ledger has additions" status visible
2648
+ // on inactive slots without yelling.
2649
+ ...augmented && !p.lit ? {
2650
+ outline: `1px dashed ${filledCard ? "rgba(255,255,255,0.4)" : t.accent}`,
2651
+ outlineOffset: -1
2652
+ } : {}
2653
+ },
2654
+ children: [
2655
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 8 }, children: "\u25B8" }),
2656
+ /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: p.label })
2657
+ ]
2658
+ },
2659
+ p.key
2660
+ );
2661
+ })
2662
+ }
2663
+ );
2664
+ }
2665
+ var HANDLE_STYLE = {
2666
+ opacity: 0,
2667
+ pointerEvents: "none",
2668
+ width: 1,
2669
+ height: 1
2670
+ };
2671
+ function LensEdge(onEdgeClick) {
2672
+ return function LensEdgeInner({
2673
+ id,
2674
+ sourceX,
2675
+ sourceY,
2676
+ targetX,
2677
+ targetY,
2678
+ sourcePosition,
2679
+ targetPosition,
2680
+ data
2681
+ }) {
2682
+ const t = useLensTheme();
2683
+ const d = data;
2684
+ const [edgePath] = getSmoothStepPath({
2685
+ sourceX,
2686
+ sourceY,
2687
+ sourcePosition,
2688
+ targetX,
2689
+ targetY,
2690
+ targetPosition,
2691
+ borderRadius: 10
2692
+ });
2693
+ const stroke = d.active ? t.accent : t.border;
2694
+ const strokeWidth = d.active ? 2.25 : 1.5;
2695
+ const strokeDasharray = d.isLoop ? "5 4" : void 0;
2696
+ const markerId = d.active ? "lens-arrow-active" : "lens-arrow-dim";
2697
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
2698
+ /* @__PURE__ */ jsx3(
2699
+ BaseEdge,
2700
+ {
2701
+ id,
2702
+ path: edgePath,
2703
+ style: {
2704
+ stroke,
2705
+ strokeWidth,
2706
+ strokeDasharray,
2707
+ filter: d.active ? `drop-shadow(0 0 6px color-mix(in srgb, ${t.accent} 50%, transparent))` : void 0,
2708
+ cursor: onEdgeClick ? "pointer" : "default"
2709
+ },
2710
+ markerEnd: `url(#${markerId})`
2711
+ }
2712
+ ),
2713
+ onEdgeClick && /* @__PURE__ */ jsx3(
2714
+ "path",
2715
+ {
2716
+ d: edgePath,
2717
+ fill: "none",
2718
+ stroke: "transparent",
2719
+ strokeWidth: 12,
2720
+ style: { cursor: "pointer" },
2721
+ onClick: () => onEdgeClick(d.stage)
2722
+ }
2723
+ )
2724
+ ] });
2725
+ };
2726
+ }
2727
+ function computeSlotDelta(portKey, ledger) {
2728
+ if (!ledger) return null;
2729
+ const num = (k) => typeof ledger[k] === "number" ? ledger[k] : 0;
2730
+ if (portKey === "system-prompt") {
2731
+ const chars = num("systemPromptChars");
2732
+ if (chars <= 0) return null;
2733
+ return chars >= 1e3 ? `+${(chars / 1e3).toFixed(1)}k chars` : `+${chars} chars`;
2734
+ }
2735
+ if (portKey === "message") {
2736
+ const sys = num("system");
2737
+ const tool = num("tool");
2738
+ const user = num("user");
2739
+ const parts = [];
2740
+ if (sys > 0) parts.push(`system +${sys}`);
2741
+ if (tool > 0) parts.push(`tool +${tool}`);
2742
+ if (user > 0) parts.push(`user +${user}`);
2743
+ return parts.length > 0 ? parts.join(" \xB7 ") : null;
2744
+ }
2745
+ if (portKey === "tool") {
2746
+ const tools = num("tools");
2747
+ const fromSkill = ledger["toolsFromSkill"] === true;
2748
+ if (tools > 0) return `+${tools} tools`;
2749
+ if (fromSkill) return "+? tools (skill)";
2750
+ return null;
2751
+ }
2752
+ return null;
2753
+ }
2754
+
2755
+ // src/react/panels/TimeTravel.tsx
2756
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2757
+ function TimeTravel({
2758
+ stages,
2759
+ focusIndex,
2760
+ onFocusChange,
2761
+ isLive
2762
+ }) {
2763
+ const t = useLensTheme();
2764
+ const max = Math.max(0, stages.length - 1);
2765
+ function step(delta) {
2766
+ const next = Math.min(max, Math.max(0, focusIndex + delta));
2767
+ onFocusChange(next);
2768
+ }
2769
+ return /* @__PURE__ */ jsxs4(
2770
+ "div",
2771
+ {
2772
+ "data-fp-lens": "time-travel",
2773
+ style: {
2774
+ // Frosted-glass oval pill, floating clear of the surrounding
2775
+ // surfaces. `color-mix` gives a translucent tint using the
2776
+ // current theme's elevated bg; `backdrop-filter` blurs
2777
+ // whatever's behind (the graph + ask card show through
2778
+ // softly). Margin creates air around the pill so it reads as
2779
+ // a distinct floating control, not a toolbar bar.
2780
+ display: "flex",
2781
+ alignItems: "center",
2782
+ gap: 10,
2783
+ padding: "8px 14px",
2784
+ margin: "10px 14px",
2785
+ background: `color-mix(in srgb, ${t.bgElev} 55%, transparent)`,
2786
+ backdropFilter: "blur(14px) saturate(140%)",
2787
+ WebkitBackdropFilter: "blur(14px) saturate(140%)",
2788
+ border: `1px solid color-mix(in srgb, ${t.border} 70%, transparent)`,
2789
+ borderRadius: 999,
2790
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.06)",
2791
+ fontFamily: t.fontSans
2792
+ },
2793
+ children: [
2794
+ /* @__PURE__ */ jsx4(
2795
+ "button",
2796
+ {
2797
+ onClick: () => step(-1),
2798
+ disabled: focusIndex <= 0 || stages.length === 0,
2799
+ style: btnStyle(t, false),
2800
+ title: "Previous step (\u2190)",
2801
+ children: "\u25C0"
2802
+ }
2803
+ ),
2804
+ /* @__PURE__ */ jsx4(
2805
+ "button",
2806
+ {
2807
+ onClick: () => step(1),
2808
+ disabled: focusIndex >= max || stages.length === 0,
2809
+ style: btnStyle(t, false),
2810
+ title: "Next step (\u2192)",
2811
+ children: "\u25B6"
2812
+ }
2813
+ ),
2814
+ /* @__PURE__ */ jsx4(
2815
+ "button",
2816
+ {
2817
+ onClick: () => onFocusChange(max),
2818
+ disabled: stages.length === 0 || isLive === true,
2819
+ style: btnStyle(t, isLive !== true && stages.length > 0),
2820
+ title: "Jump to latest step",
2821
+ children: "\u27F3 Live"
2822
+ }
2823
+ ),
2824
+ /* @__PURE__ */ jsx4(
2825
+ "input",
2826
+ {
2827
+ type: "range",
2828
+ min: 0,
2829
+ max,
2830
+ value: Math.min(focusIndex, max),
2831
+ onChange: (e) => onFocusChange(Number(e.target.value)),
2832
+ disabled: stages.length <= 1,
2833
+ style: { flex: 1, accentColor: t.accent, minWidth: 120 }
2834
+ }
2835
+ ),
2836
+ /* @__PURE__ */ jsx4(
2837
+ "div",
2838
+ {
2839
+ style: {
2840
+ fontSize: 11,
2841
+ color: t.textMuted,
2842
+ fontFamily: t.fontMono,
2843
+ whiteSpace: "nowrap",
2844
+ minWidth: 80,
2845
+ textAlign: "right"
2846
+ },
2847
+ children: stages.length === 0 ? "no steps yet" : `Step ${focusIndex + 1} / ${stages.length}`
2848
+ }
2849
+ )
2850
+ ]
2851
+ }
2852
+ );
2853
+ }
2854
+ function btnStyle(t, highlighted) {
2855
+ return {
2856
+ background: highlighted ? t.accent : `color-mix(in srgb, ${t.bg} 40%, transparent)`,
2857
+ color: highlighted ? "#fff" : t.textMuted,
2858
+ border: `1px solid color-mix(in srgb, ${t.border} 60%, transparent)`,
2859
+ borderRadius: 999,
2860
+ padding: "3px 12px",
2861
+ fontSize: 12,
2862
+ cursor: "pointer",
2863
+ width: "auto",
2864
+ fontWeight: 500,
2865
+ whiteSpace: "nowrap",
2866
+ transition: "background 140ms ease, border-color 140ms ease, color 140ms ease"
2867
+ };
2868
+ }
2869
+
2870
+ // src/react/panels/AskCard.tsx
2871
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2872
+ function AskCard({ timeline, focusIndex, stages }) {
2873
+ const t = useLensTheme();
2874
+ const currentStage = stages[focusIndex];
2875
+ const currentTurn = currentStage !== void 0 ? timeline.turns[currentStage.turnIndex] : timeline.turns[0];
2876
+ const currentIter = currentStage?.iterIndex !== void 0 ? currentTurn?.iterations.find((it) => it.index === currentStage.iterIndex) : void 0;
2877
+ const currentTool = currentStage?.toolName && currentIter ? currentIter.toolCalls.find((tc) => tc.name === currentStage.toolName) : void 0;
2878
+ return /* @__PURE__ */ jsxs5(
2879
+ "div",
2880
+ {
2881
+ "data-fp-lens": "ask-card",
2882
+ style: {
2883
+ display: "flex",
2884
+ flexDirection: "column",
2885
+ gap: 12,
2886
+ padding: 14,
2887
+ background: t.bg,
2888
+ fontFamily: t.fontSans,
2889
+ color: t.text,
2890
+ overflow: "auto"
2891
+ },
2892
+ children: [
2893
+ /* @__PURE__ */ jsxs5("section", { children: [
2894
+ /* @__PURE__ */ jsx5(Label3, { t, children: "Your question" }),
2895
+ /* @__PURE__ */ jsx5(
2896
+ "div",
2897
+ {
2898
+ style: {
2899
+ marginTop: 4,
2900
+ padding: "10px 12px",
2901
+ background: `color-mix(in srgb, ${t.accent} 12%, ${t.bgElev})`,
2902
+ border: `1px solid ${t.border}`,
2903
+ borderRadius: 6,
2904
+ fontSize: 13,
2905
+ lineHeight: 1.5
2906
+ },
2907
+ children: currentTurn?.userPrompt ?? "No question yet."
2908
+ }
2909
+ )
2910
+ ] }),
2911
+ currentStage && /* @__PURE__ */ jsxs5("section", { children: [
2912
+ /* @__PURE__ */ jsxs5(Label3, { t, children: [
2913
+ "Step ",
2914
+ focusIndex + 1,
2915
+ " / ",
2916
+ stages.length
2917
+ ] }),
2918
+ !(currentTool && currentStage.from === "tool") && /* @__PURE__ */ jsx5("div", { style: { marginTop: 4, fontSize: 13, color: t.text, lineHeight: 1.5 }, children: currentStage.label }),
2919
+ /* @__PURE__ */ jsxs5(
2920
+ "div",
2921
+ {
2922
+ style: {
2923
+ marginTop: 8,
2924
+ display: "flex",
2925
+ gap: 6,
2926
+ flexWrap: "wrap",
2927
+ fontSize: 10,
2928
+ color: t.textSubtle
2929
+ },
2930
+ children: [
2931
+ /* @__PURE__ */ jsx5(Pill, { t, children: primitivePill(currentStage) }),
2932
+ /* @__PURE__ */ jsxs5(Pill, { t, children: [
2933
+ friendlyNode(currentStage.from),
2934
+ " \u2192 ",
2935
+ friendlyNode(currentStage.to)
2936
+ ] }),
2937
+ currentStage.toolName && /* @__PURE__ */ jsx5(Pill, { t, children: currentStage.toolName })
2938
+ ]
2939
+ }
2940
+ )
2941
+ ] }),
2942
+ (() => {
2943
+ const iterInjections = currentIter?.contextInjections ?? [];
2944
+ if (iterInjections.length > 0) {
2945
+ return /* @__PURE__ */ jsxs5("section", { children: [
2946
+ /* @__PURE__ */ jsx5(Label3, { t, children: "Context injected" }),
2947
+ /* @__PURE__ */ jsx5(InjectionTags, { injections: iterInjections, t })
2948
+ ] });
2949
+ }
2950
+ const turnInjections = currentTurn?.contextInjections ?? [];
2951
+ if (turnInjections.length === 0) return null;
2952
+ return /* @__PURE__ */ jsxs5("section", { children: [
2953
+ /* @__PURE__ */ jsx5(Label3, { t, children: "Context engineered this turn" }),
2954
+ /* @__PURE__ */ jsx5(InjectionTags, { injections: turnInjections, t, dedupeBySource: true })
2955
+ ] });
2956
+ })(),
2957
+ currentStage?.from === "agent" && currentIter?.assistantContent && /* @__PURE__ */ jsxs5("section", { children: [
2958
+ /* @__PURE__ */ jsx5(Label3, { t, children: "Agent reasoning" }),
2959
+ /* @__PURE__ */ jsx5(
2960
+ "div",
2961
+ {
2962
+ style: {
2963
+ marginTop: 4,
2964
+ padding: "10px 12px",
2965
+ background: t.bgElev,
2966
+ border: `1px solid ${t.border}`,
2967
+ borderLeft: `3px solid ${t.accent}`,
2968
+ borderRadius: 6,
2969
+ fontSize: 12,
2970
+ lineHeight: 1.55,
2971
+ whiteSpace: "pre-wrap",
2972
+ maxHeight: 220,
2973
+ overflow: "auto"
2974
+ },
2975
+ children: currentIter.assistantContent
2976
+ }
2977
+ )
2978
+ ] }),
2979
+ currentTool && /* @__PURE__ */ jsxs5("section", { children: [
2980
+ /* @__PURE__ */ jsx5(Label3, { t, children: currentStage?.to === "tool" ? "Arguments" : "Tool returned" }),
2981
+ /* @__PURE__ */ jsx5(
2982
+ "pre",
2983
+ {
2984
+ style: {
2985
+ marginTop: 4,
2986
+ padding: "8px 10px",
2987
+ background: t.bgElev,
2988
+ border: `1px solid ${t.border}`,
2989
+ borderRadius: 6,
2990
+ fontSize: 11,
2991
+ fontFamily: t.fontMono,
2992
+ color: currentTool.error ? t.error : t.text,
2993
+ whiteSpace: "pre-wrap",
2994
+ maxHeight: 180,
2995
+ overflow: "auto",
2996
+ margin: 0
2997
+ },
2998
+ children: currentStage?.to === "tool" ? JSON.stringify(currentTool.arguments, null, 2) : currentTool.result
2999
+ }
3000
+ )
3001
+ ] }),
3002
+ timeline.turns.length > 1 && /* @__PURE__ */ jsxs5("section", { children: [
3003
+ /* @__PURE__ */ jsx5(Label3, { t, children: "Conversation" }),
3004
+ /* @__PURE__ */ jsxs5("div", { style: { marginTop: 4, fontSize: 12, color: t.textMuted, lineHeight: 1.5 }, children: [
3005
+ timeline.turns.length,
3006
+ " question",
3007
+ timeline.turns.length === 1 ? "" : "s",
3008
+ " so far \xB7",
3009
+ " ",
3010
+ timeline.tools.length,
3011
+ " tool call",
3012
+ timeline.tools.length === 1 ? "" : "s",
3013
+ " total"
3014
+ ] })
3015
+ ] })
3016
+ ]
3017
+ }
3018
+ );
3019
+ }
3020
+ function InjectionTags({
3021
+ injections,
3022
+ t,
3023
+ dedupeBySource = false
3024
+ }) {
3025
+ const visible = dedupeBySource ? collapseBySource(injections) : injections;
3026
+ return /* @__PURE__ */ jsx5(
3027
+ "div",
3028
+ {
3029
+ style: {
3030
+ marginTop: 6,
3031
+ display: "flex",
3032
+ gap: 6,
3033
+ flexWrap: "wrap"
3034
+ },
3035
+ children: visible.map((ci, idx) => /* @__PURE__ */ jsxs5(
3036
+ "div",
3037
+ {
3038
+ title: `${ci.source} \u2192 ${ci.slot}`,
3039
+ style: {
3040
+ display: "inline-flex",
3041
+ alignItems: "center",
3042
+ gap: 6,
3043
+ padding: "4px 8px",
3044
+ fontSize: 11,
3045
+ fontWeight: 500,
3046
+ background: `color-mix(in srgb, ${t.accent} 8%, ${t.bgElev})`,
3047
+ border: `1px solid ${t.border}`,
3048
+ borderRadius: 6,
3049
+ color: t.text,
3050
+ lineHeight: 1.4
3051
+ },
3052
+ children: [
3053
+ /* @__PURE__ */ jsx5(
3054
+ "span",
3055
+ {
3056
+ style: {
3057
+ color: t.textMuted,
3058
+ fontSize: 10,
3059
+ textTransform: "uppercase",
3060
+ letterSpacing: "0.04em"
3061
+ },
3062
+ children: ci.source
3063
+ }
3064
+ ),
3065
+ /* @__PURE__ */ jsx5("span", { style: { color: t.textSubtle }, children: "\u2192" }),
3066
+ /* @__PURE__ */ jsx5("span", { style: { color: t.textMuted, fontSize: 10 }, children: slotShort2(ci.slot) }),
3067
+ /* @__PURE__ */ jsx5("span", { style: { color: t.text }, children: ci.label })
3068
+ ]
3069
+ },
3070
+ `${ci.source}-${idx}`
3071
+ ))
3072
+ }
3073
+ );
3074
+ }
3075
+ function collapseBySource(injections) {
3076
+ const counts = /* @__PURE__ */ new Map();
3077
+ const firstBySource = /* @__PURE__ */ new Map();
3078
+ for (const ci of injections) {
3079
+ const key = `${ci.source}|${ci.slot}`;
3080
+ counts.set(key, (counts.get(key) ?? 0) + 1);
3081
+ if (!firstBySource.has(key)) firstBySource.set(key, ci);
3082
+ }
3083
+ return [...firstBySource.entries()].map(([key, first]) => {
3084
+ const n = counts.get(key) ?? 1;
3085
+ if (n <= 1) return first;
3086
+ return { ...first, label: `${first.label} \xB7 \xD7${n}` };
3087
+ });
3088
+ }
3089
+ function Label3({
3090
+ t,
3091
+ children
3092
+ }) {
3093
+ return /* @__PURE__ */ jsx5(
3094
+ "div",
3095
+ {
3096
+ style: {
3097
+ fontSize: 10,
3098
+ color: t.textSubtle,
3099
+ textTransform: "uppercase",
3100
+ letterSpacing: "0.08em",
3101
+ fontWeight: 600
3102
+ },
3103
+ children
3104
+ }
3105
+ );
3106
+ }
3107
+ function Pill({
3108
+ t,
3109
+ children,
3110
+ warn
3111
+ }) {
3112
+ return /* @__PURE__ */ jsx5(
3113
+ "span",
3114
+ {
3115
+ style: {
3116
+ padding: "1px 6px",
3117
+ borderRadius: 3,
3118
+ background: warn ? `color-mix(in srgb, ${t.warning} 20%, transparent)` : t.bgElev,
3119
+ color: warn ? t.warning : t.textMuted,
3120
+ fontWeight: 600,
3121
+ textTransform: "uppercase",
3122
+ letterSpacing: "0.04em"
3123
+ },
3124
+ children
3125
+ }
3126
+ );
3127
+ }
3128
+ function friendlyNode(id) {
3129
+ if (!id) return id;
3130
+ return id.charAt(0).toUpperCase() + id.slice(1);
3131
+ }
3132
+ function primitivePill(stage) {
3133
+ if (stage.primitive === "system-prompt") return "System Prompt";
3134
+ if (stage.primitive === "message") return "Message";
3135
+ if (stage.toolKind === "skill") return "Tool (Skill)";
3136
+ if (stage.toolKind === "ask-human") return "Tool (Ask user)";
3137
+ return "Tool";
3138
+ }
3139
+ function slotShort2(slot) {
3140
+ if (slot === "system-prompt") return "sys prompt";
3141
+ if (slot === "tools") return "tools";
3142
+ return "messages";
3143
+ }
3144
+
3145
+ // src/react/panels/RunSummary.tsx
3146
+ import { useState as useState3 } from "react";
3147
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3148
+ function RunSummary({ timeline }) {
3149
+ const t = useLensTheme();
3150
+ const [open, setOpen] = useState3(false);
3151
+ const completedTurns = timeline.turns.filter((turn) => turn.finalContent !== "");
3152
+ if (completedTurns.length === 0) return null;
3153
+ const toolCounts = /* @__PURE__ */ new Map();
3154
+ for (const tc of timeline.tools) {
3155
+ const prev = toolCounts.get(tc.name) ?? { count: 0, totalMs: 0 };
3156
+ toolCounts.set(tc.name, {
3157
+ count: prev.count + 1,
3158
+ totalMs: prev.totalMs + (tc.durationMs ?? 0)
3159
+ });
3160
+ }
3161
+ const toolList = [...toolCounts.entries()].sort((a, b) => b[1].count - a[1].count);
3162
+ const activatedSkills = /* @__PURE__ */ new Set();
3163
+ for (const tc of timeline.tools) {
3164
+ if (tc.name === "read_skill") {
3165
+ const id = tc.arguments?.id;
3166
+ if (typeof id === "string") activatedSkills.add(id);
3167
+ }
3168
+ }
3169
+ const totalIn = timeline.turns.reduce((s, turn) => s + turn.totalInputTokens, 0);
3170
+ const totalOut = timeline.turns.reduce((s, turn) => s + turn.totalOutputTokens, 0);
3171
+ const totalMs = timeline.turns.reduce((s, turn) => s + turn.totalDurationMs, 0);
3172
+ const totalIters = timeline.turns.reduce((s, turn) => s + turn.iterations.length, 0);
3173
+ return /* @__PURE__ */ jsxs6(
3174
+ "div",
3175
+ {
3176
+ "data-fp-lens": "run-summary",
3177
+ style: {
3178
+ borderTop: `1px solid ${t.border}`,
3179
+ background: t.bgElev,
3180
+ fontFamily: t.fontSans
3181
+ },
3182
+ children: [
3183
+ /* @__PURE__ */ jsxs6(
3184
+ "button",
3185
+ {
3186
+ onClick: () => setOpen((v) => !v),
3187
+ style: {
3188
+ display: "flex",
3189
+ alignItems: "center",
3190
+ gap: 8,
3191
+ width: "100%",
3192
+ padding: "8px 14px",
3193
+ background: "transparent",
3194
+ border: "none",
3195
+ color: t.textMuted,
3196
+ fontFamily: "inherit",
3197
+ fontSize: 12,
3198
+ cursor: "pointer",
3199
+ textAlign: "left",
3200
+ fontWeight: 400
3201
+ },
3202
+ children: [
3203
+ /* @__PURE__ */ jsx6("span", { style: { fontSize: 10 }, children: open ? "\u25BE" : "\u25B8" }),
3204
+ /* @__PURE__ */ jsx6(
3205
+ "span",
3206
+ {
3207
+ style: {
3208
+ fontSize: 10,
3209
+ color: t.textSubtle,
3210
+ textTransform: "uppercase",
3211
+ letterSpacing: "0.08em",
3212
+ fontWeight: 600
3213
+ },
3214
+ children: "Run summary"
3215
+ }
3216
+ ),
3217
+ /* @__PURE__ */ jsx6("span", { style: { flex: 1 } }),
3218
+ /* @__PURE__ */ jsxs6("span", { style: { fontSize: 11, color: t.textMuted }, children: [
3219
+ timeline.tools.length,
3220
+ " tool call",
3221
+ timeline.tools.length === 1 ? "" : "s",
3222
+ " \xB7",
3223
+ " ",
3224
+ activatedSkills.size,
3225
+ " skill",
3226
+ activatedSkills.size === 1 ? "" : "s",
3227
+ " \xB7",
3228
+ " ",
3229
+ totalIn.toLocaleString(),
3230
+ "\u2192",
3231
+ totalOut.toLocaleString(),
3232
+ " tok \xB7",
3233
+ " ",
3234
+ (totalMs / 1e3).toFixed(1),
3235
+ "s"
3236
+ ] })
3237
+ ]
3238
+ }
3239
+ ),
3240
+ open && /* @__PURE__ */ jsxs6(
3241
+ "div",
3242
+ {
3243
+ style: {
3244
+ padding: "6px 14px 14px",
3245
+ display: "grid",
3246
+ gridTemplateColumns: "1fr 1fr",
3247
+ gap: 14,
3248
+ fontSize: 12,
3249
+ color: t.text
3250
+ },
3251
+ children: [
3252
+ /* @__PURE__ */ jsxs6("section", { children: [
3253
+ /* @__PURE__ */ jsxs6(Label4, { t, children: [
3254
+ "Tools used \xB7 ",
3255
+ timeline.tools.length
3256
+ ] }),
3257
+ toolList.length === 0 ? /* @__PURE__ */ jsx6("div", { style: { color: t.textSubtle, fontSize: 11, fontStyle: "italic" }, children: "None." }) : /* @__PURE__ */ jsx6("ul", { style: { margin: 0, padding: 0, listStyle: "none" }, children: toolList.map(([name, stats]) => /* @__PURE__ */ jsxs6(
3258
+ "li",
3259
+ {
3260
+ style: {
3261
+ display: "flex",
3262
+ gap: 8,
3263
+ padding: "3px 0",
3264
+ fontFamily: t.fontMono,
3265
+ fontSize: 11
3266
+ },
3267
+ children: [
3268
+ /* @__PURE__ */ jsx6("span", { style: { color: t.accent, flex: 1 }, children: name }),
3269
+ /* @__PURE__ */ jsxs6("span", { style: { color: t.textMuted }, children: [
3270
+ "\xD7",
3271
+ stats.count
3272
+ ] }),
3273
+ /* @__PURE__ */ jsx6("span", { style: { color: t.textSubtle, minWidth: 60, textAlign: "right" }, children: stats.totalMs > 0 ? `${Math.round(stats.totalMs)}ms` : "\u2014" })
3274
+ ]
3275
+ },
3276
+ name
3277
+ )) })
3278
+ ] }),
3279
+ /* @__PURE__ */ jsxs6("section", { children: [
3280
+ /* @__PURE__ */ jsxs6(Label4, { t, children: [
3281
+ "Skills activated \xB7 ",
3282
+ activatedSkills.size
3283
+ ] }),
3284
+ activatedSkills.size === 0 ? /* @__PURE__ */ jsx6("div", { style: { color: t.textSubtle, fontSize: 11, fontStyle: "italic" }, children: "None." }) : /* @__PURE__ */ jsx6("ul", { style: { margin: 0, padding: 0, listStyle: "none" }, children: [...activatedSkills].map((id) => /* @__PURE__ */ jsx6(
3285
+ "li",
3286
+ {
3287
+ style: {
3288
+ padding: "3px 0",
3289
+ fontFamily: t.fontMono,
3290
+ fontSize: 11,
3291
+ color: t.text
3292
+ },
3293
+ children: id
3294
+ },
3295
+ id
3296
+ )) }),
3297
+ /* @__PURE__ */ jsxs6("div", { style: { marginTop: 10 }, children: [
3298
+ /* @__PURE__ */ jsx6(Label4, { t, children: "Totals" }),
3299
+ /* @__PURE__ */ jsxs6(
3300
+ "div",
3301
+ {
3302
+ style: {
3303
+ display: "grid",
3304
+ gridTemplateColumns: "auto 1fr",
3305
+ gap: "3px 10px",
3306
+ marginTop: 4,
3307
+ fontSize: 11,
3308
+ color: t.textMuted
3309
+ },
3310
+ children: [
3311
+ /* @__PURE__ */ jsx6("span", { children: "Tokens" }),
3312
+ /* @__PURE__ */ jsxs6("span", { style: { color: t.text, fontFamily: t.fontMono }, children: [
3313
+ totalIn.toLocaleString(),
3314
+ " \u2192 ",
3315
+ totalOut.toLocaleString()
3316
+ ] }),
3317
+ /* @__PURE__ */ jsx6("span", { children: "Wall time" }),
3318
+ /* @__PURE__ */ jsxs6("span", { style: { color: t.text, fontFamily: t.fontMono }, children: [
3319
+ (totalMs / 1e3).toFixed(2),
3320
+ "s"
3321
+ ] }),
3322
+ /* @__PURE__ */ jsx6("span", { children: "LLM calls" }),
3323
+ /* @__PURE__ */ jsx6("span", { style: { color: t.text, fontFamily: t.fontMono }, children: totalIters }),
3324
+ /* @__PURE__ */ jsx6("span", { children: "Turns" }),
3325
+ /* @__PURE__ */ jsx6("span", { style: { color: t.text, fontFamily: t.fontMono }, children: completedTurns.length })
3326
+ ]
3327
+ }
3328
+ )
3329
+ ] })
3330
+ ] })
3331
+ ]
3332
+ }
3333
+ )
3334
+ ]
3335
+ }
3336
+ );
3337
+ }
3338
+ function Label4({
3339
+ t,
3340
+ children
3341
+ }) {
3342
+ return /* @__PURE__ */ jsx6(
3343
+ "div",
3344
+ {
3345
+ style: {
3346
+ fontSize: 10,
3347
+ color: t.textSubtle,
3348
+ textTransform: "uppercase",
3349
+ letterSpacing: "0.08em",
3350
+ fontWeight: 600,
3351
+ marginBottom: 4
3352
+ },
3353
+ children
3354
+ }
3355
+ );
3356
+ }
3357
+
3358
+ // src/react/AgentLens.tsx
3359
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3360
+ function AgentLens({
3361
+ runtimeSnapshot,
3362
+ timeline: providedTimeline,
3363
+ systemPrompt,
3364
+ onToolCallClick,
3365
+ skills,
3366
+ activeSkillId
3367
+ }) {
3368
+ const t = useLensTheme();
3369
+ const timeline = useMemo2(() => {
3370
+ if (providedTimeline) return providedTimeline;
3371
+ if (!runtimeSnapshot) return null;
3372
+ return fromAgentSnapshot(runtimeSnapshot);
3373
+ }, [providedTimeline, runtimeSnapshot]);
3374
+ const [selectedIterKey, setSelectedIterKey] = useState4(null);
3375
+ const [skillsOpen, setSkillsOpen] = useState4(false);
3376
+ const derivedActiveSkill = activeSkillId ?? (typeof timeline?.finalDecision?.currentSkill === "string" ? timeline.finalDecision.currentSkill : null);
3377
+ useEffect3(() => {
3378
+ if (!skillsOpen) return;
3379
+ const onKey = (e) => {
3380
+ if (e.key === "Escape") setSkillsOpen(false);
3381
+ };
3382
+ window.addEventListener("keydown", onKey);
3383
+ return () => window.removeEventListener("keydown", onKey);
3384
+ }, [skillsOpen]);
3385
+ const handleToolClick = useCallback(
3386
+ (inv) => {
3387
+ setSelectedIterKey(`${inv.turnIndex}.${inv.iterationIndex}`);
3388
+ onToolCallClick?.(inv);
3389
+ },
3390
+ [onToolCallClick]
3391
+ );
3392
+ if (!timeline) {
3393
+ return /* @__PURE__ */ jsxs7(
3394
+ "div",
3395
+ {
3396
+ "data-fp-lens": "empty",
3397
+ style: {
3398
+ padding: 32,
3399
+ color: t.textMuted,
3400
+ fontFamily: t.fontSans,
3401
+ textAlign: "center",
3402
+ background: t.bg
3403
+ },
3404
+ children: [
3405
+ "No agent run to show yet. Pass ",
3406
+ /* @__PURE__ */ jsx7("code", { children: "runtimeSnapshot" }),
3407
+ " after the agent runs."
3408
+ ]
3409
+ }
3410
+ );
3411
+ }
3412
+ const derivedSystemPrompt = systemPrompt ?? (typeof timeline.rawSnapshot?.sharedState?.systemPrompt === "string" ? timeline.rawSnapshot.sharedState.systemPrompt : void 0);
3413
+ const stages = useMemo2(() => timeline ? deriveStages(timeline) : [], [timeline]);
3414
+ const [focusIndex, setFocusIndex] = useState4(-1);
3415
+ const liveIndex = stages.length - 1;
3416
+ const resolvedFocus = focusIndex === -1 ? liveIndex : Math.min(focusIndex, liveIndex);
3417
+ const isLive = focusIndex === -1;
3418
+ const liveIndexRef = useRef3(liveIndex);
3419
+ liveIndexRef.current = liveIndex;
3420
+ const handleFocusChange = useCallback((i) => {
3421
+ setFocusIndex(i >= liveIndexRef.current ? -1 : i);
3422
+ }, []);
3423
+ useEffect3(() => {
3424
+ if (stages.length === 0) return;
3425
+ const onKey = (e) => {
3426
+ if (skillsOpen) return;
3427
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
3428
+ const tgt = e.target;
3429
+ const tag = tgt?.tagName;
3430
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || tgt?.isContentEditable) return;
3431
+ const delta = e.key === "ArrowLeft" ? -1 : 1;
3432
+ const next = Math.min(
3433
+ liveIndex,
3434
+ Math.max(0, resolvedFocus + delta)
3435
+ );
3436
+ handleFocusChange(next);
3437
+ e.preventDefault();
3438
+ };
3439
+ window.addEventListener("keydown", onKey);
3440
+ return () => window.removeEventListener("keydown", onKey);
3441
+ }, [resolvedFocus, liveIndex, stages.length, skillsOpen]);
3442
+ const handleEdgeClick = useCallback(
3443
+ (stage) => {
3444
+ if (stage.iterIndex === void 0) return;
3445
+ setSelectedIterKey(`${stage.turnIndex}.${stage.iterIndex}`);
3446
+ handleFocusChange(stage.index);
3447
+ },
3448
+ [handleFocusChange]
3449
+ );
3450
+ const shellRef = useRef3(null);
3451
+ const [isNarrow, setIsNarrow] = useState4(false);
3452
+ useEffect3(() => {
3453
+ const el = shellRef.current;
3454
+ if (!el) return;
3455
+ const ro = new ResizeObserver(([entry]) => {
3456
+ const w = entry.contentRect.width;
3457
+ setIsNarrow(w < 640);
3458
+ });
3459
+ ro.observe(el);
3460
+ return () => ro.disconnect();
3461
+ }, []);
3462
+ return /* @__PURE__ */ jsxs7(
3463
+ "div",
3464
+ {
3465
+ ref: shellRef,
3466
+ "data-fp-lens": "shell",
3467
+ "data-fp-lens-narrow": isNarrow ? "true" : "false",
3468
+ style: {
3469
+ display: "grid",
3470
+ // Top: TimeTravel slider (tt) + Skills button trailing on the
3471
+ // right. Middle-left: vertical StageFlow. Middle-right: AskCard
3472
+ // with current question + active step detail. Below: MessagesPanel
3473
+ // full width. Footer: RunSummary (only after first turn completes).
3474
+ //
3475
+ // Wide layout: two columns (graph | ask) with shared top/bottom rows.
3476
+ // Narrow layout: single column, ask card stacks below graph — same
3477
+ // responsive pattern `<ExplainableShell>` uses when its container
3478
+ // drops below ~640px. Container-width driven (ResizeObserver), so
3479
+ // this reacts to panel drags / splitters, not the viewport.
3480
+ //
3481
+ // Both columns use `minmax(0, ...)` so grid tracks can shrink below
3482
+ // their preferred size without clipping content.
3483
+ gridTemplateColumns: isNarrow ? "minmax(0, 1fr)" : "minmax(0, 1fr) minmax(0, 320px)",
3484
+ gridTemplateRows: isNarrow ? "auto auto auto minmax(0, 1fr) auto" : "auto auto minmax(0, 1fr) auto",
3485
+ gridTemplateAreas: isNarrow ? '"tt" "graph" "ask" "messages" "summary"' : '"tt tt" "graph ask" "messages messages" "summary summary"',
3486
+ // Self-constraining sizing contract — Lens never requires the
3487
+ // host to get their flex chain exactly right.
3488
+ //
3489
+ // • `maxHeight: 100dvh` is the hard cap. Even if NOTHING
3490
+ // upstream delivers a bounded height, the shell can never
3491
+ // exceed the viewport. This is what makes the grid's
3492
+ // `minmax(0, 1fr)` messages row actually resolve to a
3493
+ // bounded number of pixels instead of expanding to content.
3494
+ // • `height: 100%` + `flex: 1 1 0%` let well-constrained
3495
+ // hosts still shrink Lens below the viewport cap when they
3496
+ // want a narrower sidebar.
3497
+ // • `overflow: hidden` clips children that would otherwise
3498
+ // blow past the grid and make scrollHeight == clientHeight
3499
+ // on MessagesPanel (the exact bug this block prevents).
3500
+ // • `minHeight: 0` unblocks grid + flex size resolution.
3501
+ flex: "1 1 0%",
3502
+ minHeight: 0,
3503
+ height: "100%",
3504
+ maxHeight: "100dvh",
3505
+ overflow: "hidden",
3506
+ background: t.bg,
3507
+ color: t.text,
3508
+ fontFamily: t.fontSans
3509
+ },
3510
+ children: [
3511
+ /* @__PURE__ */ jsx7("style", { children: `
3512
+ [data-iter-selected="true"] {
3513
+ outline-color: currentColor !important;
3514
+ background: color-mix(in srgb, currentColor 8%, transparent);
3515
+ }
3516
+ ` }),
3517
+ stages.length > 0 && /* @__PURE__ */ jsxs7(Fragment4, { children: [
3518
+ /* @__PURE__ */ jsxs7(
3519
+ "div",
3520
+ {
3521
+ style: {
3522
+ gridArea: "tt",
3523
+ display: "flex",
3524
+ alignItems: "center"
3525
+ },
3526
+ children: [
3527
+ /* @__PURE__ */ jsx7("div", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx7(
3528
+ TimeTravel,
3529
+ {
3530
+ stages,
3531
+ focusIndex: resolvedFocus,
3532
+ onFocusChange: handleFocusChange,
3533
+ isLive
3534
+ }
3535
+ ) }),
3536
+ skills && skills.length > 0 && /* @__PURE__ */ jsxs7(
3537
+ "button",
3538
+ {
3539
+ onClick: () => setSkillsOpen(true),
3540
+ title: "See all skills registered with the agent",
3541
+ style: {
3542
+ // Borderless — rides on the same airy feel as the
3543
+ // floating time-travel pill. A faint translucent fill
3544
+ // on hover keeps it discoverable.
3545
+ background: "transparent",
3546
+ border: "none",
3547
+ color: t.textMuted,
3548
+ padding: "0 16px",
3549
+ margin: "0 14px 0 0",
3550
+ fontSize: 12,
3551
+ cursor: "pointer",
3552
+ whiteSpace: "nowrap",
3553
+ display: "flex",
3554
+ alignItems: "center",
3555
+ gap: 6,
3556
+ fontWeight: 400,
3557
+ width: "auto"
3558
+ },
3559
+ children: [
3560
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u{1F4DA}" }),
3561
+ /* @__PURE__ */ jsxs7("span", { children: [
3562
+ "Skills \xB7 ",
3563
+ skills.length
3564
+ ] }),
3565
+ derivedActiveSkill && /* @__PURE__ */ jsx7(
3566
+ "span",
3567
+ {
3568
+ style: {
3569
+ fontSize: 9,
3570
+ padding: "1px 5px",
3571
+ borderRadius: 3,
3572
+ background: `color-mix(in srgb, ${t.success} 25%, transparent)`,
3573
+ color: t.success,
3574
+ fontWeight: 600,
3575
+ textTransform: "uppercase"
3576
+ },
3577
+ children: derivedActiveSkill
3578
+ }
3579
+ )
3580
+ ]
3581
+ }
3582
+ )
3583
+ ]
3584
+ }
3585
+ ),
3586
+ /* @__PURE__ */ jsx7(
3587
+ "div",
3588
+ {
3589
+ style: {
3590
+ gridArea: "graph",
3591
+ minHeight: 0,
3592
+ overflow: "hidden",
3593
+ borderRight: `1px solid ${t.border}`
3594
+ },
3595
+ children: /* @__PURE__ */ jsx7(
3596
+ StageFlow,
3597
+ {
3598
+ stages,
3599
+ focusIndex: resolvedFocus,
3600
+ onEdgeClick: handleEdgeClick,
3601
+ activeSkillId: derivedActiveSkill,
3602
+ timeline
3603
+ }
3604
+ )
3605
+ }
3606
+ ),
3607
+ /* @__PURE__ */ jsx7(
3608
+ "div",
3609
+ {
3610
+ style: {
3611
+ gridArea: "ask",
3612
+ minHeight: 0,
3613
+ overflow: "hidden",
3614
+ background: t.bg
3615
+ },
3616
+ children: /* @__PURE__ */ jsx7(
3617
+ AskCard,
3618
+ {
3619
+ timeline,
3620
+ stages,
3621
+ focusIndex: resolvedFocus
3622
+ }
3623
+ )
3624
+ }
3625
+ )
3626
+ ] }),
3627
+ /* @__PURE__ */ jsx7(
3628
+ "div",
3629
+ {
3630
+ style: {
3631
+ gridArea: "messages",
3632
+ minHeight: 0,
3633
+ overflow: "hidden",
3634
+ // `position: relative` is the containing block for the
3635
+ // absolutely-positioned MessagesPanel inside. This is the
3636
+ // mechanism that forces the panel's height to match this
3637
+ // cell exactly, no matter what the upstream flex/grid
3638
+ // chain does.
3639
+ position: "relative"
3640
+ },
3641
+ children: /* @__PURE__ */ jsx7(
3642
+ MessagesPanel,
3643
+ {
3644
+ timeline,
3645
+ onToolCallClick: handleToolClick,
3646
+ selectedIterKey,
3647
+ stages,
3648
+ focusIndex: resolvedFocus,
3649
+ onFocusChange: handleFocusChange,
3650
+ isLive,
3651
+ ...derivedSystemPrompt && { systemPrompt: derivedSystemPrompt }
3652
+ }
3653
+ )
3654
+ }
3655
+ ),
3656
+ /* @__PURE__ */ jsx7("div", { style: { gridArea: "summary" }, children: /* @__PURE__ */ jsx7(RunSummary, { timeline }) }),
3657
+ skillsOpen && skills && /* @__PURE__ */ jsx7(
3658
+ SkillsPanel,
3659
+ {
3660
+ skills,
3661
+ activeSkillId: derivedActiveSkill,
3662
+ onClose: () => setSkillsOpen(false)
3663
+ }
3664
+ )
3665
+ ]
3666
+ }
3667
+ );
3668
+ }
3669
+
3670
+ // src/react/components/Tabs/Tabs.tsx
3671
+ import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
3672
+
3673
+ // src/react/layout/FillParent.tsx
3674
+ import { jsx as jsx8 } from "react/jsx-runtime";
3675
+ function FillParent({
3676
+ children,
3677
+ dataAttr,
3678
+ outerStyle,
3679
+ innerStyle
3680
+ }) {
3681
+ return /* @__PURE__ */ jsx8(
3682
+ "div",
3683
+ {
3684
+ "data-fp-lens": dataAttr,
3685
+ style: {
3686
+ // Outer: flex child + block with definite height via 100%.
3687
+ // `position: relative` establishes the containing block for
3688
+ // the absolutely-positioned inner.
3689
+ position: "relative",
3690
+ flex: "1 1 0%",
3691
+ minHeight: 0,
3692
+ height: "100%",
3693
+ display: "flex",
3694
+ flexDirection: "column",
3695
+ ...outerStyle
3696
+ },
3697
+ children: /* @__PURE__ */ jsx8(
3698
+ "div",
3699
+ {
3700
+ style: {
3701
+ // Inner: pinned to all four edges of outer. Once outer has
3702
+ // a size (any mechanism), inner fills it exactly.
3703
+ position: "absolute",
3704
+ inset: 0,
3705
+ display: "flex",
3706
+ flexDirection: "column",
3707
+ minHeight: 0,
3708
+ ...innerStyle
3709
+ },
3710
+ children
3711
+ }
3712
+ )
3713
+ }
3714
+ );
3715
+ }
3716
+
3717
+ // src/react/components/Tabs/Tabs.tsx
3718
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3719
+ var DEFAULT_SURFACE = "#0f172a";
3720
+ var DEFAULT_TEXT = "#f8fafc";
3721
+ var DEFAULT_MUTED_TEXT = "#94a3b8";
3722
+ var DEFAULT_BORDER = "#334155";
3723
+ var DEFAULT_FONT_FAMILY = "Inter, system-ui, -apple-system, sans-serif";
3724
+ function Tabs({
3725
+ tabs,
3726
+ selectedTabId,
3727
+ defaultTabId,
3728
+ onChange,
3729
+ renderAll = false,
3730
+ leadingSlot,
3731
+ trailingSlot,
3732
+ className,
3733
+ surface = DEFAULT_SURFACE,
3734
+ textColor = DEFAULT_TEXT,
3735
+ mutedTextColor = DEFAULT_MUTED_TEXT,
3736
+ borderColor = DEFAULT_BORDER,
3737
+ fontFamily = DEFAULT_FONT_FAMILY
3738
+ }) {
3739
+ const palette = {
3740
+ surface,
3741
+ textColor,
3742
+ mutedTextColor,
3743
+ borderColor,
3744
+ fontFamily
3745
+ };
3746
+ const initialId = defaultTabId ?? tabs[0]?.id;
3747
+ const [internalId, setInternalId] = useState5(initialId);
3748
+ const isControlled = selectedTabId !== void 0;
3749
+ const selectedId = isControlled ? selectedTabId : internalId;
3750
+ const pickTab = useCallback2(
3751
+ (id) => {
3752
+ const def = tabs.find((t) => t.id === id);
3753
+ if (!def || def.disabled) return;
3754
+ if (!isControlled) setInternalId(id);
3755
+ onChange?.(id);
3756
+ },
3757
+ [tabs, isControlled, onChange]
3758
+ );
3759
+ const handleKey = useCallback2(
3760
+ (e) => {
3761
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
3762
+ e.preventDefault();
3763
+ const enabled = tabs.filter((t) => !t.disabled);
3764
+ if (enabled.length === 0) return;
3765
+ const currentIdx = enabled.findIndex((t) => t.id === selectedId);
3766
+ let nextIdx = currentIdx;
3767
+ if (e.key === "ArrowLeft") {
3768
+ nextIdx = (currentIdx - 1 + enabled.length) % enabled.length;
3769
+ } else if (e.key === "ArrowRight") {
3770
+ nextIdx = (currentIdx + 1) % enabled.length;
3771
+ } else if (e.key === "Home") {
3772
+ nextIdx = 0;
3773
+ } else if (e.key === "End") {
3774
+ nextIdx = enabled.length - 1;
3775
+ }
3776
+ pickTab(enabled[nextIdx].id);
3777
+ },
3778
+ [tabs, selectedId, pickTab]
3779
+ );
3780
+ const selectedDef = tabs.find((t) => t.id === selectedId);
3781
+ return /* @__PURE__ */ jsxs8(
3782
+ "div",
3783
+ {
3784
+ "data-fp-lens": "tabs-root",
3785
+ className,
3786
+ style: {
3787
+ // Fill the parent on every layout model we might be dropped
3788
+ // into. `flex: 1` + `minHeight: 0` wins inside flex columns;
3789
+ // `height: 100%` wins inside block parents with a definite
3790
+ // height. Applying both makes <Tabs> self-sufficient so
3791
+ // consumers don't have to wrap it in another sizer div.
3792
+ display: "flex",
3793
+ flexDirection: "column",
3794
+ flex: "1 1 0%",
3795
+ height: "100%",
3796
+ minHeight: 0
3797
+ },
3798
+ children: [
3799
+ /* @__PURE__ */ jsx9(
3800
+ TabStrip,
3801
+ {
3802
+ tabs,
3803
+ selectedId,
3804
+ onPick: pickTab,
3805
+ onKeyDown: handleKey,
3806
+ leadingSlot,
3807
+ trailingSlot,
3808
+ palette
3809
+ }
3810
+ ),
3811
+ /* @__PURE__ */ jsx9(
3812
+ TabContent,
3813
+ {
3814
+ tabs,
3815
+ selectedId,
3816
+ renderAll,
3817
+ palette
3818
+ }
3819
+ ),
3820
+ !selectedDef && renderAll === false && /* @__PURE__ */ jsx9(
3821
+ "div",
3822
+ {
3823
+ "data-fp-lens": "tabs-empty",
3824
+ style: { flex: 1, minHeight: 0 },
3825
+ "aria-live": "polite"
3826
+ }
3827
+ )
3828
+ ]
3829
+ }
3830
+ );
3831
+ }
3832
+ function TabStrip({
3833
+ tabs,
3834
+ selectedId,
3835
+ onPick,
3836
+ onKeyDown,
3837
+ leadingSlot,
3838
+ trailingSlot,
3839
+ palette
3840
+ }) {
3841
+ return /* @__PURE__ */ jsxs8(
3842
+ "div",
3843
+ {
3844
+ role: "tablist",
3845
+ onKeyDown,
3846
+ "data-fp-lens": "tabs-strip",
3847
+ style: {
3848
+ display: "flex",
3849
+ padding: "8px 14px 0",
3850
+ // Transparent so the strip inherits from the host's outer
3851
+ // color (page/sidebar). Active tab renders an explicit
3852
+ // `palette.surface` fill so it merges with the content
3853
+ // panel; inactive tabs show the host color through.
3854
+ background: "transparent",
3855
+ flexShrink: 0,
3856
+ alignItems: "flex-end",
3857
+ position: "relative",
3858
+ zIndex: 1,
3859
+ fontFamily: palette.fontFamily
3860
+ },
3861
+ children: [
3862
+ leadingSlot && /* @__PURE__ */ jsx9(
3863
+ "div",
3864
+ {
3865
+ style: {
3866
+ display: "flex",
3867
+ alignItems: "center",
3868
+ marginRight: 12,
3869
+ paddingBottom: 10
3870
+ },
3871
+ "data-fp-lens": "tabs-leading",
3872
+ children: leadingSlot
3873
+ }
3874
+ ),
3875
+ /* @__PURE__ */ jsx9(
3876
+ "div",
3877
+ {
3878
+ style: {
3879
+ display: "flex",
3880
+ gap: 2,
3881
+ alignItems: "flex-end",
3882
+ flex: 1,
3883
+ minWidth: 0
3884
+ },
3885
+ children: tabs.map((def) => /* @__PURE__ */ jsx9(
3886
+ TabButton,
3887
+ {
3888
+ def,
3889
+ selected: def.id === selectedId,
3890
+ onPick,
3891
+ palette
3892
+ },
3893
+ def.id
3894
+ ))
3895
+ }
3896
+ ),
3897
+ trailingSlot && /* @__PURE__ */ jsx9("div", { style: { display: "flex", alignItems: "center" }, children: trailingSlot })
3898
+ ]
3899
+ }
3900
+ );
3901
+ }
3902
+ function TabButton({
3903
+ def,
3904
+ selected,
3905
+ onPick,
3906
+ palette
3907
+ }) {
3908
+ const buttonRef = useRef4(null);
3909
+ useEffect4(() => {
3910
+ if (selected && document.activeElement?.getAttribute("role") === "tab") {
3911
+ buttonRef.current?.focus();
3912
+ }
3913
+ }, [selected]);
3914
+ return /* @__PURE__ */ jsxs8(
3915
+ "button",
3916
+ {
3917
+ ref: buttonRef,
3918
+ role: "tab",
3919
+ "aria-selected": selected,
3920
+ "aria-disabled": def.disabled,
3921
+ tabIndex: selected ? 0 : -1,
3922
+ disabled: def.disabled,
3923
+ title: def.title,
3924
+ onClick: () => onPick(def.id),
3925
+ "data-fp-lens-tab": def.id,
3926
+ "data-fp-lens-selected": selected,
3927
+ style: {
3928
+ padding: "9px 16px 10px",
3929
+ cursor: def.disabled ? "not-allowed" : "pointer",
3930
+ fontSize: 13,
3931
+ fontFamily: "inherit",
3932
+ width: "auto",
3933
+ display: "flex",
3934
+ alignItems: "center",
3935
+ gap: 6,
3936
+ color: selected ? palette.textColor : palette.mutedTextColor,
3937
+ // SAME color used on the content panel below. Devtools sanity
3938
+ // check: this string equals <TabContent>'s `background`. The
3939
+ // active tab has no border — its fill IS the surface, so any
3940
+ // outline would break the "tab and body are one continuous
3941
+ // shape" illusion the user asked for.
3942
+ background: selected ? palette.surface : "transparent",
3943
+ border: "none",
3944
+ borderRadius: "10px 10px 0 0",
3945
+ fontWeight: selected ? 600 : 500,
3946
+ opacity: def.disabled ? 0.45 : 1,
3947
+ transition: "background 140ms ease, color 140ms ease, border-color 140ms ease"
3948
+ },
3949
+ children: [
3950
+ /* @__PURE__ */ jsx9("span", { children: def.label }),
3951
+ def.trailing
3952
+ ]
3953
+ }
3954
+ );
3955
+ }
3956
+ function TabContent({
3957
+ tabs,
3958
+ selectedId,
3959
+ renderAll,
3960
+ palette
3961
+ }) {
3962
+ const bg = palette.surface;
3963
+ if (renderAll) {
3964
+ return /* @__PURE__ */ jsx9(
3965
+ FillParent,
3966
+ {
3967
+ dataAttr: "tabs-content",
3968
+ outerStyle: { background: bg },
3969
+ children: tabs.map((def) => /* @__PURE__ */ jsx9(
3970
+ "div",
3971
+ {
3972
+ role: "tabpanel",
3973
+ "aria-hidden": def.id !== selectedId,
3974
+ style: {
3975
+ position: "absolute",
3976
+ inset: 0,
3977
+ visibility: def.id === selectedId ? "visible" : "hidden",
3978
+ pointerEvents: def.id === selectedId ? "auto" : "none",
3979
+ display: "flex",
3980
+ flexDirection: "column",
3981
+ minHeight: 0
3982
+ },
3983
+ children: def.content
3984
+ },
3985
+ def.id
3986
+ ))
3987
+ }
3988
+ );
3989
+ }
3990
+ const selectedDef = tabs.find((t) => t.id === selectedId);
3991
+ if (!selectedDef) return null;
3992
+ return /* @__PURE__ */ jsx9(
3993
+ FillParent,
3994
+ {
3995
+ dataAttr: "tabs-content",
3996
+ outerStyle: { background: bg },
3997
+ innerStyle: { background: bg },
3998
+ children: /* @__PURE__ */ jsx9(
3999
+ "div",
4000
+ {
4001
+ role: "tabpanel",
4002
+ style: {
4003
+ position: "absolute",
4004
+ inset: 0,
4005
+ display: "flex",
4006
+ flexDirection: "column",
4007
+ minHeight: 0
4008
+ },
4009
+ children: selectedDef.content
4010
+ }
4011
+ )
4012
+ }
4013
+ );
4014
+ }
4015
+
4016
+ // src/react/layout/SelfSizingRoot.tsx
4017
+ import { jsx as jsx10 } from "react/jsx-runtime";
4018
+ function SelfSizingRoot({
4019
+ children,
4020
+ className,
4021
+ dataAttr = "self-sizing-root",
4022
+ minHeight = "min(400px, 100dvh)",
4023
+ maxHeight = "100dvh",
4024
+ style
4025
+ }) {
4026
+ return /* @__PURE__ */ jsx10(
4027
+ "div",
4028
+ {
4029
+ className,
4030
+ "data-fp-lens": dataAttr,
4031
+ style: {
4032
+ // Layout — all five declarations work together. The parent's
4033
+ // layout model decides which ones "win":
4034
+ // • inside a flex column parent → `flex: 1` dominates
4035
+ // • inside a grid cell → `height: 100%` dominates
4036
+ // • inside a block with height → `height: 100%` dominates
4037
+ // • inside an unsized parent → `min-height` (floor) wins
4038
+ // The `max-height: 100dvh` caps all of the above so Lens
4039
+ // never exceeds the viewport.
4040
+ display: "flex",
4041
+ flexDirection: "column",
4042
+ flex: "1 1 0%",
4043
+ height: "100%",
4044
+ minHeight,
4045
+ maxHeight,
4046
+ // CSS containment — Lens is a self-contained layout island.
4047
+ // `layout`: inner layout doesn't influence outer layout.
4048
+ // `size`: inner element sizes doesn't affect outer element
4049
+ // size (requires an explicit size — provided above).
4050
+ // `paint`: nothing inside paints outside the box. Clips
4051
+ // descendant overflow too, so we don't need
4052
+ // `overflow: hidden` separately, but we keep it
4053
+ // for older browsers that don't honour paint
4054
+ // containment.
4055
+ contain: "layout size paint",
4056
+ overflow: "hidden",
4057
+ // Custom caller style merged last, but the layout keys above
4058
+ // are intentionally NOT overridable — if a consumer wants to
4059
+ // change sizing, they use the explicit props. This is how we
4060
+ // guarantee "library-quality" self-containment: the layout
4061
+ // contract is stable no matter what style prop comes in.
4062
+ ...style
4063
+ },
4064
+ children
4065
+ }
4066
+ );
4067
+ }
4068
+
4069
+ // src/react/hooks/useLiveTimeline.ts
4070
+ import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef5, useState as useState6 } from "react";
4071
+ function useLiveTimeline() {
4072
+ const builderRef = useRef5(null);
4073
+ if (!builderRef.current) builderRef.current = new LiveTimelineBuilder();
4074
+ const builder = builderRef.current;
4075
+ const [timeline, setTimeline] = useState6(() => builder.getTimeline());
4076
+ const sync = useCallback3(() => {
4077
+ setTimeline(builder.getTimeline());
4078
+ }, [builder]);
4079
+ const ingest = useCallback3(
4080
+ (event) => {
4081
+ builder.ingest(event);
4082
+ sync();
4083
+ },
4084
+ [builder, sync]
4085
+ );
4086
+ const startTurn = useCallback3(
4087
+ (userPrompt) => {
4088
+ builder.startTurn(userPrompt);
4089
+ sync();
4090
+ },
4091
+ [builder, sync]
4092
+ );
4093
+ const setSystemPrompt = useCallback3(
4094
+ (prompt) => {
4095
+ builder.setSystemPrompt(prompt);
4096
+ sync();
4097
+ },
4098
+ [builder, sync]
4099
+ );
4100
+ const setFinalDecision = useCallback3(
4101
+ (decision) => {
4102
+ builder.setFinalDecision(decision);
4103
+ sync();
4104
+ },
4105
+ [builder, sync]
4106
+ );
4107
+ const reset = useCallback3(() => {
4108
+ builder.reset();
4109
+ sync();
4110
+ }, [builder, sync]);
4111
+ return useMemo3(
4112
+ () => ({ timeline, ingest, startTurn, setSystemPrompt, setFinalDecision, reset, builder }),
4113
+ [timeline, ingest, startTurn, setSystemPrompt, setFinalDecision, reset, builder]
4114
+ );
4115
+ }
4116
+
4117
+ // src/react/Lens.tsx
4118
+ import { jsx as jsx11 } from "react/jsx-runtime";
4119
+ function Lens({
4120
+ runtimeSnapshot,
4121
+ timeline,
4122
+ appName,
4123
+ systemPrompt,
4124
+ skills,
4125
+ activeSkillId,
4126
+ onToolCallClick,
4127
+ defaultTabId = "lens",
4128
+ trailingSlot,
4129
+ renderAll = false,
4130
+ traceView,
4131
+ narrativeEntries,
4132
+ spec,
4133
+ theme,
4134
+ for: runnerProp
4135
+ }) {
4136
+ const themedTokens = useLensTheme();
4137
+ const t = theme ? resolve(theme) : themedTokens;
4138
+ const [selectedId, setSelectedId] = useState7(defaultTabId);
4139
+ const autoLens = useLiveTimeline();
4140
+ const [, bumpSnapshot] = useReducer((x) => x + 1, 0);
4141
+ useEffect5(() => {
4142
+ if (!runnerProp?.observe) return;
4143
+ autoLens.reset();
4144
+ const stop = runnerProp.observe((event) => {
4145
+ autoLens.ingest(event);
4146
+ const type = event && typeof event === "object" ? event.type : void 0;
4147
+ if (type === "turn_end" || type === "agentfootprint.agent.turn_complete") {
4148
+ bumpSnapshot();
4149
+ }
4150
+ });
4151
+ return stop;
4152
+ }, [runnerProp]);
4153
+ const usingRunner = !!runnerProp?.observe;
4154
+ const resolvedTimeline = usingRunner ? autoLens.timeline : timeline;
4155
+ const resolvedSnapshot = usingRunner ? runnerProp?.getSnapshot?.() ?? null : runtimeSnapshot ?? null;
4156
+ const resolvedNarrative = usingRunner ? runnerProp?.getNarrativeEntries?.() : narrativeEntries;
4157
+ const resolvedSpec = usingRunner ? runnerProp?.getSpec?.() : spec;
4158
+ const agentLensProps = {
4159
+ runtimeSnapshot: resolvedSnapshot,
4160
+ ...resolvedTimeline !== void 0 ? { timeline: resolvedTimeline } : {},
4161
+ ...systemPrompt !== void 0 ? { systemPrompt } : {},
4162
+ ...skills !== void 0 ? { skills } : {},
4163
+ ...activeSkillId !== void 0 ? { activeSkillId } : {},
4164
+ ...onToolCallClick !== void 0 ? { onToolCallClick } : {}
4165
+ };
4166
+ const brand = appName ? /* @__PURE__ */ jsx11(
4167
+ "span",
4168
+ {
4169
+ "data-fp-lens": "brand",
4170
+ style: {
4171
+ fontSize: 13,
4172
+ fontWeight: 600,
4173
+ color: t.text,
4174
+ fontFamily: t.fontSans,
4175
+ letterSpacing: "0.01em",
4176
+ whiteSpace: "nowrap"
4177
+ },
4178
+ title: `Lens \xB7 ${appName}`,
4179
+ children: appName
4180
+ }
4181
+ ) : null;
4182
+ const shell = /* @__PURE__ */ jsx11(
4183
+ Tabs,
4184
+ {
4185
+ tabs: [
4186
+ {
4187
+ id: "lens",
4188
+ label: "Lens",
4189
+ content: /* @__PURE__ */ jsx11(AgentLens, { ...agentLensProps })
4190
+ },
4191
+ {
4192
+ id: "trace",
4193
+ label: "Explainable Trace",
4194
+ // Consumer-supplied override wins; otherwise render a
4195
+ // default `<ExplainableShell>` from the same runtime
4196
+ // snapshot so consumers get something useful out of the
4197
+ // box without wiring anything.
4198
+ content: traceView ?? /* @__PURE__ */ jsx11(
4199
+ ExplainableShell,
4200
+ {
4201
+ runtimeSnapshot: resolvedSnapshot ?? null,
4202
+ ...resolvedNarrative !== void 0 ? { narrativeEntries: [...resolvedNarrative] } : {},
4203
+ ...resolvedSpec !== void 0 ? { spec: resolvedSpec } : {}
4204
+ }
4205
+ )
4206
+ }
4207
+ ],
4208
+ selectedTabId: selectedId,
4209
+ onChange: setSelectedId,
4210
+ renderAll,
4211
+ surface: t.bg,
4212
+ textColor: t.text,
4213
+ mutedTextColor: t.textMuted,
4214
+ borderColor: t.border,
4215
+ fontFamily: t.fontSans,
4216
+ ...brand !== null ? { leadingSlot: brand } : {},
4217
+ ...trailingSlot !== void 0 ? { trailingSlot } : {}
4218
+ }
4219
+ );
4220
+ const sized = /* @__PURE__ */ jsx11(SelfSizingRoot, { dataAttr: "shell", children: shell });
4221
+ return theme ? /* @__PURE__ */ jsx11(FootprintTheme, { tokens: theme, children: sized }) : sized;
4222
+ }
4223
+
4224
+ // src/react/layout/Stack.tsx
4225
+ import { jsx as jsx12 } from "react/jsx-runtime";
4226
+ function Stack({
4227
+ children,
4228
+ direction = "column",
4229
+ gap = 8,
4230
+ align = "stretch",
4231
+ justify = "flex-start",
4232
+ wrap = false,
4233
+ fill = false,
4234
+ padding,
4235
+ dataAttr,
4236
+ style,
4237
+ className
4238
+ }) {
4239
+ const mainAxisMin = direction === "column" ? "minHeight" : "minWidth";
4240
+ return /* @__PURE__ */ jsx12(
4241
+ "div",
4242
+ {
4243
+ "data-fp-lens": dataAttr,
4244
+ className,
4245
+ style: {
4246
+ display: "flex",
4247
+ flexDirection: direction,
4248
+ gap,
4249
+ alignItems: align,
4250
+ justifyContent: justify,
4251
+ flexWrap: wrap ? "wrap" : "nowrap",
4252
+ ...fill ? { flex: "1 1 0%", [mainAxisMin]: 0 } : {},
4253
+ ...padding !== void 0 ? { padding } : {},
4254
+ ...style
4255
+ },
4256
+ children
4257
+ }
4258
+ );
4259
+ }
4260
+
4261
+ // src/react/layout/Scroller.tsx
4262
+ import { jsx as jsx13 } from "react/jsx-runtime";
4263
+ var SCROLLBAR_STYLE_ID = "fp-lens-scroller-hide";
4264
+ function ensureHideScrollbarStyles() {
4265
+ if (typeof document === "undefined") return;
4266
+ if (document.getElementById(SCROLLBAR_STYLE_ID)) return;
4267
+ const el = document.createElement("style");
4268
+ el.id = SCROLLBAR_STYLE_ID;
4269
+ el.textContent = `
4270
+ [data-fp-lens-hide-scrollbar="true"] {
4271
+ scrollbar-width: none;
4272
+ -ms-overflow-style: none;
4273
+ }
4274
+ [data-fp-lens-hide-scrollbar="true"]::-webkit-scrollbar {
4275
+ display: none;
4276
+ }
4277
+ `;
4278
+ document.head.appendChild(el);
4279
+ }
4280
+ function Scroller({
4281
+ children,
4282
+ direction = "y",
4283
+ padding = 0,
4284
+ hideScrollbar = false,
4285
+ dataAttr,
4286
+ outerStyle,
4287
+ innerStyle,
4288
+ className
4289
+ }) {
4290
+ if (hideScrollbar) ensureHideScrollbarStyles();
4291
+ const overflowKey = direction === "y" ? "overflowY" : "overflowX";
4292
+ const crossOverflowKey = direction === "y" ? "overflowX" : "overflowY";
4293
+ return /* @__PURE__ */ jsx13(
4294
+ "div",
4295
+ {
4296
+ "data-fp-lens": dataAttr,
4297
+ className,
4298
+ style: {
4299
+ position: "relative",
4300
+ flex: "1 1 0%",
4301
+ minHeight: 0,
4302
+ minWidth: 0,
4303
+ height: "100%",
4304
+ display: "flex",
4305
+ flexDirection: "column",
4306
+ ...outerStyle
4307
+ },
4308
+ children: /* @__PURE__ */ jsx13(
4309
+ "div",
4310
+ {
4311
+ "data-fp-lens-hide-scrollbar": hideScrollbar ? "true" : void 0,
4312
+ style: {
4313
+ position: "absolute",
4314
+ inset: 0,
4315
+ [overflowKey]: "auto",
4316
+ [crossOverflowKey]: "hidden",
4317
+ padding,
4318
+ // Contain — internal scroll never triggers a scroll on any
4319
+ // ancestor, and painting stops at the clip box.
4320
+ contain: "content",
4321
+ display: "flex",
4322
+ flexDirection: direction === "y" ? "column" : "row",
4323
+ ...innerStyle
4324
+ },
4325
+ children
4326
+ }
4327
+ )
4328
+ }
4329
+ );
4330
+ }
4331
+
4332
+ // src/react/layout/Surface.tsx
4333
+ import { jsx as jsx14 } from "react/jsx-runtime";
4334
+ var DEFAULT_PALETTE = {
4335
+ surface: "#0f172a",
4336
+ borderColor: "#334155",
4337
+ textColor: "#f8fafc",
4338
+ elevatedSurface: "#1e293b",
4339
+ shadow: "0 2px 8px rgba(0, 0, 0, 0.18)"
4340
+ };
4341
+ function resolveVariantStyles(variant, palette, padding, radius) {
4342
+ if (variant === "none") {
4343
+ return {};
4344
+ }
4345
+ if (variant === "pill") {
4346
+ return {
4347
+ background: palette.surface,
4348
+ color: palette.textColor,
4349
+ border: `1px solid ${palette.borderColor}`,
4350
+ borderRadius: radius ?? 999,
4351
+ padding: padding ?? "2px 10px",
4352
+ fontSize: 11,
4353
+ fontWeight: 600,
4354
+ whiteSpace: "nowrap",
4355
+ display: "inline-flex",
4356
+ alignItems: "center"
4357
+ };
4358
+ }
4359
+ if (variant === "well") {
4360
+ return {
4361
+ background: palette.elevatedSurface ?? palette.surface,
4362
+ color: palette.textColor,
4363
+ border: `1px solid ${palette.borderColor}`,
4364
+ borderRadius: radius ?? 6,
4365
+ padding: padding ?? 12
4366
+ };
4367
+ }
4368
+ return {
4369
+ background: palette.surface,
4370
+ color: palette.textColor,
4371
+ border: `1px solid ${palette.borderColor}`,
4372
+ borderRadius: radius ?? 10,
4373
+ padding: padding ?? 14,
4374
+ boxShadow: palette.shadow
4375
+ };
4376
+ }
4377
+ function Surface({
4378
+ children,
4379
+ variant = "card",
4380
+ palette: paletteOverride,
4381
+ padding,
4382
+ radius,
4383
+ dataAttr,
4384
+ style,
4385
+ className
4386
+ }) {
4387
+ const palette = {
4388
+ ...DEFAULT_PALETTE,
4389
+ ...paletteOverride
4390
+ };
4391
+ const variantStyles = resolveVariantStyles(variant, palette, padding, radius);
4392
+ return /* @__PURE__ */ jsx14(
4393
+ "div",
4394
+ {
4395
+ "data-fp-lens": dataAttr ?? `surface-${variant}`,
4396
+ className,
4397
+ style: { ...variantStyles, ...style },
4398
+ children
4399
+ }
4400
+ );
4401
+ }
4402
+
4403
+ // src/react/layout/Table.tsx
4404
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState8 } from "react";
4405
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
4406
+ var DEFAULT_PALETTE2 = {
4407
+ surface: "#0f172a",
4408
+ elevatedSurface: "#1e293b",
4409
+ borderColor: "#334155",
4410
+ textColor: "#f8fafc",
4411
+ mutedTextColor: "#94a3b8",
4412
+ headerColor: "#f8fafc",
4413
+ headerBg: "#1e293b"
4414
+ };
4415
+ function Table({
4416
+ columns,
4417
+ rows,
4418
+ rowKey,
4419
+ mode = "auto",
4420
+ collapseAt = 640,
4421
+ emptyState,
4422
+ palette: paletteOverride,
4423
+ dataAttr = "table"
4424
+ }) {
4425
+ const palette = { ...DEFAULT_PALETTE2, ...paletteOverride };
4426
+ const containerRef = useRef6(null);
4427
+ const [resolvedMode, setResolvedMode] = useState8(
4428
+ mode === "auto" ? "table" : mode
4429
+ );
4430
+ useEffect6(() => {
4431
+ if (mode !== "auto") {
4432
+ setResolvedMode(mode);
4433
+ return;
4434
+ }
4435
+ const el = containerRef.current;
4436
+ if (!el) return;
4437
+ if (typeof ResizeObserver === "undefined") {
4438
+ setResolvedMode("table");
4439
+ return;
4440
+ }
4441
+ const ro = new ResizeObserver((entries) => {
4442
+ const width = entries[0]?.contentRect?.width ?? 0;
4443
+ setResolvedMode(width >= collapseAt ? "table" : "cards");
4444
+ });
4445
+ ro.observe(el);
4446
+ return () => ro.disconnect();
4447
+ }, [mode, collapseAt]);
4448
+ if (rows.length === 0) {
4449
+ return /* @__PURE__ */ jsx15(
4450
+ "div",
4451
+ {
4452
+ ref: containerRef,
4453
+ "data-fp-lens": dataAttr,
4454
+ style: {
4455
+ padding: 24,
4456
+ textAlign: "center",
4457
+ color: palette.mutedTextColor,
4458
+ fontSize: 13
4459
+ },
4460
+ children: emptyState ?? "No rows to display."
4461
+ }
4462
+ );
4463
+ }
4464
+ return /* @__PURE__ */ jsx15(
4465
+ "div",
4466
+ {
4467
+ ref: containerRef,
4468
+ "data-fp-lens": dataAttr,
4469
+ "data-fp-lens-mode": resolvedMode,
4470
+ style: {
4471
+ width: "100%",
4472
+ color: palette.textColor
4473
+ },
4474
+ children: resolvedMode === "table" ? /* @__PURE__ */ jsx15(TableMode, { columns, rows, rowKey, palette }) : /* @__PURE__ */ jsx15(CardsMode, { columns, rows, rowKey, palette })
4475
+ }
4476
+ );
4477
+ }
4478
+ function TableMode({
4479
+ columns,
4480
+ rows,
4481
+ rowKey,
4482
+ palette
4483
+ }) {
4484
+ return /* @__PURE__ */ jsxs9(
4485
+ "table",
4486
+ {
4487
+ "data-fp-lens-mode": "table",
4488
+ style: {
4489
+ width: "100%",
4490
+ borderCollapse: "collapse",
4491
+ fontSize: 13
4492
+ },
4493
+ children: [
4494
+ /* @__PURE__ */ jsx15("thead", { children: /* @__PURE__ */ jsx15("tr", { children: columns.map((col) => /* @__PURE__ */ jsx15(
4495
+ "th",
4496
+ {
4497
+ scope: "col",
4498
+ style: {
4499
+ position: "sticky",
4500
+ top: 0,
4501
+ background: palette.headerBg,
4502
+ color: palette.headerColor,
4503
+ textAlign: col.align ?? "left",
4504
+ padding: "10px 12px",
4505
+ fontWeight: 600,
4506
+ fontSize: 11,
4507
+ textTransform: "uppercase",
4508
+ letterSpacing: "0.08em",
4509
+ borderBottom: `1px solid ${palette.borderColor}`,
4510
+ width: col.width,
4511
+ zIndex: 1
4512
+ },
4513
+ children: col.header
4514
+ },
4515
+ col.id
4516
+ )) }) }),
4517
+ /* @__PURE__ */ jsx15("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsx15(
4518
+ "tr",
4519
+ {
4520
+ style: {
4521
+ borderBottom: `1px solid ${palette.borderColor}`
4522
+ },
4523
+ children: columns.map((col) => /* @__PURE__ */ jsx15(
4524
+ "td",
4525
+ {
4526
+ style: {
4527
+ padding: "10px 12px",
4528
+ textAlign: col.align ?? "left",
4529
+ verticalAlign: "top"
4530
+ },
4531
+ children: col.cell(row, i)
4532
+ },
4533
+ col.id
4534
+ ))
4535
+ },
4536
+ rowKey(row, i)
4537
+ )) })
4538
+ ]
4539
+ }
4540
+ );
4541
+ }
4542
+ function CardsMode({
4543
+ columns,
4544
+ rows,
4545
+ rowKey,
4546
+ palette
4547
+ }) {
4548
+ return /* @__PURE__ */ jsx15(
4549
+ "div",
4550
+ {
4551
+ "data-fp-lens-mode": "cards",
4552
+ style: {
4553
+ display: "flex",
4554
+ flexDirection: "column",
4555
+ gap: 8
4556
+ },
4557
+ children: rows.map((row, i) => {
4558
+ const cardId = rowKey(row, i);
4559
+ return /* @__PURE__ */ jsx15(
4560
+ "div",
4561
+ {
4562
+ role: "group",
4563
+ "aria-label": cardId,
4564
+ style: {
4565
+ background: palette.elevatedSurface,
4566
+ border: `1px solid ${palette.borderColor}`,
4567
+ borderRadius: 8,
4568
+ padding: 12,
4569
+ display: "flex",
4570
+ flexDirection: "column",
4571
+ gap: 6
4572
+ },
4573
+ children: columns.map((col) => {
4574
+ const label = col.cardLabel === null ? null : col.cardLabel ?? (typeof col.header === "string" ? col.header : col.id);
4575
+ return /* @__PURE__ */ jsxs9(
4576
+ "div",
4577
+ {
4578
+ style: {
4579
+ display: "grid",
4580
+ gridTemplateColumns: label ? "120px minmax(0, 1fr)" : "minmax(0, 1fr)",
4581
+ gap: 8,
4582
+ fontSize: 13
4583
+ },
4584
+ children: [
4585
+ label && /* @__PURE__ */ jsx15(
4586
+ "span",
4587
+ {
4588
+ style: {
4589
+ color: palette.mutedTextColor,
4590
+ fontSize: 11,
4591
+ textTransform: "uppercase",
4592
+ letterSpacing: "0.06em",
4593
+ fontWeight: 600
4594
+ },
4595
+ children: label
4596
+ }
4597
+ ),
4598
+ /* @__PURE__ */ jsx15("span", { style: { color: palette.textColor }, children: col.cell(row, i) })
4599
+ ]
4600
+ },
4601
+ col.id
4602
+ );
4603
+ })
4604
+ },
4605
+ cardId
4606
+ );
4607
+ })
4608
+ }
4609
+ );
4610
+ }
4611
+
4612
+ // src/react/layout/FocusRegion.tsx
4613
+ import {
4614
+ useCallback as useCallback4,
4615
+ useEffect as useEffect7,
4616
+ useRef as useRef7,
4617
+ useState as useState9
4618
+ } from "react";
4619
+ import { jsx as jsx16 } from "react/jsx-runtime";
4620
+ var EMPTY_STATE = {
4621
+ focused: false,
4622
+ focusVisible: false,
4623
+ focusedElement: null
4624
+ };
4625
+ function useFocusTracking(ref) {
4626
+ const [state, setState] = useState9(EMPTY_STATE);
4627
+ const lastInputKeyboardRef = useRef7(false);
4628
+ useEffect7(() => {
4629
+ const node = ref.current;
4630
+ if (!node) return;
4631
+ const onKeyDown = () => {
4632
+ lastInputKeyboardRef.current = true;
4633
+ };
4634
+ const onPointerDown = () => {
4635
+ lastInputKeyboardRef.current = false;
4636
+ };
4637
+ const onFocusIn = (e) => {
4638
+ setState({
4639
+ focused: true,
4640
+ focusVisible: lastInputKeyboardRef.current,
4641
+ focusedElement: e.target ?? null
4642
+ });
4643
+ };
4644
+ const onFocusOut = (e) => {
4645
+ const next = e.relatedTarget;
4646
+ if (next && node.contains(next)) {
4647
+ setState((prev) => ({
4648
+ ...prev,
4649
+ focusedElement: next instanceof Element ? next : null
4650
+ }));
4651
+ return;
4652
+ }
4653
+ setState(EMPTY_STATE);
4654
+ };
4655
+ node.addEventListener("focusin", onFocusIn);
4656
+ node.addEventListener("focusout", onFocusOut);
4657
+ document.addEventListener("keydown", onKeyDown, true);
4658
+ document.addEventListener("pointerdown", onPointerDown, true);
4659
+ return () => {
4660
+ node.removeEventListener("focusin", onFocusIn);
4661
+ node.removeEventListener("focusout", onFocusOut);
4662
+ document.removeEventListener("keydown", onKeyDown, true);
4663
+ document.removeEventListener("pointerdown", onPointerDown, true);
4664
+ };
4665
+ }, [ref]);
4666
+ return state;
4667
+ }
4668
+ function FocusRegion({
4669
+ children,
4670
+ onFocusChange,
4671
+ showRing = true,
4672
+ ringColor = "#6366f1",
4673
+ ringWidth = 2,
4674
+ dataAttr,
4675
+ style,
4676
+ className
4677
+ }) {
4678
+ const ref = useRef7(null);
4679
+ const state = useFocusTracking(ref);
4680
+ const lastEmittedRef = useRef7(EMPTY_STATE);
4681
+ const emit = useCallback4(
4682
+ (s) => {
4683
+ const prev = lastEmittedRef.current;
4684
+ if (prev.focused === s.focused && prev.focusVisible === s.focusVisible && prev.focusedElement === s.focusedElement) {
4685
+ return;
4686
+ }
4687
+ lastEmittedRef.current = s;
4688
+ onFocusChange?.(s);
4689
+ },
4690
+ [onFocusChange]
4691
+ );
4692
+ useEffect7(() => {
4693
+ emit(state);
4694
+ }, [state, emit]);
4695
+ const ringVisible = showRing && state.focusVisible && state.focused;
4696
+ return /* @__PURE__ */ jsx16(
4697
+ "div",
4698
+ {
4699
+ ref,
4700
+ "data-fp-lens": dataAttr ?? "focus-region",
4701
+ "data-focused": state.focused,
4702
+ "data-focus-visible": state.focusVisible,
4703
+ className,
4704
+ style: {
4705
+ position: "relative",
4706
+ outline: ringVisible ? `${ringWidth}px solid ${ringColor}` : "none",
4707
+ outlineOffset: 2,
4708
+ transition: "outline-color 140ms ease",
4709
+ ...style
4710
+ },
4711
+ children
4712
+ }
4713
+ );
4714
+ }
4715
+
4716
+ // src/react/panels/IterationStrip.tsx
4717
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
4718
+ function IterationStrip({ timeline, selectedKey, onSelect }) {
4719
+ const t = useLensTheme();
4720
+ const chips = timeline.turns.flatMap(
4721
+ (turn) => turn.iterations.map((iter, stepIdx) => ({
4722
+ key: `${turn.index}.${iter.index}`,
4723
+ turn: turn.index + 1,
4724
+ step: stepIdx + 1,
4725
+ label: stepHeadline(iter),
4726
+ durationMs: iter.durationMs ?? 0,
4727
+ stopReason: iter.stopReason,
4728
+ toolCount: iter.toolCalls.length
4729
+ }))
4730
+ );
4731
+ return /* @__PURE__ */ jsxs10(
4732
+ "div",
4733
+ {
4734
+ "data-fp-lens": "iteration-strip",
4735
+ style: {
4736
+ display: "flex",
4737
+ gap: 4,
4738
+ padding: "8px 12px",
4739
+ overflowX: "auto",
4740
+ borderBottom: `1px solid ${t.border}`,
4741
+ background: t.bgElev
4742
+ },
4743
+ children: [
4744
+ chips.length === 0 && /* @__PURE__ */ jsx17("span", { style: { color: t.textSubtle, fontSize: 11 }, children: "No iterations yet." }),
4745
+ chips.map((c) => {
4746
+ const active = c.key === selectedKey;
4747
+ const isFinal = c.toolCount === 0;
4748
+ const secs = c.durationMs >= 1e3 ? `${(c.durationMs / 1e3).toFixed(1)}s` : `${Math.round(c.durationMs)}ms`;
4749
+ return /* @__PURE__ */ jsxs10(
4750
+ "button",
4751
+ {
4752
+ onClick: () => onSelect?.(c.key),
4753
+ style: {
4754
+ background: active ? t.accent : "transparent",
4755
+ color: active ? "#fff" : t.textMuted,
4756
+ border: `1px solid ${active ? t.accent : t.border}`,
4757
+ borderRadius: 4,
4758
+ padding: "4px 10px",
4759
+ fontSize: 11,
4760
+ fontFamily: t.fontSans,
4761
+ cursor: "pointer",
4762
+ whiteSpace: "nowrap",
4763
+ flexShrink: 0,
4764
+ maxWidth: 280,
4765
+ overflow: "hidden",
4766
+ textOverflow: "ellipsis"
4767
+ },
4768
+ title: `Question ${c.turn} \xB7 Step ${c.step} \xB7 ${secs}${c.stopReason ? ` \xB7 stop: ${c.stopReason}` : ""}`,
4769
+ children: [
4770
+ /* @__PURE__ */ jsxs10("span", { style: { fontWeight: 600 }, children: [
4771
+ "Step ",
4772
+ c.step
4773
+ ] }),
4774
+ /* @__PURE__ */ jsxs10("span", { style: { opacity: 0.85 }, children: [
4775
+ " \xB7 ",
4776
+ c.label
4777
+ ] }),
4778
+ isFinal && /* @__PURE__ */ jsx17("span", { style: { marginLeft: 6 }, children: "\u2713" })
4779
+ ]
4780
+ },
4781
+ c.key
4782
+ );
4783
+ })
4784
+ ]
4785
+ }
4786
+ );
4787
+ }
4788
+ function stepHeadline(iter) {
4789
+ if (iter.toolCalls.length === 0) return "Ready to answer";
4790
+ if (iter.toolCalls.length === 1) {
4791
+ const tc = iter.toolCalls[0];
4792
+ if (tc.name === "list_skills") return "Looking up skills";
4793
+ if (tc.name === "read_skill") {
4794
+ const id = tc.arguments?.id;
4795
+ return id ? `Activated ${id}` : "Activating skill";
4796
+ }
4797
+ if (tc.name === "ask_human" || tc.name === "ask_user") {
4798
+ return "Asked user";
4799
+ }
4800
+ return `Called tool (${tc.name})`;
4801
+ }
4802
+ if (iter.toolCalls.length <= 3) {
4803
+ return `Called ${iter.toolCalls.length} tools (${iter.toolCalls.map((tc) => tc.name).join(", ")})`;
4804
+ }
4805
+ return `Called ${iter.toolCalls.length} tools in parallel`;
4806
+ }
4807
+
4808
+ // src/react/panels/ToolCallInspector.tsx
4809
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
4810
+ function ToolCallInspector({
4811
+ timeline,
4812
+ selectedId,
4813
+ onSelect
4814
+ }) {
4815
+ const t = useLensTheme();
4816
+ return /* @__PURE__ */ jsxs11(
4817
+ "div",
4818
+ {
4819
+ "data-fp-lens": "tool-call-inspector",
4820
+ style: {
4821
+ background: t.bg,
4822
+ color: t.text,
4823
+ fontFamily: t.fontSans,
4824
+ display: "flex",
4825
+ flexDirection: "column",
4826
+ minHeight: 0,
4827
+ overflow: "hidden"
4828
+ },
4829
+ children: [
4830
+ /* @__PURE__ */ jsxs11(
4831
+ "div",
4832
+ {
4833
+ style: {
4834
+ padding: "10px 14px",
4835
+ borderBottom: `1px solid ${t.border}`,
4836
+ fontSize: 11,
4837
+ color: t.textSubtle,
4838
+ textTransform: "uppercase",
4839
+ letterSpacing: "0.08em",
4840
+ fontWeight: 600,
4841
+ background: t.bgElev
4842
+ },
4843
+ children: [
4844
+ "Every tool Neo called \xB7 ",
4845
+ timeline.tools.length
4846
+ ]
4847
+ }
4848
+ ),
4849
+ /* @__PURE__ */ jsxs11("div", { style: { overflow: "auto", flex: 1 }, children: [
4850
+ timeline.tools.length === 0 && /* @__PURE__ */ jsx18("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "Neo hasn't called any tools yet." }),
4851
+ timeline.tools.map((tc) => {
4852
+ const active = tc.id === selectedId;
4853
+ const errored = tc.error === true;
4854
+ return /* @__PURE__ */ jsxs11(
4855
+ "button",
4856
+ {
4857
+ onClick: () => onSelect?.(tc),
4858
+ style: {
4859
+ display: "flex",
4860
+ flexDirection: "column",
4861
+ alignItems: "stretch",
4862
+ width: "100%",
4863
+ textAlign: "left",
4864
+ padding: "8px 12px",
4865
+ background: active ? t.bgHover : "transparent",
4866
+ border: "none",
4867
+ borderLeft: `3px solid ${active ? t.accent : errored ? t.error : "transparent"}`,
4868
+ borderBottom: `1px solid ${t.border}`,
4869
+ color: t.text,
4870
+ cursor: "pointer",
4871
+ fontFamily: "inherit"
4872
+ },
4873
+ children: [
4874
+ /* @__PURE__ */ jsxs11(
4875
+ "div",
4876
+ {
4877
+ style: {
4878
+ display: "flex",
4879
+ gap: 8,
4880
+ alignItems: "baseline",
4881
+ fontSize: 12,
4882
+ fontFamily: t.fontMono
4883
+ },
4884
+ children: [
4885
+ /* @__PURE__ */ jsx18("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: tc.name }),
4886
+ /* @__PURE__ */ jsxs11(
4887
+ "span",
4888
+ {
4889
+ style: { color: t.textSubtle, fontSize: 10, marginLeft: "auto" },
4890
+ children: [
4891
+ "t",
4892
+ tc.turnIndex + 1,
4893
+ ".i",
4894
+ tc.iterationIndex,
4895
+ tc.durationMs !== void 0 && ` \xB7 ${Math.round(tc.durationMs)}ms`
4896
+ ]
4897
+ }
4898
+ )
4899
+ ]
4900
+ }
4901
+ ),
4902
+ /* @__PURE__ */ jsx18(
4903
+ "div",
4904
+ {
4905
+ style: {
4906
+ fontSize: 10,
4907
+ color: t.textMuted,
4908
+ marginTop: 2,
4909
+ fontFamily: t.fontMono,
4910
+ overflow: "hidden",
4911
+ textOverflow: "ellipsis",
4912
+ whiteSpace: "nowrap"
4913
+ },
4914
+ children: shortArgs2(tc.arguments)
4915
+ }
4916
+ )
4917
+ ]
4918
+ },
4919
+ tc.id
4920
+ );
4921
+ })
4922
+ ] })
4923
+ ]
4924
+ }
4925
+ );
4926
+ }
4927
+ function shortArgs2(args) {
4928
+ const keys = Object.keys(args);
4929
+ if (keys.length === 0) return "\u2014 no args \u2014";
4930
+ return keys.map((k) => {
4931
+ const v = args[k];
4932
+ if (typeof v === "string") return `${k}: "${v.length > 20 ? v.slice(0, 20) + "\u2026" : v}"`;
4933
+ if (typeof v === "number" || typeof v === "boolean") return `${k}: ${v}`;
4934
+ return `${k}: \u2026`;
4935
+ }).join(", ");
4936
+ }
4937
+
4938
+ // src/react/hooks/useLens.ts
4939
+ import { useRef as useRef8 } from "react";
4940
+ function useLens(factory) {
4941
+ const ref = useRef8(null);
4942
+ if (ref.current === null) {
4943
+ ref.current = factory();
4944
+ }
4945
+ return ref.current;
4946
+ }
4947
+
4948
+ export {
4949
+ useLensTheme,
4950
+ resolve,
4951
+ MessagesPanel,
4952
+ SkillsPanel,
4953
+ StageFlow,
4954
+ TimeTravel,
4955
+ AskCard,
4956
+ RunSummary,
4957
+ AgentLens,
4958
+ FillParent,
4959
+ Tabs,
4960
+ SelfSizingRoot,
4961
+ useLiveTimeline,
4962
+ Lens,
4963
+ Stack,
4964
+ Scroller,
4965
+ Surface,
4966
+ Table,
4967
+ useFocusTracking,
4968
+ FocusRegion,
4969
+ IterationStrip,
4970
+ ToolCallInspector,
4971
+ useLens
4972
+ };
4973
+ //# sourceMappingURL=chunk-AWPI6OVI.js.map