agentfootprint-lens 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/chunk-2JKRPWPZ.js +3793 -0
  2. package/dist/chunk-2JKRPWPZ.js.map +1 -0
  3. package/dist/chunk-2OGMN63Q.js +4430 -0
  4. package/dist/chunk-2OGMN63Q.js.map +1 -0
  5. package/dist/chunk-2ZNJKUV7.js +3781 -0
  6. package/dist/chunk-2ZNJKUV7.js.map +1 -0
  7. package/dist/chunk-4LDHJAAY.js +3772 -0
  8. package/dist/chunk-4LDHJAAY.js.map +1 -0
  9. package/dist/chunk-7KGWX7RU.js +547 -0
  10. package/dist/chunk-7KGWX7RU.js.map +1 -0
  11. package/dist/chunk-EFG52PIH.js +3783 -0
  12. package/dist/chunk-EFG52PIH.js.map +1 -0
  13. package/dist/chunk-FAHWCPLM.js +3930 -0
  14. package/dist/chunk-FAHWCPLM.js.map +1 -0
  15. package/dist/chunk-FH3WCJCV.js +3828 -0
  16. package/dist/chunk-FH3WCJCV.js.map +1 -0
  17. package/dist/chunk-LOKXVQL6.js +3822 -0
  18. package/dist/chunk-LOKXVQL6.js.map +1 -0
  19. package/dist/chunk-OROO4WJY.js +3821 -0
  20. package/dist/chunk-OROO4WJY.js.map +1 -0
  21. package/dist/chunk-PPSBDRYH.js +3793 -0
  22. package/dist/chunk-PPSBDRYH.js.map +1 -0
  23. package/dist/chunk-ZDXQDQI4.js +3822 -0
  24. package/dist/chunk-ZDXQDQI4.js.map +1 -0
  25. package/dist/core.cjs +575 -0
  26. package/dist/core.cjs.map +1 -0
  27. package/dist/core.d.cts +363 -0
  28. package/dist/core.d.ts +363 -0
  29. package/dist/core.js +11 -0
  30. package/dist/core.js.map +1 -0
  31. package/dist/index.cjs +4052 -436
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +5 -319
  34. package/dist/index.d.ts +5 -319
  35. package/dist/index.js +45 -1352
  36. package/dist/index.js.map +1 -1
  37. package/dist/react.cjs +5015 -0
  38. package/dist/react.cjs.map +1 -0
  39. package/dist/react.d.cts +524 -0
  40. package/dist/react.d.ts +524 -0
  41. package/dist/react.js +57 -0
  42. package/dist/react.js.map +1 -0
  43. package/package.json +25 -4
@@ -0,0 +1,3793 @@
1
+ import {
2
+ LiveTimelineBuilder,
3
+ deriveStages,
4
+ fromAgentSnapshot
5
+ } from "./chunk-7KGWX7RU.js";
6
+
7
+ // src/react/Lens.tsx
8
+ import { useState as useState6 } from "react";
9
+ import { ExplainableShell, FootprintTheme } from "footprint-explainable-ui";
10
+
11
+ // src/react/AgentLens.tsx
12
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, 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 { useMemo } from "react";
1773
+ import {
1774
+ ReactFlow,
1775
+ Background,
1776
+ BackgroundVariant,
1777
+ Handle,
1778
+ Position,
1779
+ BaseEdge,
1780
+ getSmoothStepPath
1781
+ } from "@xyflow/react";
1782
+ import "@xyflow/react/dist/style.css";
1783
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1784
+ var NODE_POSITIONS = {
1785
+ user: { x: 100, y: 20 },
1786
+ agent: { x: 100, y: 160 },
1787
+ tool: { x: 100, y: 380 },
1788
+ skill: { x: 320, y: 380 }
1789
+ };
1790
+ function pickHandles(from, to) {
1791
+ if (from === "user" && to === "agent") return { sourceHandle: "b-out", targetHandle: "t-in" };
1792
+ if (from === "agent" && to === "user") return { sourceHandle: "t-out", targetHandle: "b-in" };
1793
+ if (from === "agent" && to === "tool") return { sourceHandle: "b-out", targetHandle: "t-in" };
1794
+ if (from === "tool" && to === "agent") return { sourceHandle: "t-out", targetHandle: "b-in" };
1795
+ if (from === "agent" && to === "skill") return { sourceHandle: "r-out", targetHandle: "l-in" };
1796
+ if (from === "skill" && to === "agent") return { sourceHandle: "l-out", targetHandle: "r-in" };
1797
+ return { sourceHandle: "r-out", targetHandle: "l-in" };
1798
+ }
1799
+ var NODE_LABELS = {
1800
+ user: "User",
1801
+ agent: "Agent",
1802
+ tool: "Tool",
1803
+ skill: "Skill"
1804
+ };
1805
+ var NODE_SUBLABELS = {
1806
+ user: "You",
1807
+ agent: "The LLM",
1808
+ tool: "Data source / action",
1809
+ // "Adds context + tools" is what the user actually observes when a
1810
+ // skill activates — the skill body lands in System Prompt and its
1811
+ // tool list surfaces in Tools. That's the whole effect; no need for
1812
+ // jargon like "bracket over a primitive."
1813
+ skill: "Adds context + tools"
1814
+ };
1815
+ function StageFlow({
1816
+ stages,
1817
+ focusIndex,
1818
+ onEdgeClick,
1819
+ height = 460,
1820
+ activeSkillId
1821
+ }) {
1822
+ const t = useLensTheme();
1823
+ const focus = focusIndex !== void 0 && focusIndex >= 0 ? focusIndex : stages.length - 1;
1824
+ const visible = useMemo(() => stages.slice(0, focus + 1), [stages, focus]);
1825
+ const activeStage = visible[visible.length - 1];
1826
+ const touched = useMemo(() => {
1827
+ const set = /* @__PURE__ */ new Set();
1828
+ for (const s of visible) {
1829
+ set.add(s.from);
1830
+ set.add(s.to);
1831
+ if (s.alsoLights) set.add(s.alsoLights);
1832
+ }
1833
+ return set;
1834
+ }, [visible]);
1835
+ const activeNodes = useMemo(() => {
1836
+ const s = /* @__PURE__ */ new Set();
1837
+ if (activeStage) {
1838
+ s.add(activeStage.to);
1839
+ if (activeStage.alsoLights) s.add(activeStage.alsoLights);
1840
+ }
1841
+ return s;
1842
+ }, [activeStage]);
1843
+ const edges = useMemo(() => {
1844
+ const byKey = /* @__PURE__ */ new Map();
1845
+ visible.forEach((s) => {
1846
+ byKey.set(`${s.from}\u2192${s.to}`, { from: s.from, to: s.to, lastStage: s });
1847
+ });
1848
+ return [...byKey.values()].map(({ from, to, lastStage }) => {
1849
+ const isActive = activeStage !== void 0 && from === activeStage.from && to === activeStage.to;
1850
+ const { sourceHandle, targetHandle } = pickHandles(from, to);
1851
+ const isLoop = to === "agent" && (from === "tool" || from === "skill");
1852
+ return {
1853
+ id: `${from}\u2192${to}`,
1854
+ source: from,
1855
+ target: to,
1856
+ sourceHandle,
1857
+ targetHandle,
1858
+ type: "labelled",
1859
+ // Only loop edges get the marching-ants animation.
1860
+ animated: isLoop,
1861
+ data: {
1862
+ primitive: lastStage.primitive,
1863
+ active: isActive,
1864
+ isLoop,
1865
+ stage: lastStage
1866
+ }
1867
+ };
1868
+ });
1869
+ }, [visible, activeStage]);
1870
+ const nodes = useMemo(() => {
1871
+ return Object.keys(NODE_POSITIONS).filter((id) => {
1872
+ if (id === "skill") return touched.has("skill");
1873
+ return true;
1874
+ }).map((id) => ({
1875
+ id,
1876
+ type: "lens",
1877
+ position: NODE_POSITIONS[id],
1878
+ data: {
1879
+ id,
1880
+ active: activeNodes.has(id),
1881
+ touched: touched.has(id),
1882
+ // Which of the Agent's three ports actually MUTATED this step.
1883
+ // Multiple can be true at once (read_skill touches all three).
1884
+ ...id === "agent" && activeStage ? { activeMutations: activeStage.mutations } : {},
1885
+ // Skill annotation on the Agent node — tells the user which
1886
+ // skill is governing the current System Prompt + Tools. Pure
1887
+ // context signal; doesn't affect layout.
1888
+ ...id === "agent" && activeSkillId ? { activeSkillId } : {},
1889
+ // Tool node shows the SPECIFIC tool name that's currently
1890
+ // being called — debugging without this is guesswork ("we
1891
+ // called a tool — but which one?"). Only surfaces when the
1892
+ // active stage actually references a tool name.
1893
+ ...id === "tool" && activeStage?.toolName && (activeStage.from === "tool" || activeStage.to === "tool") ? {
1894
+ activeLabel: activeStage.toolName,
1895
+ ...activeStage.parallelCount ? { parallelCount: activeStage.parallelCount } : {}
1896
+ } : {}
1897
+ },
1898
+ draggable: false
1899
+ }));
1900
+ }, [activeNodes, touched, activeStage, activeSkillId]);
1901
+ const nodeTypes = useMemo(() => ({ lens: LensNode }), []);
1902
+ const edgeTypes = useMemo(() => ({ labelled: LensEdge(onEdgeClick) }), [onEdgeClick]);
1903
+ return /* @__PURE__ */ jsxs3(
1904
+ "div",
1905
+ {
1906
+ "data-fp-lens": "stage-flow",
1907
+ style: {
1908
+ height,
1909
+ background: t.bg,
1910
+ borderBottom: `1px solid ${t.border}`
1911
+ },
1912
+ children: [
1913
+ /* @__PURE__ */ jsx3(EdgeMarkerDefs, {}),
1914
+ /* @__PURE__ */ jsx3(
1915
+ ReactFlow,
1916
+ {
1917
+ nodes,
1918
+ edges,
1919
+ nodeTypes,
1920
+ edgeTypes,
1921
+ fitView: true,
1922
+ fitViewOptions: { padding: 0.15 },
1923
+ proOptions: { hideAttribution: true },
1924
+ nodesDraggable: false,
1925
+ nodesConnectable: false,
1926
+ elementsSelectable: false,
1927
+ panOnDrag: false,
1928
+ zoomOnScroll: false,
1929
+ zoomOnPinch: false,
1930
+ zoomOnDoubleClick: false,
1931
+ children: /* @__PURE__ */ jsx3(
1932
+ Background,
1933
+ {
1934
+ variant: BackgroundVariant.Dots,
1935
+ gap: 18,
1936
+ size: 1,
1937
+ color: t.border
1938
+ }
1939
+ )
1940
+ }
1941
+ )
1942
+ ]
1943
+ }
1944
+ );
1945
+ }
1946
+ function EdgeMarkerDefs() {
1947
+ const t = useLensTheme();
1948
+ const active = t.accent;
1949
+ const loop = `color-mix(in srgb, ${t.accent} 55%, ${t.border})`;
1950
+ const dim = t.border;
1951
+ return /* @__PURE__ */ jsx3(
1952
+ "svg",
1953
+ {
1954
+ "aria-hidden": "true",
1955
+ style: { position: "absolute", width: 0, height: 0, pointerEvents: "none" },
1956
+ children: /* @__PURE__ */ jsx3("defs", { children: [
1957
+ { id: "lens-arrow-active", fill: active },
1958
+ { id: "lens-arrow-loop", fill: loop },
1959
+ { id: "lens-arrow-dim", fill: dim }
1960
+ ].map((m) => /* @__PURE__ */ jsx3(
1961
+ "marker",
1962
+ {
1963
+ id: m.id,
1964
+ viewBox: "0 0 10 10",
1965
+ refX: "9",
1966
+ refY: "5",
1967
+ markerWidth: "6",
1968
+ markerHeight: "6",
1969
+ orient: "auto-start-reverse",
1970
+ children: /* @__PURE__ */ jsx3("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: m.fill })
1971
+ },
1972
+ m.id
1973
+ )) })
1974
+ }
1975
+ );
1976
+ }
1977
+ var NODE_KEYFRAMES_ID = "fp-lens-node-keyframes";
1978
+ var NODE_KEYFRAMES_CSS = `
1979
+ @media (prefers-reduced-motion: no-preference) {
1980
+ @keyframes fp-lens-pulse {
1981
+ 0%, 100% { opacity: 0.4; transform: scale(1); }
1982
+ 50% { opacity: 0.12; transform: scale(1.08); }
1983
+ }
1984
+ }
1985
+ @media (prefers-reduced-motion: reduce) {
1986
+ @keyframes fp-lens-pulse { 0%, 100% { opacity: 0.3; } }
1987
+ }
1988
+ `;
1989
+ function injectNodeKeyframes() {
1990
+ if (typeof document === "undefined") return;
1991
+ if (document.getElementById(NODE_KEYFRAMES_ID)) return;
1992
+ const el = document.createElement("style");
1993
+ el.id = NODE_KEYFRAMES_ID;
1994
+ el.textContent = NODE_KEYFRAMES_CSS;
1995
+ document.head.appendChild(el);
1996
+ }
1997
+ function LensNode({ data }) {
1998
+ const t = useLensTheme();
1999
+ const d = data;
2000
+ injectNodeKeyframes();
2001
+ const isActive = d.active;
2002
+ const isDone = !d.active && d.touched;
2003
+ const bg = isActive ? t.accent : isDone ? t.bgElev : t.bg;
2004
+ const border = isActive ? t.accent : isDone ? t.border : t.border;
2005
+ const textColor = isActive ? "#ffffff" : isDone ? t.text : t.textSubtle;
2006
+ 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)`;
2007
+ const label = NODE_LABELS[d.id];
2008
+ const sub = isActive && d.activeLabel ? d.activeLabel : NODE_SUBLABELS[d.id];
2009
+ const isAgent = d.id === "agent";
2010
+ return /* @__PURE__ */ jsxs3("div", { style: { position: "relative", display: "inline-block" }, children: [
2011
+ isActive && /* @__PURE__ */ jsx3(
2012
+ "div",
2013
+ {
2014
+ style: {
2015
+ position: "absolute",
2016
+ inset: -6,
2017
+ borderRadius: 14,
2018
+ border: `2px solid ${t.accent}`,
2019
+ opacity: 0.35,
2020
+ pointerEvents: "none",
2021
+ animation: "fp-lens-pulse 1.6s ease-out infinite"
2022
+ }
2023
+ }
2024
+ ),
2025
+ /* @__PURE__ */ jsxs3(
2026
+ "div",
2027
+ {
2028
+ style: {
2029
+ width: isAgent ? 200 : 150,
2030
+ padding: "12px 16px",
2031
+ borderRadius: 10,
2032
+ background: bg,
2033
+ border: `2px solid ${border}`,
2034
+ color: textColor,
2035
+ fontFamily: t.fontSans,
2036
+ textAlign: "center",
2037
+ boxShadow: shadow,
2038
+ transition: "background 220ms ease, border-color 220ms ease, box-shadow 220ms ease, color 220ms ease"
2039
+ },
2040
+ children: [
2041
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }, children: [
2042
+ /* @__PURE__ */ jsx3(NodeIcon, { id: d.id, color: textColor }),
2043
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 13, fontWeight: 600 }, children: label })
2044
+ ] }),
2045
+ /* @__PURE__ */ jsx3(
2046
+ "div",
2047
+ {
2048
+ style: {
2049
+ fontSize: isActive && d.activeLabel ? 11 : 10,
2050
+ color: isActive ? "rgba(255,255,255,0.95)" : t.textSubtle,
2051
+ marginTop: 2,
2052
+ fontWeight: isActive && d.activeLabel ? 600 : 400,
2053
+ fontFamily: isActive && d.activeLabel ? t.fontMono : t.fontSans,
2054
+ maxWidth: isAgent ? 180 : 130,
2055
+ overflow: "hidden",
2056
+ textOverflow: "ellipsis",
2057
+ whiteSpace: "nowrap",
2058
+ margin: "2px auto 0"
2059
+ },
2060
+ title: sub,
2061
+ children: sub
2062
+ }
2063
+ ),
2064
+ d.id === "tool" && isActive && (d.parallelCount ?? 0) > 1 && /* @__PURE__ */ jsxs3(
2065
+ "div",
2066
+ {
2067
+ title: `This tool is one of ${d.parallelCount} called in parallel this round`,
2068
+ style: {
2069
+ marginTop: 4,
2070
+ alignSelf: "center",
2071
+ display: "inline-block",
2072
+ padding: "1px 7px",
2073
+ borderRadius: 999,
2074
+ background: "rgba(255,255,255,0.25)",
2075
+ color: "#ffffff",
2076
+ fontSize: 9,
2077
+ fontWeight: 700,
2078
+ letterSpacing: "0.08em",
2079
+ textTransform: "uppercase"
2080
+ },
2081
+ children: [
2082
+ "\u26A1 Parallel \xB7 ",
2083
+ d.parallelCount
2084
+ ]
2085
+ }
2086
+ ),
2087
+ isAgent && /* @__PURE__ */ jsxs3(Fragment3, { children: [
2088
+ d.activeSkillId && /* @__PURE__ */ jsxs3(
2089
+ "div",
2090
+ {
2091
+ title: `System Prompt + Tools are currently governed by the ${d.activeSkillId} skill`,
2092
+ style: {
2093
+ marginTop: 8,
2094
+ padding: "3px 9px",
2095
+ borderRadius: 999,
2096
+ background: isActive ? "rgba(255,255,255,0.18)" : t.bgElev,
2097
+ border: `1px solid ${isActive ? "rgba(255,255,255,0.35)" : t.border}`,
2098
+ color: isActive ? "#ffffff" : t.accent,
2099
+ fontSize: 10,
2100
+ fontFamily: t.fontMono,
2101
+ fontWeight: 600,
2102
+ whiteSpace: "nowrap",
2103
+ maxWidth: 180,
2104
+ overflow: "hidden",
2105
+ textOverflow: "ellipsis",
2106
+ display: "inline-block"
2107
+ },
2108
+ children: [
2109
+ "\u{1F4DA} ",
2110
+ d.activeSkillId
2111
+ ]
2112
+ }
2113
+ ),
2114
+ /* @__PURE__ */ jsx3(
2115
+ AgentPorts,
2116
+ {
2117
+ active: d.active,
2118
+ mutations: d.activeMutations,
2119
+ filledCard: isActive
2120
+ }
2121
+ )
2122
+ ] }),
2123
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: Position.Top, id: "t-out", style: handleStyle(-14, 0) }),
2124
+ /* @__PURE__ */ jsx3(Handle, { type: "target", position: Position.Top, id: "t-in", style: handleStyle(14, 0) }),
2125
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: Position.Bottom, id: "b-out", style: handleStyle(14, 0) }),
2126
+ /* @__PURE__ */ jsx3(Handle, { type: "target", position: Position.Bottom, id: "b-in", style: handleStyle(-14, 0) }),
2127
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: Position.Left, id: "l-out", style: handleStyle(0, 10) }),
2128
+ /* @__PURE__ */ jsx3(Handle, { type: "target", position: Position.Left, id: "l-in", style: handleStyle(0, -10) }),
2129
+ /* @__PURE__ */ jsx3(Handle, { type: "source", position: Position.Right, id: "r-out", style: handleStyle(0, -10) }),
2130
+ /* @__PURE__ */ jsx3(Handle, { type: "target", position: Position.Right, id: "r-in", style: handleStyle(0, 10) })
2131
+ ]
2132
+ }
2133
+ )
2134
+ ] });
2135
+ }
2136
+ function NodeIcon({ id, color }) {
2137
+ const size = 16;
2138
+ const props = {
2139
+ width: size,
2140
+ height: size,
2141
+ viewBox: `0 0 ${size} ${size}`,
2142
+ fill: "none",
2143
+ style: { flexShrink: 0 }
2144
+ };
2145
+ if (id === "user") {
2146
+ return /* @__PURE__ */ jsxs3("svg", { ...props, children: [
2147
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "5", r: "2.5", stroke: color, strokeWidth: "1.5" }),
2148
+ /* @__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" })
2149
+ ] });
2150
+ }
2151
+ if (id === "agent") {
2152
+ return /* @__PURE__ */ jsxs3("svg", { ...props, children: [
2153
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "6", stroke: color, strokeWidth: "1.5" }),
2154
+ /* @__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" }),
2155
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "9.5", r: "1", fill: color }),
2156
+ /* @__PURE__ */ jsx3("line", { x1: "8", y1: "2", x2: "8", y2: "3.5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
2157
+ /* @__PURE__ */ jsx3("line", { x1: "12.5", y1: "4", x2: "11.2", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
2158
+ /* @__PURE__ */ jsx3("line", { x1: "3.5", y1: "4", x2: "4.8", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
2159
+ ] });
2160
+ }
2161
+ if (id === "tool") {
2162
+ return /* @__PURE__ */ jsxs3("svg", { ...props, children: [
2163
+ /* @__PURE__ */ jsx3("circle", { cx: "8", cy: "8", r: "3", stroke: color, strokeWidth: "1.5" }),
2164
+ [0, 45, 90, 135, 180, 225, 270, 315].map((angle) => {
2165
+ const rad = angle * Math.PI / 180;
2166
+ const x1 = 8 + Math.cos(rad) * 4.5;
2167
+ const y1 = 8 + Math.sin(rad) * 4.5;
2168
+ const x2 = 8 + Math.cos(rad) * 6;
2169
+ const y2 = 8 + Math.sin(rad) * 6;
2170
+ return /* @__PURE__ */ jsx3(
2171
+ "line",
2172
+ {
2173
+ x1,
2174
+ y1,
2175
+ x2,
2176
+ y2,
2177
+ stroke: color,
2178
+ strokeWidth: "1.5",
2179
+ strokeLinecap: "round"
2180
+ },
2181
+ angle
2182
+ );
2183
+ })
2184
+ ] });
2185
+ }
2186
+ return /* @__PURE__ */ jsxs3("svg", { ...props, children: [
2187
+ /* @__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" }),
2188
+ /* @__PURE__ */ jsx3("path", { d: "M5.5 7h4M5.5 9.5h4", stroke: color, strokeWidth: "1.2", strokeLinecap: "round" })
2189
+ ] });
2190
+ }
2191
+ function handleStyle(dx, dy) {
2192
+ return {
2193
+ ...HANDLE_STYLE,
2194
+ transform: `translate(${dx}px, ${dy}px)`
2195
+ };
2196
+ }
2197
+ function AgentPorts({
2198
+ active,
2199
+ mutations,
2200
+ filledCard
2201
+ }) {
2202
+ const t = useLensTheme();
2203
+ const litSP = active && mutations?.systemPrompt === true;
2204
+ const litMsg = active && mutations?.messages === true;
2205
+ const litTools = active && mutations?.tools === true;
2206
+ const spBadge = mutations?.systemPromptDeltaChars !== void 0 ? `+${mutations.systemPromptDeltaChars.toLocaleString()} chars` : null;
2207
+ const toolsBadge = (() => {
2208
+ const added = mutations?.toolsAdded ?? 0;
2209
+ const removed = mutations?.toolsRemoved ?? 0;
2210
+ if (added === 0 && removed === 0) return null;
2211
+ const bits = [];
2212
+ if (added > 0) bits.push(`+${added}`);
2213
+ if (removed > 0) bits.push(`-${removed}`);
2214
+ return bits.join(" / ");
2215
+ })();
2216
+ const ports = [
2217
+ {
2218
+ key: "system-prompt",
2219
+ label: "System Prompt",
2220
+ hint: "Instructions Neo runs on",
2221
+ lit: litSP,
2222
+ badge: litSP ? spBadge : null
2223
+ },
2224
+ {
2225
+ key: "message",
2226
+ label: "Messages",
2227
+ hint: "Conversation so far",
2228
+ lit: litMsg,
2229
+ badge: null
2230
+ },
2231
+ {
2232
+ key: "tool",
2233
+ label: "Tools",
2234
+ hint: "What Neo can call",
2235
+ lit: litTools,
2236
+ badge: litTools ? toolsBadge : null
2237
+ }
2238
+ ];
2239
+ const boxBg = filledCard ? "rgba(255,255,255,0.12)" : t.bg;
2240
+ const boxBorder = filledCard ? "rgba(255,255,255,0.25)" : t.border;
2241
+ const portIdle = filledCard ? "rgba(255,255,255,0.7)" : t.textMuted;
2242
+ const portLitBg = filledCard ? "rgba(255,255,255,0.25)" : `color-mix(in srgb, ${t.accent} 30%, transparent)`;
2243
+ const portLitColor = filledCard ? "#ffffff" : t.accent;
2244
+ const portLitBorder = filledCard ? "#ffffff" : t.accent;
2245
+ const portIdleBorder = filledCard ? "rgba(255,255,255,0.3)" : t.border;
2246
+ return /* @__PURE__ */ jsx3(
2247
+ "div",
2248
+ {
2249
+ style: {
2250
+ display: "flex",
2251
+ flexDirection: "column",
2252
+ gap: 3,
2253
+ marginTop: 8,
2254
+ padding: 4,
2255
+ background: boxBg,
2256
+ border: `1px solid ${boxBorder}`,
2257
+ borderRadius: 6
2258
+ },
2259
+ children: ports.map((p) => /* @__PURE__ */ jsxs3(
2260
+ "div",
2261
+ {
2262
+ title: p.hint,
2263
+ style: {
2264
+ padding: "2px 8px",
2265
+ borderRadius: 3,
2266
+ background: p.lit ? portLitBg : "transparent",
2267
+ color: p.lit ? portLitColor : portIdle,
2268
+ fontSize: 10,
2269
+ fontWeight: p.lit ? 600 : 500,
2270
+ letterSpacing: "0.02em",
2271
+ textAlign: "left",
2272
+ fontFamily: t.fontSans,
2273
+ display: "flex",
2274
+ alignItems: "center",
2275
+ gap: 6,
2276
+ borderLeft: `2px solid ${p.lit ? portLitBorder : portIdleBorder}`
2277
+ },
2278
+ children: [
2279
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 8 }, children: "\u25B8" }),
2280
+ /* @__PURE__ */ jsx3("span", { style: { flex: 1 }, children: p.label }),
2281
+ p.badge && /* @__PURE__ */ jsx3(
2282
+ "span",
2283
+ {
2284
+ style: {
2285
+ fontSize: 9,
2286
+ padding: "0 4px",
2287
+ borderRadius: 3,
2288
+ background: filledCard ? "rgba(255,255,255,0.22)" : t.bg,
2289
+ color: p.lit ? portLitColor : portIdle,
2290
+ fontFamily: t.fontMono,
2291
+ fontWeight: 600
2292
+ },
2293
+ children: p.badge
2294
+ }
2295
+ )
2296
+ ]
2297
+ },
2298
+ p.key
2299
+ ))
2300
+ }
2301
+ );
2302
+ }
2303
+ var HANDLE_STYLE = {
2304
+ opacity: 0,
2305
+ pointerEvents: "none",
2306
+ width: 1,
2307
+ height: 1
2308
+ };
2309
+ function LensEdge(onEdgeClick) {
2310
+ return function LensEdgeInner({
2311
+ id,
2312
+ sourceX,
2313
+ sourceY,
2314
+ targetX,
2315
+ targetY,
2316
+ sourcePosition,
2317
+ targetPosition,
2318
+ data
2319
+ }) {
2320
+ const t = useLensTheme();
2321
+ const d = data;
2322
+ const [edgePath] = getSmoothStepPath({
2323
+ sourceX,
2324
+ sourceY,
2325
+ sourcePosition,
2326
+ targetX,
2327
+ targetY,
2328
+ targetPosition,
2329
+ borderRadius: 10
2330
+ });
2331
+ const stroke = d.active ? t.accent : d.isLoop ? `color-mix(in srgb, ${t.accent} 55%, ${t.border})` : t.border;
2332
+ const strokeWidth = d.active ? 2.25 : d.isLoop ? 1.75 : 1.5;
2333
+ const strokeDasharray = d.isLoop ? "5 4" : void 0;
2334
+ const markerId = d.active ? "lens-arrow-active" : d.isLoop ? "lens-arrow-loop" : "lens-arrow-dim";
2335
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
2336
+ /* @__PURE__ */ jsx3(
2337
+ BaseEdge,
2338
+ {
2339
+ id,
2340
+ path: edgePath,
2341
+ style: {
2342
+ stroke,
2343
+ strokeWidth,
2344
+ strokeDasharray,
2345
+ filter: d.active ? `drop-shadow(0 0 6px color-mix(in srgb, ${t.accent} 50%, transparent))` : void 0,
2346
+ cursor: onEdgeClick ? "pointer" : "default"
2347
+ },
2348
+ markerEnd: `url(#${markerId})`
2349
+ }
2350
+ ),
2351
+ onEdgeClick && /* @__PURE__ */ jsx3(
2352
+ "path",
2353
+ {
2354
+ d: edgePath,
2355
+ fill: "none",
2356
+ stroke: "transparent",
2357
+ strokeWidth: 12,
2358
+ style: { cursor: "pointer" },
2359
+ onClick: () => onEdgeClick(d.stage)
2360
+ }
2361
+ )
2362
+ ] });
2363
+ };
2364
+ }
2365
+
2366
+ // src/react/panels/TimeTravel.tsx
2367
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2368
+ function TimeTravel({
2369
+ stages,
2370
+ focusIndex,
2371
+ onFocusChange,
2372
+ isLive
2373
+ }) {
2374
+ const t = useLensTheme();
2375
+ const max = Math.max(0, stages.length - 1);
2376
+ function step(delta) {
2377
+ const next = Math.min(max, Math.max(0, focusIndex + delta));
2378
+ onFocusChange(next);
2379
+ }
2380
+ return /* @__PURE__ */ jsxs4(
2381
+ "div",
2382
+ {
2383
+ "data-fp-lens": "time-travel",
2384
+ style: {
2385
+ // Frosted-glass oval pill, floating clear of the surrounding
2386
+ // surfaces. `color-mix` gives a translucent tint using the
2387
+ // current theme's elevated bg; `backdrop-filter` blurs
2388
+ // whatever's behind (the graph + ask card show through
2389
+ // softly). Margin creates air around the pill so it reads as
2390
+ // a distinct floating control, not a toolbar bar.
2391
+ display: "flex",
2392
+ alignItems: "center",
2393
+ gap: 10,
2394
+ padding: "8px 14px",
2395
+ margin: "10px 14px",
2396
+ background: `color-mix(in srgb, ${t.bgElev} 55%, transparent)`,
2397
+ backdropFilter: "blur(14px) saturate(140%)",
2398
+ WebkitBackdropFilter: "blur(14px) saturate(140%)",
2399
+ border: `1px solid color-mix(in srgb, ${t.border} 70%, transparent)`,
2400
+ borderRadius: 999,
2401
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.06)",
2402
+ fontFamily: t.fontSans
2403
+ },
2404
+ children: [
2405
+ /* @__PURE__ */ jsx4(
2406
+ "button",
2407
+ {
2408
+ onClick: () => step(-1),
2409
+ disabled: focusIndex <= 0 || stages.length === 0,
2410
+ style: btnStyle(t, false),
2411
+ title: "Previous step (\u2190)",
2412
+ children: "\u25C0"
2413
+ }
2414
+ ),
2415
+ /* @__PURE__ */ jsx4(
2416
+ "button",
2417
+ {
2418
+ onClick: () => step(1),
2419
+ disabled: focusIndex >= max || stages.length === 0,
2420
+ style: btnStyle(t, false),
2421
+ title: "Next step (\u2192)",
2422
+ children: "\u25B6"
2423
+ }
2424
+ ),
2425
+ /* @__PURE__ */ jsx4(
2426
+ "button",
2427
+ {
2428
+ onClick: () => onFocusChange(max),
2429
+ disabled: stages.length === 0 || isLive === true,
2430
+ style: btnStyle(t, isLive !== true && stages.length > 0),
2431
+ title: "Jump to latest step",
2432
+ children: "\u27F3 Live"
2433
+ }
2434
+ ),
2435
+ /* @__PURE__ */ jsx4(
2436
+ "input",
2437
+ {
2438
+ type: "range",
2439
+ min: 0,
2440
+ max,
2441
+ value: Math.min(focusIndex, max),
2442
+ onChange: (e) => onFocusChange(Number(e.target.value)),
2443
+ disabled: stages.length <= 1,
2444
+ style: { flex: 1, accentColor: t.accent, minWidth: 120 }
2445
+ }
2446
+ ),
2447
+ /* @__PURE__ */ jsx4(
2448
+ "div",
2449
+ {
2450
+ style: {
2451
+ fontSize: 11,
2452
+ color: t.textMuted,
2453
+ fontFamily: t.fontMono,
2454
+ whiteSpace: "nowrap",
2455
+ minWidth: 80,
2456
+ textAlign: "right"
2457
+ },
2458
+ children: stages.length === 0 ? "no steps yet" : `Step ${focusIndex + 1} / ${stages.length}`
2459
+ }
2460
+ )
2461
+ ]
2462
+ }
2463
+ );
2464
+ }
2465
+ function btnStyle(t, highlighted) {
2466
+ return {
2467
+ background: highlighted ? t.accent : `color-mix(in srgb, ${t.bg} 40%, transparent)`,
2468
+ color: highlighted ? "#fff" : t.textMuted,
2469
+ border: `1px solid color-mix(in srgb, ${t.border} 60%, transparent)`,
2470
+ borderRadius: 999,
2471
+ padding: "3px 12px",
2472
+ fontSize: 12,
2473
+ cursor: "pointer",
2474
+ width: "auto",
2475
+ fontWeight: 500,
2476
+ whiteSpace: "nowrap",
2477
+ transition: "background 140ms ease, border-color 140ms ease, color 140ms ease"
2478
+ };
2479
+ }
2480
+
2481
+ // src/react/panels/AskCard.tsx
2482
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2483
+ function AskCard({ timeline, focusIndex, stages }) {
2484
+ const t = useLensTheme();
2485
+ const currentStage = stages[focusIndex];
2486
+ const currentTurn = currentStage !== void 0 ? timeline.turns[currentStage.turnIndex] : timeline.turns[0];
2487
+ const currentIter = currentStage?.iterIndex !== void 0 ? currentTurn?.iterations.find((it) => it.index === currentStage.iterIndex) : void 0;
2488
+ const currentTool = currentStage?.toolName && currentIter ? currentIter.toolCalls.find((tc) => tc.name === currentStage.toolName) : void 0;
2489
+ return /* @__PURE__ */ jsxs5(
2490
+ "div",
2491
+ {
2492
+ "data-fp-lens": "ask-card",
2493
+ style: {
2494
+ display: "flex",
2495
+ flexDirection: "column",
2496
+ gap: 12,
2497
+ padding: 14,
2498
+ background: t.bg,
2499
+ fontFamily: t.fontSans,
2500
+ color: t.text,
2501
+ overflow: "auto"
2502
+ },
2503
+ children: [
2504
+ /* @__PURE__ */ jsxs5("section", { children: [
2505
+ /* @__PURE__ */ jsx5(Label3, { t, children: "Your question" }),
2506
+ /* @__PURE__ */ jsx5(
2507
+ "div",
2508
+ {
2509
+ style: {
2510
+ marginTop: 4,
2511
+ padding: "10px 12px",
2512
+ background: `color-mix(in srgb, ${t.accent} 12%, ${t.bgElev})`,
2513
+ border: `1px solid ${t.border}`,
2514
+ borderRadius: 6,
2515
+ fontSize: 13,
2516
+ lineHeight: 1.5
2517
+ },
2518
+ children: currentTurn?.userPrompt ?? "No question yet."
2519
+ }
2520
+ )
2521
+ ] }),
2522
+ currentStage && /* @__PURE__ */ jsxs5("section", { children: [
2523
+ /* @__PURE__ */ jsxs5(Label3, { t, children: [
2524
+ "Step ",
2525
+ focusIndex + 1,
2526
+ " / ",
2527
+ stages.length
2528
+ ] }),
2529
+ !(currentTool && currentStage.from === "tool") && /* @__PURE__ */ jsx5("div", { style: { marginTop: 4, fontSize: 13, color: t.text, lineHeight: 1.5 }, children: currentStage.label }),
2530
+ /* @__PURE__ */ jsxs5(
2531
+ "div",
2532
+ {
2533
+ style: {
2534
+ marginTop: 8,
2535
+ display: "flex",
2536
+ gap: 6,
2537
+ flexWrap: "wrap",
2538
+ fontSize: 10,
2539
+ color: t.textSubtle
2540
+ },
2541
+ children: [
2542
+ /* @__PURE__ */ jsx5(Pill, { t, children: primitivePill(currentStage) }),
2543
+ /* @__PURE__ */ jsxs5(Pill, { t, children: [
2544
+ friendlyNode(currentStage.from),
2545
+ " \u2192 ",
2546
+ friendlyNode(currentStage.to)
2547
+ ] }),
2548
+ currentStage.toolName && /* @__PURE__ */ jsx5(Pill, { t, children: currentStage.toolName })
2549
+ ]
2550
+ }
2551
+ )
2552
+ ] }),
2553
+ currentStage?.from === "agent" && currentIter?.assistantContent && /* @__PURE__ */ jsxs5("section", { children: [
2554
+ /* @__PURE__ */ jsx5(Label3, { t, children: "Agent reasoning" }),
2555
+ /* @__PURE__ */ jsx5(
2556
+ "div",
2557
+ {
2558
+ style: {
2559
+ marginTop: 4,
2560
+ padding: "10px 12px",
2561
+ background: t.bgElev,
2562
+ border: `1px solid ${t.border}`,
2563
+ borderLeft: `3px solid ${t.accent}`,
2564
+ borderRadius: 6,
2565
+ fontSize: 12,
2566
+ lineHeight: 1.55,
2567
+ whiteSpace: "pre-wrap",
2568
+ maxHeight: 220,
2569
+ overflow: "auto"
2570
+ },
2571
+ children: currentIter.assistantContent
2572
+ }
2573
+ )
2574
+ ] }),
2575
+ currentTool && /* @__PURE__ */ jsxs5("section", { children: [
2576
+ /* @__PURE__ */ jsx5(Label3, { t, children: currentStage?.to === "tool" ? "Arguments" : "Tool returned" }),
2577
+ /* @__PURE__ */ jsx5(
2578
+ "pre",
2579
+ {
2580
+ style: {
2581
+ marginTop: 4,
2582
+ padding: "8px 10px",
2583
+ background: t.bgElev,
2584
+ border: `1px solid ${t.border}`,
2585
+ borderRadius: 6,
2586
+ fontSize: 11,
2587
+ fontFamily: t.fontMono,
2588
+ color: currentTool.error ? t.error : t.text,
2589
+ whiteSpace: "pre-wrap",
2590
+ maxHeight: 180,
2591
+ overflow: "auto",
2592
+ margin: 0
2593
+ },
2594
+ children: currentStage?.to === "tool" ? JSON.stringify(currentTool.arguments, null, 2) : currentTool.result
2595
+ }
2596
+ )
2597
+ ] }),
2598
+ timeline.turns.length > 1 && /* @__PURE__ */ jsxs5("section", { children: [
2599
+ /* @__PURE__ */ jsx5(Label3, { t, children: "Conversation" }),
2600
+ /* @__PURE__ */ jsxs5("div", { style: { marginTop: 4, fontSize: 12, color: t.textMuted, lineHeight: 1.5 }, children: [
2601
+ timeline.turns.length,
2602
+ " question",
2603
+ timeline.turns.length === 1 ? "" : "s",
2604
+ " so far \xB7",
2605
+ " ",
2606
+ timeline.tools.length,
2607
+ " tool call",
2608
+ timeline.tools.length === 1 ? "" : "s",
2609
+ " total"
2610
+ ] })
2611
+ ] })
2612
+ ]
2613
+ }
2614
+ );
2615
+ }
2616
+ function Label3({
2617
+ t,
2618
+ children
2619
+ }) {
2620
+ return /* @__PURE__ */ jsx5(
2621
+ "div",
2622
+ {
2623
+ style: {
2624
+ fontSize: 10,
2625
+ color: t.textSubtle,
2626
+ textTransform: "uppercase",
2627
+ letterSpacing: "0.08em",
2628
+ fontWeight: 600
2629
+ },
2630
+ children
2631
+ }
2632
+ );
2633
+ }
2634
+ function Pill({
2635
+ t,
2636
+ children,
2637
+ warn
2638
+ }) {
2639
+ return /* @__PURE__ */ jsx5(
2640
+ "span",
2641
+ {
2642
+ style: {
2643
+ padding: "1px 6px",
2644
+ borderRadius: 3,
2645
+ background: warn ? `color-mix(in srgb, ${t.warning} 20%, transparent)` : t.bgElev,
2646
+ color: warn ? t.warning : t.textMuted,
2647
+ fontWeight: 600,
2648
+ textTransform: "uppercase",
2649
+ letterSpacing: "0.04em"
2650
+ },
2651
+ children
2652
+ }
2653
+ );
2654
+ }
2655
+ function friendlyNode(id) {
2656
+ if (!id) return id;
2657
+ return id.charAt(0).toUpperCase() + id.slice(1);
2658
+ }
2659
+ function primitivePill(stage) {
2660
+ if (stage.primitive === "system-prompt") return "System Prompt";
2661
+ if (stage.primitive === "message") return "Message";
2662
+ if (stage.toolKind === "skill") return "Tool (Skill)";
2663
+ if (stage.toolKind === "ask-human") return "Tool (Ask user)";
2664
+ return "Tool";
2665
+ }
2666
+
2667
+ // src/react/panels/RunSummary.tsx
2668
+ import { useState as useState3 } from "react";
2669
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2670
+ function RunSummary({ timeline }) {
2671
+ const t = useLensTheme();
2672
+ const [open, setOpen] = useState3(false);
2673
+ const completedTurns = timeline.turns.filter((turn) => turn.finalContent !== "");
2674
+ if (completedTurns.length === 0) return null;
2675
+ const toolCounts = /* @__PURE__ */ new Map();
2676
+ for (const tc of timeline.tools) {
2677
+ const prev = toolCounts.get(tc.name) ?? { count: 0, totalMs: 0 };
2678
+ toolCounts.set(tc.name, {
2679
+ count: prev.count + 1,
2680
+ totalMs: prev.totalMs + (tc.durationMs ?? 0)
2681
+ });
2682
+ }
2683
+ const toolList = [...toolCounts.entries()].sort((a, b) => b[1].count - a[1].count);
2684
+ const activatedSkills = /* @__PURE__ */ new Set();
2685
+ for (const tc of timeline.tools) {
2686
+ if (tc.name === "read_skill") {
2687
+ const id = tc.arguments?.id;
2688
+ if (typeof id === "string") activatedSkills.add(id);
2689
+ }
2690
+ }
2691
+ const totalIn = timeline.turns.reduce((s, turn) => s + turn.totalInputTokens, 0);
2692
+ const totalOut = timeline.turns.reduce((s, turn) => s + turn.totalOutputTokens, 0);
2693
+ const totalMs = timeline.turns.reduce((s, turn) => s + turn.totalDurationMs, 0);
2694
+ const totalIters = timeline.turns.reduce((s, turn) => s + turn.iterations.length, 0);
2695
+ return /* @__PURE__ */ jsxs6(
2696
+ "div",
2697
+ {
2698
+ "data-fp-lens": "run-summary",
2699
+ style: {
2700
+ borderTop: `1px solid ${t.border}`,
2701
+ background: t.bgElev,
2702
+ fontFamily: t.fontSans
2703
+ },
2704
+ children: [
2705
+ /* @__PURE__ */ jsxs6(
2706
+ "button",
2707
+ {
2708
+ onClick: () => setOpen((v) => !v),
2709
+ style: {
2710
+ display: "flex",
2711
+ alignItems: "center",
2712
+ gap: 8,
2713
+ width: "100%",
2714
+ padding: "8px 14px",
2715
+ background: "transparent",
2716
+ border: "none",
2717
+ color: t.textMuted,
2718
+ fontFamily: "inherit",
2719
+ fontSize: 12,
2720
+ cursor: "pointer",
2721
+ textAlign: "left",
2722
+ fontWeight: 400
2723
+ },
2724
+ children: [
2725
+ /* @__PURE__ */ jsx6("span", { style: { fontSize: 10 }, children: open ? "\u25BE" : "\u25B8" }),
2726
+ /* @__PURE__ */ jsx6(
2727
+ "span",
2728
+ {
2729
+ style: {
2730
+ fontSize: 10,
2731
+ color: t.textSubtle,
2732
+ textTransform: "uppercase",
2733
+ letterSpacing: "0.08em",
2734
+ fontWeight: 600
2735
+ },
2736
+ children: "Run summary"
2737
+ }
2738
+ ),
2739
+ /* @__PURE__ */ jsx6("span", { style: { flex: 1 } }),
2740
+ /* @__PURE__ */ jsxs6("span", { style: { fontSize: 11, color: t.textMuted }, children: [
2741
+ timeline.tools.length,
2742
+ " tool call",
2743
+ timeline.tools.length === 1 ? "" : "s",
2744
+ " \xB7",
2745
+ " ",
2746
+ activatedSkills.size,
2747
+ " skill",
2748
+ activatedSkills.size === 1 ? "" : "s",
2749
+ " \xB7",
2750
+ " ",
2751
+ totalIn.toLocaleString(),
2752
+ "\u2192",
2753
+ totalOut.toLocaleString(),
2754
+ " tok \xB7",
2755
+ " ",
2756
+ (totalMs / 1e3).toFixed(1),
2757
+ "s"
2758
+ ] })
2759
+ ]
2760
+ }
2761
+ ),
2762
+ open && /* @__PURE__ */ jsxs6(
2763
+ "div",
2764
+ {
2765
+ style: {
2766
+ padding: "6px 14px 14px",
2767
+ display: "grid",
2768
+ gridTemplateColumns: "1fr 1fr",
2769
+ gap: 14,
2770
+ fontSize: 12,
2771
+ color: t.text
2772
+ },
2773
+ children: [
2774
+ /* @__PURE__ */ jsxs6("section", { children: [
2775
+ /* @__PURE__ */ jsxs6(Label4, { t, children: [
2776
+ "Tools used \xB7 ",
2777
+ timeline.tools.length
2778
+ ] }),
2779
+ 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(
2780
+ "li",
2781
+ {
2782
+ style: {
2783
+ display: "flex",
2784
+ gap: 8,
2785
+ padding: "3px 0",
2786
+ fontFamily: t.fontMono,
2787
+ fontSize: 11
2788
+ },
2789
+ children: [
2790
+ /* @__PURE__ */ jsx6("span", { style: { color: t.accent, flex: 1 }, children: name }),
2791
+ /* @__PURE__ */ jsxs6("span", { style: { color: t.textMuted }, children: [
2792
+ "\xD7",
2793
+ stats.count
2794
+ ] }),
2795
+ /* @__PURE__ */ jsx6("span", { style: { color: t.textSubtle, minWidth: 60, textAlign: "right" }, children: stats.totalMs > 0 ? `${Math.round(stats.totalMs)}ms` : "\u2014" })
2796
+ ]
2797
+ },
2798
+ name
2799
+ )) })
2800
+ ] }),
2801
+ /* @__PURE__ */ jsxs6("section", { children: [
2802
+ /* @__PURE__ */ jsxs6(Label4, { t, children: [
2803
+ "Skills activated \xB7 ",
2804
+ activatedSkills.size
2805
+ ] }),
2806
+ 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(
2807
+ "li",
2808
+ {
2809
+ style: {
2810
+ padding: "3px 0",
2811
+ fontFamily: t.fontMono,
2812
+ fontSize: 11,
2813
+ color: t.text
2814
+ },
2815
+ children: id
2816
+ },
2817
+ id
2818
+ )) }),
2819
+ /* @__PURE__ */ jsxs6("div", { style: { marginTop: 10 }, children: [
2820
+ /* @__PURE__ */ jsx6(Label4, { t, children: "Totals" }),
2821
+ /* @__PURE__ */ jsxs6(
2822
+ "div",
2823
+ {
2824
+ style: {
2825
+ display: "grid",
2826
+ gridTemplateColumns: "auto 1fr",
2827
+ gap: "3px 10px",
2828
+ marginTop: 4,
2829
+ fontSize: 11,
2830
+ color: t.textMuted
2831
+ },
2832
+ children: [
2833
+ /* @__PURE__ */ jsx6("span", { children: "Tokens" }),
2834
+ /* @__PURE__ */ jsxs6("span", { style: { color: t.text, fontFamily: t.fontMono }, children: [
2835
+ totalIn.toLocaleString(),
2836
+ " \u2192 ",
2837
+ totalOut.toLocaleString()
2838
+ ] }),
2839
+ /* @__PURE__ */ jsx6("span", { children: "Wall time" }),
2840
+ /* @__PURE__ */ jsxs6("span", { style: { color: t.text, fontFamily: t.fontMono }, children: [
2841
+ (totalMs / 1e3).toFixed(2),
2842
+ "s"
2843
+ ] }),
2844
+ /* @__PURE__ */ jsx6("span", { children: "LLM calls" }),
2845
+ /* @__PURE__ */ jsx6("span", { style: { color: t.text, fontFamily: t.fontMono }, children: totalIters }),
2846
+ /* @__PURE__ */ jsx6("span", { children: "Turns" }),
2847
+ /* @__PURE__ */ jsx6("span", { style: { color: t.text, fontFamily: t.fontMono }, children: completedTurns.length })
2848
+ ]
2849
+ }
2850
+ )
2851
+ ] })
2852
+ ] })
2853
+ ]
2854
+ }
2855
+ )
2856
+ ]
2857
+ }
2858
+ );
2859
+ }
2860
+ function Label4({
2861
+ t,
2862
+ children
2863
+ }) {
2864
+ return /* @__PURE__ */ jsx6(
2865
+ "div",
2866
+ {
2867
+ style: {
2868
+ fontSize: 10,
2869
+ color: t.textSubtle,
2870
+ textTransform: "uppercase",
2871
+ letterSpacing: "0.08em",
2872
+ fontWeight: 600,
2873
+ marginBottom: 4
2874
+ },
2875
+ children
2876
+ }
2877
+ );
2878
+ }
2879
+
2880
+ // src/react/AgentLens.tsx
2881
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2882
+ function AgentLens({
2883
+ runtimeSnapshot,
2884
+ timeline: providedTimeline,
2885
+ systemPrompt,
2886
+ onToolCallClick,
2887
+ skills,
2888
+ activeSkillId
2889
+ }) {
2890
+ const t = useLensTheme();
2891
+ const timeline = useMemo2(() => {
2892
+ if (providedTimeline) return providedTimeline;
2893
+ if (!runtimeSnapshot) return null;
2894
+ return fromAgentSnapshot(runtimeSnapshot);
2895
+ }, [providedTimeline, runtimeSnapshot]);
2896
+ const [selectedIterKey, setSelectedIterKey] = useState4(null);
2897
+ const [skillsOpen, setSkillsOpen] = useState4(false);
2898
+ const derivedActiveSkill = activeSkillId ?? (typeof timeline?.finalDecision?.currentSkill === "string" ? timeline.finalDecision.currentSkill : null);
2899
+ useEffect2(() => {
2900
+ if (!skillsOpen) return;
2901
+ const onKey = (e) => {
2902
+ if (e.key === "Escape") setSkillsOpen(false);
2903
+ };
2904
+ window.addEventListener("keydown", onKey);
2905
+ return () => window.removeEventListener("keydown", onKey);
2906
+ }, [skillsOpen]);
2907
+ const handleToolClick = useCallback(
2908
+ (inv) => {
2909
+ setSelectedIterKey(`${inv.turnIndex}.${inv.iterationIndex}`);
2910
+ onToolCallClick?.(inv);
2911
+ },
2912
+ [onToolCallClick]
2913
+ );
2914
+ if (!timeline) {
2915
+ return /* @__PURE__ */ jsxs7(
2916
+ "div",
2917
+ {
2918
+ "data-fp-lens": "empty",
2919
+ style: {
2920
+ padding: 32,
2921
+ color: t.textMuted,
2922
+ fontFamily: t.fontSans,
2923
+ textAlign: "center",
2924
+ background: t.bg
2925
+ },
2926
+ children: [
2927
+ "No agent run to show yet. Pass ",
2928
+ /* @__PURE__ */ jsx7("code", { children: "runtimeSnapshot" }),
2929
+ " after the agent runs."
2930
+ ]
2931
+ }
2932
+ );
2933
+ }
2934
+ const derivedSystemPrompt = systemPrompt ?? (typeof timeline.rawSnapshot?.sharedState?.systemPrompt === "string" ? timeline.rawSnapshot.sharedState.systemPrompt : void 0);
2935
+ const stages = useMemo2(() => timeline ? deriveStages(timeline) : [], [timeline]);
2936
+ const [focusIndex, setFocusIndex] = useState4(-1);
2937
+ const liveIndex = stages.length - 1;
2938
+ const resolvedFocus = focusIndex === -1 ? liveIndex : Math.min(focusIndex, liveIndex);
2939
+ const isLive = focusIndex === -1;
2940
+ const liveIndexRef = useRef2(liveIndex);
2941
+ liveIndexRef.current = liveIndex;
2942
+ const handleFocusChange = useCallback((i) => {
2943
+ setFocusIndex(i >= liveIndexRef.current ? -1 : i);
2944
+ }, []);
2945
+ useEffect2(() => {
2946
+ if (stages.length === 0) return;
2947
+ const onKey = (e) => {
2948
+ if (skillsOpen) return;
2949
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
2950
+ const tgt = e.target;
2951
+ const tag = tgt?.tagName;
2952
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || tgt?.isContentEditable) return;
2953
+ const delta = e.key === "ArrowLeft" ? -1 : 1;
2954
+ const next = Math.min(
2955
+ liveIndex,
2956
+ Math.max(0, resolvedFocus + delta)
2957
+ );
2958
+ handleFocusChange(next);
2959
+ e.preventDefault();
2960
+ };
2961
+ window.addEventListener("keydown", onKey);
2962
+ return () => window.removeEventListener("keydown", onKey);
2963
+ }, [resolvedFocus, liveIndex, stages.length, skillsOpen]);
2964
+ const handleEdgeClick = useCallback(
2965
+ (stage) => {
2966
+ if (stage.iterIndex === void 0) return;
2967
+ setSelectedIterKey(`${stage.turnIndex}.${stage.iterIndex}`);
2968
+ handleFocusChange(stage.index);
2969
+ },
2970
+ [handleFocusChange]
2971
+ );
2972
+ return /* @__PURE__ */ jsxs7(
2973
+ "div",
2974
+ {
2975
+ "data-fp-lens": "shell",
2976
+ style: {
2977
+ display: "grid",
2978
+ // Top: TimeTravel slider (tt) + Skills button trailing on the
2979
+ // right. Middle-left: vertical StageFlow. Middle-right: AskCard
2980
+ // with current question + active step detail. Below: MessagesPanel
2981
+ // full width. Footer: RunSummary (only after first turn completes).
2982
+ gridTemplateColumns: "minmax(320px, 1fr) minmax(220px, 320px)",
2983
+ gridTemplateRows: "auto auto minmax(0, 1fr) auto",
2984
+ gridTemplateAreas: '"tt tt" "graph ask" "messages messages" "summary summary"',
2985
+ // Self-constraining sizing contract — Lens never requires the
2986
+ // host to get their flex chain exactly right.
2987
+ //
2988
+ // • `maxHeight: 100dvh` is the hard cap. Even if NOTHING
2989
+ // upstream delivers a bounded height, the shell can never
2990
+ // exceed the viewport. This is what makes the grid's
2991
+ // `minmax(0, 1fr)` messages row actually resolve to a
2992
+ // bounded number of pixels instead of expanding to content.
2993
+ // • `height: 100%` + `flex: 1 1 0%` let well-constrained
2994
+ // hosts still shrink Lens below the viewport cap when they
2995
+ // want a narrower sidebar.
2996
+ // • `overflow: hidden` clips children that would otherwise
2997
+ // blow past the grid and make scrollHeight == clientHeight
2998
+ // on MessagesPanel (the exact bug this block prevents).
2999
+ // • `minHeight: 0` unblocks grid + flex size resolution.
3000
+ flex: "1 1 0%",
3001
+ minHeight: 0,
3002
+ height: "100%",
3003
+ maxHeight: "100dvh",
3004
+ overflow: "hidden",
3005
+ background: t.bg,
3006
+ color: t.text,
3007
+ fontFamily: t.fontSans
3008
+ },
3009
+ children: [
3010
+ /* @__PURE__ */ jsx7("style", { children: `
3011
+ [data-iter-selected="true"] {
3012
+ outline-color: currentColor !important;
3013
+ background: color-mix(in srgb, currentColor 8%, transparent);
3014
+ }
3015
+ ` }),
3016
+ stages.length > 0 && /* @__PURE__ */ jsxs7(Fragment4, { children: [
3017
+ /* @__PURE__ */ jsxs7(
3018
+ "div",
3019
+ {
3020
+ style: {
3021
+ gridArea: "tt",
3022
+ display: "flex",
3023
+ alignItems: "center"
3024
+ },
3025
+ children: [
3026
+ /* @__PURE__ */ jsx7("div", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx7(
3027
+ TimeTravel,
3028
+ {
3029
+ stages,
3030
+ focusIndex: resolvedFocus,
3031
+ onFocusChange: handleFocusChange,
3032
+ isLive
3033
+ }
3034
+ ) }),
3035
+ skills && skills.length > 0 && /* @__PURE__ */ jsxs7(
3036
+ "button",
3037
+ {
3038
+ onClick: () => setSkillsOpen(true),
3039
+ title: "See all skills registered with the agent",
3040
+ style: {
3041
+ // Borderless — rides on the same airy feel as the
3042
+ // floating time-travel pill. A faint translucent fill
3043
+ // on hover keeps it discoverable.
3044
+ background: "transparent",
3045
+ border: "none",
3046
+ color: t.textMuted,
3047
+ padding: "0 16px",
3048
+ margin: "0 14px 0 0",
3049
+ fontSize: 12,
3050
+ cursor: "pointer",
3051
+ whiteSpace: "nowrap",
3052
+ display: "flex",
3053
+ alignItems: "center",
3054
+ gap: 6,
3055
+ fontWeight: 400,
3056
+ width: "auto"
3057
+ },
3058
+ children: [
3059
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\u{1F4DA}" }),
3060
+ /* @__PURE__ */ jsxs7("span", { children: [
3061
+ "Skills \xB7 ",
3062
+ skills.length
3063
+ ] }),
3064
+ derivedActiveSkill && /* @__PURE__ */ jsx7(
3065
+ "span",
3066
+ {
3067
+ style: {
3068
+ fontSize: 9,
3069
+ padding: "1px 5px",
3070
+ borderRadius: 3,
3071
+ background: `color-mix(in srgb, ${t.success} 25%, transparent)`,
3072
+ color: t.success,
3073
+ fontWeight: 600,
3074
+ textTransform: "uppercase"
3075
+ },
3076
+ children: derivedActiveSkill
3077
+ }
3078
+ )
3079
+ ]
3080
+ }
3081
+ )
3082
+ ]
3083
+ }
3084
+ ),
3085
+ /* @__PURE__ */ jsx7(
3086
+ "div",
3087
+ {
3088
+ style: {
3089
+ gridArea: "graph",
3090
+ minHeight: 0,
3091
+ overflow: "hidden",
3092
+ borderRight: `1px solid ${t.border}`
3093
+ },
3094
+ children: /* @__PURE__ */ jsx7(
3095
+ StageFlow,
3096
+ {
3097
+ stages,
3098
+ focusIndex: resolvedFocus,
3099
+ onEdgeClick: handleEdgeClick,
3100
+ activeSkillId: derivedActiveSkill
3101
+ }
3102
+ )
3103
+ }
3104
+ ),
3105
+ /* @__PURE__ */ jsx7(
3106
+ "div",
3107
+ {
3108
+ style: {
3109
+ gridArea: "ask",
3110
+ minHeight: 0,
3111
+ overflow: "hidden",
3112
+ background: t.bg
3113
+ },
3114
+ children: /* @__PURE__ */ jsx7(
3115
+ AskCard,
3116
+ {
3117
+ timeline,
3118
+ stages,
3119
+ focusIndex: resolvedFocus
3120
+ }
3121
+ )
3122
+ }
3123
+ )
3124
+ ] }),
3125
+ /* @__PURE__ */ jsx7(
3126
+ "div",
3127
+ {
3128
+ style: {
3129
+ gridArea: "messages",
3130
+ minHeight: 0,
3131
+ overflow: "hidden",
3132
+ // `position: relative` is the containing block for the
3133
+ // absolutely-positioned MessagesPanel inside. This is the
3134
+ // mechanism that forces the panel's height to match this
3135
+ // cell exactly, no matter what the upstream flex/grid
3136
+ // chain does.
3137
+ position: "relative"
3138
+ },
3139
+ children: /* @__PURE__ */ jsx7(
3140
+ MessagesPanel,
3141
+ {
3142
+ timeline,
3143
+ onToolCallClick: handleToolClick,
3144
+ selectedIterKey,
3145
+ stages,
3146
+ focusIndex: resolvedFocus,
3147
+ onFocusChange: handleFocusChange,
3148
+ isLive,
3149
+ ...derivedSystemPrompt && { systemPrompt: derivedSystemPrompt }
3150
+ }
3151
+ )
3152
+ }
3153
+ ),
3154
+ /* @__PURE__ */ jsx7("div", { style: { gridArea: "summary" }, children: /* @__PURE__ */ jsx7(RunSummary, { timeline }) }),
3155
+ skillsOpen && skills && /* @__PURE__ */ jsx7(
3156
+ SkillsPanel,
3157
+ {
3158
+ skills,
3159
+ activeSkillId: derivedActiveSkill,
3160
+ onClose: () => setSkillsOpen(false)
3161
+ }
3162
+ )
3163
+ ]
3164
+ }
3165
+ );
3166
+ }
3167
+
3168
+ // src/react/components/Tabs/Tabs.tsx
3169
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState5 } from "react";
3170
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3171
+ function Tabs({
3172
+ tabs,
3173
+ selectedTabId,
3174
+ defaultTabId,
3175
+ onChange,
3176
+ renderAll = false,
3177
+ leadingSlot,
3178
+ trailingSlot,
3179
+ className
3180
+ }) {
3181
+ const initialId = defaultTabId ?? tabs[0]?.id;
3182
+ const [internalId, setInternalId] = useState5(initialId);
3183
+ const isControlled = selectedTabId !== void 0;
3184
+ const selectedId = isControlled ? selectedTabId : internalId;
3185
+ const pickTab = useCallback2(
3186
+ (id) => {
3187
+ const def = tabs.find((t) => t.id === id);
3188
+ if (!def || def.disabled) return;
3189
+ if (!isControlled) setInternalId(id);
3190
+ onChange?.(id);
3191
+ },
3192
+ [tabs, isControlled, onChange]
3193
+ );
3194
+ const handleKey = useCallback2(
3195
+ (e) => {
3196
+ if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
3197
+ e.preventDefault();
3198
+ const enabled = tabs.filter((t) => !t.disabled);
3199
+ if (enabled.length === 0) return;
3200
+ const currentIdx = enabled.findIndex((t) => t.id === selectedId);
3201
+ let nextIdx = currentIdx;
3202
+ if (e.key === "ArrowLeft") {
3203
+ nextIdx = (currentIdx - 1 + enabled.length) % enabled.length;
3204
+ } else if (e.key === "ArrowRight") {
3205
+ nextIdx = (currentIdx + 1) % enabled.length;
3206
+ } else if (e.key === "Home") {
3207
+ nextIdx = 0;
3208
+ } else if (e.key === "End") {
3209
+ nextIdx = enabled.length - 1;
3210
+ }
3211
+ pickTab(enabled[nextIdx].id);
3212
+ },
3213
+ [tabs, selectedId, pickTab]
3214
+ );
3215
+ const selectedDef = tabs.find((t) => t.id === selectedId);
3216
+ return /* @__PURE__ */ jsxs8(
3217
+ "div",
3218
+ {
3219
+ "data-fp-lens": "tabs-root",
3220
+ className,
3221
+ style: {
3222
+ display: "flex",
3223
+ flexDirection: "column",
3224
+ height: "100%",
3225
+ minHeight: 0
3226
+ },
3227
+ children: [
3228
+ /* @__PURE__ */ jsx8(
3229
+ TabStrip,
3230
+ {
3231
+ tabs,
3232
+ selectedId,
3233
+ onPick: pickTab,
3234
+ onKeyDown: handleKey,
3235
+ leadingSlot,
3236
+ trailingSlot
3237
+ }
3238
+ ),
3239
+ /* @__PURE__ */ jsx8(TabContent, { tabs, selectedId, renderAll }),
3240
+ !selectedDef && renderAll === false && /* @__PURE__ */ jsx8(
3241
+ "div",
3242
+ {
3243
+ "data-fp-lens": "tabs-empty",
3244
+ style: { flex: 1, minHeight: 0 },
3245
+ "aria-live": "polite"
3246
+ }
3247
+ )
3248
+ ]
3249
+ }
3250
+ );
3251
+ }
3252
+ function TabStrip({
3253
+ tabs,
3254
+ selectedId,
3255
+ onPick,
3256
+ onKeyDown,
3257
+ leadingSlot,
3258
+ trailingSlot
3259
+ }) {
3260
+ const t = useLensTheme();
3261
+ return /* @__PURE__ */ jsxs8(
3262
+ "div",
3263
+ {
3264
+ role: "tablist",
3265
+ onKeyDown,
3266
+ "data-fp-lens": "tabs-strip",
3267
+ style: {
3268
+ display: "flex",
3269
+ padding: "8px 14px 0",
3270
+ // Strip is rendered a shade DARKER than the content/active-tab
3271
+ // surface. Combined with the active-tab's inherits-from-content
3272
+ // background below, this yields the Claude Design Studio
3273
+ // "tab-into-body" merge: active tab disappears into the panel,
3274
+ // inactive tabs float on a darker outer band. The 14% mix is
3275
+ // subtle on light themes (white → near-white), pronounced
3276
+ // enough on dark themes (slate → near-black) to differentiate.
3277
+ background: `color-mix(in srgb, ${t.bg} 86%, black 14%)`,
3278
+ flexShrink: 0,
3279
+ alignItems: "flex-end",
3280
+ position: "relative",
3281
+ zIndex: 1,
3282
+ fontFamily: t.fontSans
3283
+ },
3284
+ children: [
3285
+ leadingSlot && /* @__PURE__ */ jsx8(
3286
+ "div",
3287
+ {
3288
+ style: {
3289
+ display: "flex",
3290
+ alignItems: "center",
3291
+ marginRight: 12,
3292
+ paddingBottom: 10
3293
+ },
3294
+ "data-fp-lens": "tabs-leading",
3295
+ children: leadingSlot
3296
+ }
3297
+ ),
3298
+ /* @__PURE__ */ jsx8(
3299
+ "div",
3300
+ {
3301
+ style: {
3302
+ display: "flex",
3303
+ gap: 2,
3304
+ alignItems: "flex-end",
3305
+ flex: 1,
3306
+ minWidth: 0
3307
+ },
3308
+ children: tabs.map((def) => /* @__PURE__ */ jsx8(
3309
+ TabButton,
3310
+ {
3311
+ def,
3312
+ selected: def.id === selectedId,
3313
+ onPick
3314
+ },
3315
+ def.id
3316
+ ))
3317
+ }
3318
+ ),
3319
+ trailingSlot && /* @__PURE__ */ jsx8("div", { style: { display: "flex", alignItems: "center" }, children: trailingSlot })
3320
+ ]
3321
+ }
3322
+ );
3323
+ }
3324
+ function TabButton({
3325
+ def,
3326
+ selected,
3327
+ onPick
3328
+ }) {
3329
+ const t = useLensTheme();
3330
+ const buttonRef = useRef3(null);
3331
+ useEffect3(() => {
3332
+ if (selected && document.activeElement?.getAttribute("role") === "tab") {
3333
+ buttonRef.current?.focus();
3334
+ }
3335
+ }, [selected]);
3336
+ return /* @__PURE__ */ jsxs8(
3337
+ "button",
3338
+ {
3339
+ ref: buttonRef,
3340
+ role: "tab",
3341
+ "aria-selected": selected,
3342
+ "aria-disabled": def.disabled,
3343
+ tabIndex: selected ? 0 : -1,
3344
+ disabled: def.disabled,
3345
+ title: def.title,
3346
+ onClick: () => onPick(def.id),
3347
+ "data-fp-lens-tab": def.id,
3348
+ "data-fp-lens-selected": selected,
3349
+ style: {
3350
+ padding: "9px 16px 10px",
3351
+ cursor: def.disabled ? "not-allowed" : "pointer",
3352
+ fontSize: 13,
3353
+ fontFamily: "inherit",
3354
+ width: "auto",
3355
+ display: "flex",
3356
+ alignItems: "center",
3357
+ gap: 6,
3358
+ color: selected ? t.text : t.textMuted,
3359
+ // Active tab paints the SAME color as the content panel below
3360
+ // (and the same color AgentLens's shell paints inside it), so
3361
+ // tab + content merge into one continuous surface.
3362
+ background: selected ? t.bg : "transparent",
3363
+ border: `1px solid ${selected ? t.border : "transparent"}`,
3364
+ borderBottom: "none",
3365
+ borderRadius: "10px 10px 0 0",
3366
+ marginBottom: -1,
3367
+ fontWeight: selected ? 600 : 500,
3368
+ opacity: def.disabled ? 0.45 : 1,
3369
+ transition: "background 140ms ease, color 140ms ease, border-color 140ms ease"
3370
+ },
3371
+ children: [
3372
+ /* @__PURE__ */ jsx8("span", { children: def.label }),
3373
+ def.trailing
3374
+ ]
3375
+ }
3376
+ );
3377
+ }
3378
+ function TabContent({
3379
+ tabs,
3380
+ selectedId,
3381
+ renderAll
3382
+ }) {
3383
+ const t = useLensTheme();
3384
+ const bg = t.bg;
3385
+ if (renderAll) {
3386
+ return /* @__PURE__ */ jsx8(
3387
+ "div",
3388
+ {
3389
+ style: {
3390
+ flex: 1,
3391
+ minHeight: 0,
3392
+ position: "relative",
3393
+ background: bg
3394
+ },
3395
+ "data-fp-lens": "tabs-content",
3396
+ children: tabs.map((def) => /* @__PURE__ */ jsx8(
3397
+ "div",
3398
+ {
3399
+ role: "tabpanel",
3400
+ "aria-hidden": def.id !== selectedId,
3401
+ style: {
3402
+ position: "absolute",
3403
+ inset: 0,
3404
+ visibility: def.id === selectedId ? "visible" : "hidden",
3405
+ pointerEvents: def.id === selectedId ? "auto" : "none",
3406
+ display: "flex",
3407
+ flexDirection: "column",
3408
+ minHeight: 0
3409
+ },
3410
+ children: def.content
3411
+ },
3412
+ def.id
3413
+ ))
3414
+ }
3415
+ );
3416
+ }
3417
+ const selectedDef = tabs.find((t2) => t2.id === selectedId);
3418
+ if (!selectedDef) return null;
3419
+ return /* @__PURE__ */ jsx8(
3420
+ "div",
3421
+ {
3422
+ role: "tabpanel",
3423
+ "data-fp-lens": "tabs-content",
3424
+ style: {
3425
+ flex: 1,
3426
+ minHeight: 0,
3427
+ display: "flex",
3428
+ flexDirection: "column",
3429
+ background: bg
3430
+ },
3431
+ children: selectedDef.content
3432
+ }
3433
+ );
3434
+ }
3435
+
3436
+ // src/react/Lens.tsx
3437
+ import { jsx as jsx9 } from "react/jsx-runtime";
3438
+ function Lens({
3439
+ runtimeSnapshot,
3440
+ timeline,
3441
+ appName,
3442
+ systemPrompt,
3443
+ skills,
3444
+ activeSkillId,
3445
+ onToolCallClick,
3446
+ defaultTabId = "lens",
3447
+ trailingSlot,
3448
+ renderAll = false,
3449
+ traceView,
3450
+ theme
3451
+ }) {
3452
+ const t = useLensTheme();
3453
+ const [selectedId, setSelectedId] = useState6(defaultTabId);
3454
+ const agentLensProps = {
3455
+ runtimeSnapshot,
3456
+ ...timeline !== void 0 ? { timeline } : {},
3457
+ ...systemPrompt !== void 0 ? { systemPrompt } : {},
3458
+ ...skills !== void 0 ? { skills } : {},
3459
+ ...activeSkillId !== void 0 ? { activeSkillId } : {},
3460
+ ...onToolCallClick !== void 0 ? { onToolCallClick } : {}
3461
+ };
3462
+ const brand = appName ? /* @__PURE__ */ jsx9(
3463
+ "span",
3464
+ {
3465
+ "data-fp-lens": "brand",
3466
+ style: {
3467
+ fontSize: 13,
3468
+ fontWeight: 600,
3469
+ color: t.text,
3470
+ fontFamily: t.fontSans,
3471
+ letterSpacing: "0.01em",
3472
+ whiteSpace: "nowrap"
3473
+ },
3474
+ title: `Lens \xB7 ${appName}`,
3475
+ children: appName
3476
+ }
3477
+ ) : null;
3478
+ const shell = /* @__PURE__ */ jsx9(
3479
+ Tabs,
3480
+ {
3481
+ tabs: [
3482
+ {
3483
+ id: "lens",
3484
+ label: "Lens",
3485
+ content: /* @__PURE__ */ jsx9(AgentLens, { ...agentLensProps })
3486
+ },
3487
+ {
3488
+ id: "trace",
3489
+ label: "Explainable Trace",
3490
+ // Consumer-supplied override wins; otherwise render a
3491
+ // default `<ExplainableShell>` from the same runtime
3492
+ // snapshot so consumers get something useful out of the
3493
+ // box without wiring anything.
3494
+ content: traceView ?? /* @__PURE__ */ jsx9(ExplainableShell, { runtimeSnapshot: runtimeSnapshot ?? null })
3495
+ }
3496
+ ],
3497
+ selectedTabId: selectedId,
3498
+ onChange: setSelectedId,
3499
+ renderAll,
3500
+ ...brand !== null ? { leadingSlot: brand } : {},
3501
+ ...trailingSlot !== void 0 ? { trailingSlot } : {}
3502
+ }
3503
+ );
3504
+ return theme ? /* @__PURE__ */ jsx9(FootprintTheme, { tokens: theme, children: shell }) : shell;
3505
+ }
3506
+
3507
+ // src/react/panels/IterationStrip.tsx
3508
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3509
+ function IterationStrip({ timeline, selectedKey, onSelect }) {
3510
+ const t = useLensTheme();
3511
+ const chips = timeline.turns.flatMap(
3512
+ (turn) => turn.iterations.map((iter, stepIdx) => ({
3513
+ key: `${turn.index}.${iter.index}`,
3514
+ turn: turn.index + 1,
3515
+ step: stepIdx + 1,
3516
+ label: stepHeadline(iter),
3517
+ durationMs: iter.durationMs ?? 0,
3518
+ stopReason: iter.stopReason,
3519
+ toolCount: iter.toolCalls.length
3520
+ }))
3521
+ );
3522
+ return /* @__PURE__ */ jsxs9(
3523
+ "div",
3524
+ {
3525
+ "data-fp-lens": "iteration-strip",
3526
+ style: {
3527
+ display: "flex",
3528
+ gap: 4,
3529
+ padding: "8px 12px",
3530
+ overflowX: "auto",
3531
+ borderBottom: `1px solid ${t.border}`,
3532
+ background: t.bgElev
3533
+ },
3534
+ children: [
3535
+ chips.length === 0 && /* @__PURE__ */ jsx10("span", { style: { color: t.textSubtle, fontSize: 11 }, children: "No iterations yet." }),
3536
+ chips.map((c) => {
3537
+ const active = c.key === selectedKey;
3538
+ const isFinal = c.toolCount === 0;
3539
+ const secs = c.durationMs >= 1e3 ? `${(c.durationMs / 1e3).toFixed(1)}s` : `${Math.round(c.durationMs)}ms`;
3540
+ return /* @__PURE__ */ jsxs9(
3541
+ "button",
3542
+ {
3543
+ onClick: () => onSelect?.(c.key),
3544
+ style: {
3545
+ background: active ? t.accent : "transparent",
3546
+ color: active ? "#fff" : t.textMuted,
3547
+ border: `1px solid ${active ? t.accent : t.border}`,
3548
+ borderRadius: 4,
3549
+ padding: "4px 10px",
3550
+ fontSize: 11,
3551
+ fontFamily: t.fontSans,
3552
+ cursor: "pointer",
3553
+ whiteSpace: "nowrap",
3554
+ flexShrink: 0,
3555
+ maxWidth: 280,
3556
+ overflow: "hidden",
3557
+ textOverflow: "ellipsis"
3558
+ },
3559
+ title: `Question ${c.turn} \xB7 Step ${c.step} \xB7 ${secs}${c.stopReason ? ` \xB7 stop: ${c.stopReason}` : ""}`,
3560
+ children: [
3561
+ /* @__PURE__ */ jsxs9("span", { style: { fontWeight: 600 }, children: [
3562
+ "Step ",
3563
+ c.step
3564
+ ] }),
3565
+ /* @__PURE__ */ jsxs9("span", { style: { opacity: 0.85 }, children: [
3566
+ " \xB7 ",
3567
+ c.label
3568
+ ] }),
3569
+ isFinal && /* @__PURE__ */ jsx10("span", { style: { marginLeft: 6 }, children: "\u2713" })
3570
+ ]
3571
+ },
3572
+ c.key
3573
+ );
3574
+ })
3575
+ ]
3576
+ }
3577
+ );
3578
+ }
3579
+ function stepHeadline(iter) {
3580
+ if (iter.toolCalls.length === 0) return "Ready to answer";
3581
+ if (iter.toolCalls.length === 1) {
3582
+ const tc = iter.toolCalls[0];
3583
+ if (tc.name === "list_skills") return "Looking up skills";
3584
+ if (tc.name === "read_skill") {
3585
+ const id = tc.arguments?.id;
3586
+ return id ? `Activated ${id}` : "Activating skill";
3587
+ }
3588
+ if (tc.name === "ask_human" || tc.name === "ask_user") {
3589
+ return "Asked user";
3590
+ }
3591
+ return `Called tool (${tc.name})`;
3592
+ }
3593
+ if (iter.toolCalls.length <= 3) {
3594
+ return `Called ${iter.toolCalls.length} tools (${iter.toolCalls.map((tc) => tc.name).join(", ")})`;
3595
+ }
3596
+ return `Called ${iter.toolCalls.length} tools in parallel`;
3597
+ }
3598
+
3599
+ // src/react/panels/ToolCallInspector.tsx
3600
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3601
+ function ToolCallInspector({
3602
+ timeline,
3603
+ selectedId,
3604
+ onSelect
3605
+ }) {
3606
+ const t = useLensTheme();
3607
+ return /* @__PURE__ */ jsxs10(
3608
+ "div",
3609
+ {
3610
+ "data-fp-lens": "tool-call-inspector",
3611
+ style: {
3612
+ background: t.bg,
3613
+ color: t.text,
3614
+ fontFamily: t.fontSans,
3615
+ display: "flex",
3616
+ flexDirection: "column",
3617
+ minHeight: 0,
3618
+ overflow: "hidden"
3619
+ },
3620
+ children: [
3621
+ /* @__PURE__ */ jsxs10(
3622
+ "div",
3623
+ {
3624
+ style: {
3625
+ padding: "10px 14px",
3626
+ borderBottom: `1px solid ${t.border}`,
3627
+ fontSize: 11,
3628
+ color: t.textSubtle,
3629
+ textTransform: "uppercase",
3630
+ letterSpacing: "0.08em",
3631
+ fontWeight: 600,
3632
+ background: t.bgElev
3633
+ },
3634
+ children: [
3635
+ "Every tool Neo called \xB7 ",
3636
+ timeline.tools.length
3637
+ ]
3638
+ }
3639
+ ),
3640
+ /* @__PURE__ */ jsxs10("div", { style: { overflow: "auto", flex: 1 }, children: [
3641
+ timeline.tools.length === 0 && /* @__PURE__ */ jsx11("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "Neo hasn't called any tools yet." }),
3642
+ timeline.tools.map((tc) => {
3643
+ const active = tc.id === selectedId;
3644
+ const errored = tc.error === true;
3645
+ return /* @__PURE__ */ jsxs10(
3646
+ "button",
3647
+ {
3648
+ onClick: () => onSelect?.(tc),
3649
+ style: {
3650
+ display: "flex",
3651
+ flexDirection: "column",
3652
+ alignItems: "stretch",
3653
+ width: "100%",
3654
+ textAlign: "left",
3655
+ padding: "8px 12px",
3656
+ background: active ? t.bgHover : "transparent",
3657
+ border: "none",
3658
+ borderLeft: `3px solid ${active ? t.accent : errored ? t.error : "transparent"}`,
3659
+ borderBottom: `1px solid ${t.border}`,
3660
+ color: t.text,
3661
+ cursor: "pointer",
3662
+ fontFamily: "inherit"
3663
+ },
3664
+ children: [
3665
+ /* @__PURE__ */ jsxs10(
3666
+ "div",
3667
+ {
3668
+ style: {
3669
+ display: "flex",
3670
+ gap: 8,
3671
+ alignItems: "baseline",
3672
+ fontSize: 12,
3673
+ fontFamily: t.fontMono
3674
+ },
3675
+ children: [
3676
+ /* @__PURE__ */ jsx11("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: tc.name }),
3677
+ /* @__PURE__ */ jsxs10(
3678
+ "span",
3679
+ {
3680
+ style: { color: t.textSubtle, fontSize: 10, marginLeft: "auto" },
3681
+ children: [
3682
+ "t",
3683
+ tc.turnIndex + 1,
3684
+ ".i",
3685
+ tc.iterationIndex,
3686
+ tc.durationMs !== void 0 && ` \xB7 ${Math.round(tc.durationMs)}ms`
3687
+ ]
3688
+ }
3689
+ )
3690
+ ]
3691
+ }
3692
+ ),
3693
+ /* @__PURE__ */ jsx11(
3694
+ "div",
3695
+ {
3696
+ style: {
3697
+ fontSize: 10,
3698
+ color: t.textMuted,
3699
+ marginTop: 2,
3700
+ fontFamily: t.fontMono,
3701
+ overflow: "hidden",
3702
+ textOverflow: "ellipsis",
3703
+ whiteSpace: "nowrap"
3704
+ },
3705
+ children: shortArgs2(tc.arguments)
3706
+ }
3707
+ )
3708
+ ]
3709
+ },
3710
+ tc.id
3711
+ );
3712
+ })
3713
+ ] })
3714
+ ]
3715
+ }
3716
+ );
3717
+ }
3718
+ function shortArgs2(args) {
3719
+ const keys = Object.keys(args);
3720
+ if (keys.length === 0) return "\u2014 no args \u2014";
3721
+ return keys.map((k) => {
3722
+ const v = args[k];
3723
+ if (typeof v === "string") return `${k}: "${v.length > 20 ? v.slice(0, 20) + "\u2026" : v}"`;
3724
+ if (typeof v === "number" || typeof v === "boolean") return `${k}: ${v}`;
3725
+ return `${k}: \u2026`;
3726
+ }).join(", ");
3727
+ }
3728
+
3729
+ // src/react/hooks/useLiveTimeline.ts
3730
+ import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef4, useState as useState7 } from "react";
3731
+ function useLiveTimeline() {
3732
+ const builderRef = useRef4(null);
3733
+ if (!builderRef.current) builderRef.current = new LiveTimelineBuilder();
3734
+ const builder = builderRef.current;
3735
+ const [timeline, setTimeline] = useState7(() => builder.getTimeline());
3736
+ const sync = useCallback3(() => {
3737
+ setTimeline(builder.getTimeline());
3738
+ }, [builder]);
3739
+ const ingest = useCallback3(
3740
+ (event) => {
3741
+ builder.ingest(event);
3742
+ sync();
3743
+ },
3744
+ [builder, sync]
3745
+ );
3746
+ const startTurn = useCallback3(
3747
+ (userPrompt) => {
3748
+ builder.startTurn(userPrompt);
3749
+ sync();
3750
+ },
3751
+ [builder, sync]
3752
+ );
3753
+ const setSystemPrompt = useCallback3(
3754
+ (prompt) => {
3755
+ builder.setSystemPrompt(prompt);
3756
+ sync();
3757
+ },
3758
+ [builder, sync]
3759
+ );
3760
+ const setFinalDecision = useCallback3(
3761
+ (decision) => {
3762
+ builder.setFinalDecision(decision);
3763
+ sync();
3764
+ },
3765
+ [builder, sync]
3766
+ );
3767
+ const reset = useCallback3(() => {
3768
+ builder.reset();
3769
+ sync();
3770
+ }, [builder, sync]);
3771
+ return useMemo3(
3772
+ () => ({ timeline, ingest, startTurn, setSystemPrompt, setFinalDecision, reset, builder }),
3773
+ [timeline, ingest, startTurn, setSystemPrompt, setFinalDecision, reset, builder]
3774
+ );
3775
+ }
3776
+
3777
+ export {
3778
+ useLensTheme,
3779
+ resolve,
3780
+ MessagesPanel,
3781
+ SkillsPanel,
3782
+ StageFlow,
3783
+ TimeTravel,
3784
+ AskCard,
3785
+ RunSummary,
3786
+ AgentLens,
3787
+ Tabs,
3788
+ Lens,
3789
+ IterationStrip,
3790
+ ToolCallInspector,
3791
+ useLiveTimeline
3792
+ };
3793
+ //# sourceMappingURL=chunk-2JKRPWPZ.js.map