agentfootprint-lens 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,16 +17,30 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var src_exports = {};
22
32
  __export(src_exports, {
23
33
  AgentLens: () => AgentLens,
34
+ AskCard: () => AskCard,
24
35
  IterationStrip: () => IterationStrip,
25
36
  LiveTimelineBuilder: () => LiveTimelineBuilder,
26
37
  MessagesPanel: () => MessagesPanel,
38
+ RunSummary: () => RunSummary,
39
+ SkillsPanel: () => SkillsPanel,
40
+ StageFlow: () => StageFlow,
41
+ TimeTravel: () => TimeTravel,
27
42
  ToolCallInspector: () => ToolCallInspector,
43
+ deriveStages: () => deriveStages,
28
44
  fromAgentSnapshot: () => fromAgentSnapshot,
29
45
  resolveLensTheme: () => resolve,
30
46
  useLensTheme: () => useLensTheme,
@@ -33,7 +49,7 @@ __export(src_exports, {
33
49
  module.exports = __toCommonJS(src_exports);
34
50
 
35
51
  // src/AgentLens.tsx
36
- var import_react2 = require("react");
52
+ var import_react6 = require("react");
37
53
 
38
54
  // src/adapters/fromAgentSnapshot.ts
39
55
  function fromAgentSnapshot(runtime) {
@@ -241,7 +257,7 @@ function finalizeTurn(t) {
241
257
  }
242
258
 
243
259
  // src/panels/MessagesPanel.tsx
244
- var import_react = require("react");
260
+ var import_react = __toESM(require("react"), 1);
245
261
 
246
262
  // src/theme/useLensTheme.ts
247
263
  var import_footprint_explainable_ui = require("footprint-explainable-ui");
@@ -278,27 +294,105 @@ function MessagesPanel({
278
294
  timeline,
279
295
  onToolCallClick,
280
296
  systemPrompt,
281
- selectedIterKey
297
+ selectedIterKey,
298
+ stages,
299
+ focusIndex,
300
+ onFocusChange,
301
+ isLive
282
302
  }) {
283
303
  const t = useLensTheme();
284
304
  const scrollRef = (0, import_react.useRef)(null);
305
+ const iterRanges = useIterationStageRanges(stages);
306
+ (0, import_react.useEffect)(() => {
307
+ if (!scrollRef.current) {
308
+ console.log("[Lens scroll] selectedIter effect skipped: no scrollRef");
309
+ return;
310
+ }
311
+ if (selectedIterKey) {
312
+ const target = scrollRef.current.querySelector(
313
+ `[data-iter-key="${CSS.escape(selectedIterKey)}"]`
314
+ );
315
+ console.log("[Lens scroll] selectedIterKey path", {
316
+ selectedIterKey,
317
+ foundTarget: !!target
318
+ });
319
+ if (target) {
320
+ target.scrollIntoView({ block: "start", behavior: "smooth" });
321
+ target.setAttribute("data-iter-selected", "true");
322
+ const h = window.setTimeout(() => target.removeAttribute("data-iter-selected"), 1200);
323
+ return () => window.clearTimeout(h);
324
+ }
325
+ }
326
+ }, [selectedIterKey]);
285
327
  (0, import_react.useEffect)(() => {
286
- if (!selectedIterKey || !scrollRef.current) return;
328
+ const ctx = {
329
+ focusIndex,
330
+ isLive,
331
+ stagesLen: stages?.length ?? 0,
332
+ iterRangesSize: iterRanges.size,
333
+ hasScrollRef: !!scrollRef.current
334
+ };
335
+ if (!scrollRef.current || focusIndex === void 0 || !stages?.length) {
336
+ console.log("[Lens scroll] focus effect SKIPPED", ctx);
337
+ return;
338
+ }
339
+ if (isLive) {
340
+ const el = scrollRef.current;
341
+ const beforeTop2 = el.scrollTop;
342
+ const sh = el.scrollHeight;
343
+ const ch = el.clientHeight;
344
+ const canScroll = sh > ch;
345
+ const alreadyAtBottom = Math.abs(sh - ch - beforeTop2) < 2;
346
+ el.scrollTo({ top: sh, behavior: "auto" });
347
+ const afterTop = el.scrollTop;
348
+ const moved = afterTop !== beforeTop2;
349
+ console.log(
350
+ `[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}`,
351
+ { ...ctx, scrollHeight: sh, clientHeight: ch, beforeScrollTop: beforeTop2, afterScrollTop: afterTop }
352
+ );
353
+ return;
354
+ }
355
+ const key = keyForStage(iterRanges, focusIndex);
356
+ if (!key) {
357
+ console.log("[Lens scroll] focus effect: no iterKey for focusIndex", ctx);
358
+ return;
359
+ }
287
360
  const target = scrollRef.current.querySelector(
288
- `[data-iter-key="${CSS.escape(selectedIterKey)}"]`
361
+ `[data-iter-key="${CSS.escape(key)}"]`
289
362
  );
290
- if (!target) return;
363
+ if (!target) {
364
+ console.log("[Lens scroll] focus effect: DOM node missing for key", {
365
+ ...ctx,
366
+ key
367
+ });
368
+ return;
369
+ }
370
+ const container = scrollRef.current;
371
+ const rect = target.getBoundingClientRect();
372
+ const cRect = container.getBoundingClientRect();
373
+ const relTop = rect.top - cRect.top;
374
+ const beforeTop = container.scrollTop;
291
375
  target.scrollIntoView({ block: "start", behavior: "smooth" });
292
- target.setAttribute("data-iter-selected", "true");
293
- const h = window.setTimeout(() => target.removeAttribute("data-iter-selected"), 1200);
294
- return () => window.clearTimeout(h);
295
- }, [selectedIterKey]);
376
+ console.log(
377
+ `[Lens scroll] SCRUB key=${key} \xB7 relTop=${Math.round(relTop)} sh=${container.scrollHeight} ch=${container.clientHeight} before=${beforeTop}`,
378
+ { ...ctx, key }
379
+ );
380
+ }, [focusIndex, isLive, iterRanges, stages?.length]);
296
381
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
297
382
  "div",
298
383
  {
299
384
  ref: scrollRef,
300
385
  "data-fp-lens": "messages-panel",
301
386
  style: {
387
+ // Absolute + inset:0 inside a `position: relative` wrapper
388
+ // forces the panel to be EXACTLY the size of its wrapper cell,
389
+ // regardless of flex/grid height resolution quirks upstream.
390
+ // Without this, percentage heights and 1fr rows can fail to
391
+ // resolve and the panel grows to its content → no scroll.
392
+ // The grid-area wrapper in AgentLens sets `position: relative`
393
+ // to make this the containing block.
394
+ position: "absolute",
395
+ inset: 0,
302
396
  display: "flex",
303
397
  flexDirection: "column",
304
398
  gap: 16,
@@ -318,7 +412,11 @@ function MessagesPanel({
318
412
  {
319
413
  turn,
320
414
  allMessages: timeline.messages,
321
- onToolCallClick
415
+ onToolCallClick,
416
+ iterRanges,
417
+ focusIndex,
418
+ stages,
419
+ onFocusChange
322
420
  },
323
421
  turn.index
324
422
  ))
@@ -386,23 +484,44 @@ function SystemBubble({ text }) {
386
484
  function TurnBlock({
387
485
  turn,
388
486
  allMessages,
389
- onToolCallClick
487
+ onToolCallClick,
488
+ iterRanges,
489
+ focusIndex,
490
+ stages,
491
+ onFocusChange
390
492
  }) {
391
493
  const t = useLensTheme();
392
494
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
393
495
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TurnHeader, { turn }),
394
496
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UserBubble, { text: turn.userPrompt }),
395
- turn.iterations.map((iter, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
396
- IterationBlock,
397
- {
398
- iter,
399
- iterPositionInTurn: i + 1,
400
- turnIndex: turn.index,
401
- allMessages,
402
- onToolCallClick
403
- },
404
- iter.index
405
- )),
497
+ turn.iterations.map((iter, i) => {
498
+ const key = `${turn.index}.${iter.index}`;
499
+ const range = iterRanges?.get(key);
500
+ let state = "future";
501
+ if (range && focusIndex !== void 0) {
502
+ if (focusIndex < range.firstStageIndex) state = "future";
503
+ else if (focusIndex > range.lastStageIndex) state = "past";
504
+ else state = "active";
505
+ } else if (!range && focusIndex === void 0) {
506
+ state = "active";
507
+ }
508
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
509
+ IterationBlock,
510
+ {
511
+ iter,
512
+ iterPositionInTurn: i + 1,
513
+ turnIndex: turn.index,
514
+ allMessages,
515
+ onToolCallClick,
516
+ state,
517
+ stages,
518
+ range,
519
+ focusIndex,
520
+ ...range && onFocusChange ? { onClick: () => onFocusChange(range.lastStageIndex) } : {}
521
+ },
522
+ iter.index
523
+ );
524
+ }),
406
525
  turn.finalContent && turn.iterations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11, color: t.textSubtle, textAlign: "center" }, children: [
407
526
  "Answer compiled \xB7 ",
408
527
  turn.iterations.length,
@@ -467,30 +586,105 @@ function iterationHeadline(iter) {
467
586
  return "Neo is ready to answer";
468
587
  }
469
588
  if (iter.toolCalls.length === 1) {
470
- const tc = iter.toolCalls[0];
471
- if (tc.name === "list_skills") return "Neo is looking up available skills";
472
- if (tc.name === "read_skill") {
473
- const id = tc.arguments?.id ?? "?";
474
- return `Neo activated the \u201C${id}\u201D skill`;
475
- }
476
- return `Neo called ${tc.name} to get data`;
589
+ return singleToolHeadline(iter.toolCalls[0]);
477
590
  }
478
591
  const names = iter.toolCalls.map((tc) => tc.name);
479
- if (names.length <= 3) return `Neo called ${names.join(", ")} in parallel`;
480
- return `Neo gathered data from ${names.length} sources in parallel`;
592
+ if (names.length <= 3) {
593
+ return `Neo called ${names.length} tools in parallel (${names.join(", ")})`;
594
+ }
595
+ return `Neo gathered data from ${names.length} tools in parallel`;
596
+ }
597
+ function singleToolHeadline(tc) {
598
+ if (tc.name === "list_skills") return "Neo is looking up available skills";
599
+ if (tc.name === "read_skill") {
600
+ const id = tc.arguments?.id ?? "?";
601
+ return `Neo activated the "${id}" skill`;
602
+ }
603
+ if (tc.name === "ask_human" || tc.name === "ask_user") {
604
+ return "Neo asked the user for clarification";
605
+ }
606
+ return `Neo called tool (${tc.name})`;
481
607
  }
482
608
  function IterationBlock({
483
609
  iter,
484
610
  iterPositionInTurn,
485
611
  turnIndex,
486
612
  allMessages,
487
- onToolCallClick
613
+ onToolCallClick,
614
+ state = "active",
615
+ stages,
616
+ range,
617
+ focusIndex,
618
+ onClick
488
619
  }) {
489
620
  const t = useLensTheme();
490
621
  const [showContext, setShowContext] = (0, import_react.useState)(false);
491
622
  const key = `${turnIndex}.${iter.index}`;
492
623
  const headline = iterationHeadline(iter);
493
624
  const contextMessages = allMessages.slice(0, iter.messagesSentCount);
625
+ let revealIdx = Number.POSITIVE_INFINITY;
626
+ if (range && focusIndex !== void 0) {
627
+ if (state === "future") revealIdx = -1;
628
+ else if (state === "past") revealIdx = range.lastStageIndex;
629
+ else revealIdx = focusIndex;
630
+ }
631
+ const roundHasStarted = revealIdx >= (range?.firstStageIndex ?? 0);
632
+ const revealedMutations = (() => {
633
+ if (!stages || !range) return void 0;
634
+ const agg = {
635
+ systemPrompt: false,
636
+ tools: false,
637
+ systemPromptDeltaChars: 0,
638
+ toolsAdded: 0,
639
+ toolsRemoved: 0,
640
+ systemPromptAdded: "",
641
+ toolsAddedList: []
642
+ };
643
+ const stop = Math.min(revealIdx, range.lastStageIndex);
644
+ for (let i = range.firstStageIndex; i <= stop; i++) {
645
+ const m = stages[i]?.mutations;
646
+ if (!m) continue;
647
+ if (m.systemPrompt) agg.systemPrompt = true;
648
+ if (m.tools) agg.tools = true;
649
+ if (m.systemPromptDeltaChars) agg.systemPromptDeltaChars += m.systemPromptDeltaChars;
650
+ if (m.toolsAdded) agg.toolsAdded += m.toolsAdded;
651
+ if (m.toolsRemoved) agg.toolsRemoved += m.toolsRemoved;
652
+ if (m.systemPromptAdded) {
653
+ agg.systemPromptAdded = agg.systemPromptAdded ? `${agg.systemPromptAdded}
654
+
655
+ ${m.systemPromptAdded}` : m.systemPromptAdded;
656
+ }
657
+ if (m.activatedSkillId && !agg.activatedSkillId) {
658
+ agg.activatedSkillId = m.activatedSkillId;
659
+ }
660
+ if (m.toolsAddedList?.length) {
661
+ for (const name of m.toolsAddedList) {
662
+ if (!agg.toolsAddedList.includes(name)) agg.toolsAddedList.push(name);
663
+ }
664
+ }
665
+ }
666
+ return agg;
667
+ })();
668
+ const mutations = revealedMutations;
669
+ const toolStageIdx = /* @__PURE__ */ new Map();
670
+ if (stages) {
671
+ for (const tc of iter.toolCalls) {
672
+ let outIdx = -1;
673
+ let retIdx = -1;
674
+ for (let i = 0; i < stages.length; i++) {
675
+ const s = stages[i];
676
+ if (s.turnIndex !== turnIndex || s.iterIndex !== iter.index) continue;
677
+ if (s.toolName !== tc.name) continue;
678
+ if (s.from === "agent" && s.to === "tool" && outIdx < 0) outIdx = i;
679
+ else if (s.from === "tool" && s.to === "agent" && retIdx < 0) retIdx = i;
680
+ }
681
+ toolStageIdx.set(tc.id, { outIdx, retIdx });
682
+ }
683
+ }
684
+ const opacity = state === "future" ? 0.4 : state === "past" ? 0.85 : 1;
685
+ const isActiveRound = state === "active";
686
+ const background = isActiveRound ? `color-mix(in srgb, ${t.accent} 10%, ${t.bg})` : "transparent";
687
+ const boxShadow = isActiveRound ? `0 0 0 1px ${t.accent}, 0 0 24px color-mix(in srgb, ${t.accent} 22%, transparent)` : "none";
494
688
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
495
689
  "div",
496
690
  {
@@ -498,17 +692,36 @@ function IterationBlock({
498
692
  "data-turn-index": turnIndex,
499
693
  "data-iter-index": iter.index,
500
694
  style: {
695
+ position: "relative",
501
696
  display: "flex",
502
697
  flexDirection: "column",
503
698
  gap: 6,
504
- padding: 8,
505
- margin: -8,
506
- borderRadius: t.radius,
507
- outline: "2px solid transparent",
508
- outlineOffset: 2,
509
- transition: "outline-color 180ms ease, background 180ms ease"
699
+ padding: 12,
700
+ margin: isActiveRound ? "2px -12px" : "-8px",
701
+ borderRadius: 8,
702
+ background,
703
+ borderLeft: isActiveRound ? `3px solid ${t.accent}` : "3px solid transparent",
704
+ boxShadow,
705
+ opacity,
706
+ transition: "background 220ms ease, box-shadow 220ms ease, opacity 220ms ease, border-left-color 220ms ease, margin 220ms ease"
510
707
  },
511
708
  children: [
709
+ isActiveRound && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
710
+ "span",
711
+ {
712
+ "aria-hidden": "true",
713
+ style: {
714
+ position: "absolute",
715
+ left: -3,
716
+ top: 8,
717
+ bottom: 8,
718
+ width: 3,
719
+ background: t.accent,
720
+ boxShadow: `0 0 8px ${t.accent}`,
721
+ pointerEvents: "none"
722
+ }
723
+ }
724
+ ),
512
725
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
513
726
  "div",
514
727
  {
@@ -520,12 +733,36 @@ function IterationBlock({
520
733
  color: t.textMuted
521
734
  },
522
735
  children: [
523
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: t.accent, fontWeight: 600 }, children: [
524
- "Step ",
525
- iterPositionInTurn,
526
- ":"
527
- ] }),
528
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.text }, children: headline }),
736
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
737
+ "button",
738
+ {
739
+ onClick,
740
+ disabled: !onClick,
741
+ title: onClick ? "Jump the slider to this round" : void 0,
742
+ style: {
743
+ background: "transparent",
744
+ border: "none",
745
+ padding: 0,
746
+ margin: 0,
747
+ display: "flex",
748
+ alignItems: "baseline",
749
+ gap: 8,
750
+ cursor: onClick ? "pointer" : "default",
751
+ color: "inherit",
752
+ font: "inherit",
753
+ width: "auto",
754
+ textAlign: "left"
755
+ },
756
+ children: [
757
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: t.accent, fontWeight: 600 }, children: [
758
+ "Round ",
759
+ iterPositionInTurn,
760
+ ":"
761
+ ] }),
762
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.text }, children: headline })
763
+ ]
764
+ }
765
+ ),
529
766
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
530
767
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
531
768
  "span",
@@ -566,21 +803,57 @@ function IterationBlock({
566
803
  ]
567
804
  }
568
805
  ),
569
- iter.assistantContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
806
+ roundHasStarted && (iter.model || iter.stopReason || (iter.matchedInstructions?.length ?? 0) > 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
570
807
  "div",
571
808
  {
572
809
  style: {
573
- background: t.bgElev,
574
- border: `1px solid ${t.border}`,
575
- borderRadius: `2px ${t.radius} ${t.radius} ${t.radius}`,
576
- padding: "10px 14px",
577
- maxWidth: 820,
578
- whiteSpace: "pre-wrap"
810
+ display: "flex",
811
+ flexWrap: "wrap",
812
+ gap: 6,
813
+ fontSize: 10,
814
+ color: t.textSubtle,
815
+ fontFamily: t.fontMono,
816
+ paddingLeft: 12
579
817
  },
580
- children: iter.assistantContent
818
+ children: [
819
+ iter.model && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetaPill, { t, title: "Model the LLM call was routed to", children: iter.model }),
820
+ iter.stopReason && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MetaPill, { t, title: "Why the LLM stopped producing tokens", children: [
821
+ "stop: ",
822
+ iter.stopReason
823
+ ] }),
824
+ iter.matchedInstructions?.map((id) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
825
+ MetaPill,
826
+ {
827
+ t,
828
+ title: "Instruction injected into this round",
829
+ accent: true,
830
+ children: [
831
+ "\u25B8 ",
832
+ id
833
+ ]
834
+ },
835
+ id
836
+ ))
837
+ ]
581
838
  }
582
839
  ),
583
- iter.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6, paddingLeft: 12 }, children: iter.toolCalls.map((tc) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolCallCard, { invocation: tc, onClick: onToolCallClick }, tc.id)) }),
840
+ roundHasStarted && iter.assistantContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReasoningBubble, { text: iter.assistantContent }),
841
+ roundHasStarted && iter.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6, paddingLeft: 12 }, children: iter.toolCalls.map((tc) => {
842
+ const idx = toolStageIdx.get(tc.id);
843
+ const cardRevealed = !idx || idx.outIdx < 0 || revealIdx >= idx.outIdx;
844
+ if (!cardRevealed) return null;
845
+ const resultRevealed = !idx || idx.retIdx < 0 || revealIdx >= idx.retIdx;
846
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
847
+ ToolCallCard,
848
+ {
849
+ invocation: tc,
850
+ onClick: onToolCallClick,
851
+ resultRevealed
852
+ },
853
+ tc.id
854
+ );
855
+ }) }),
856
+ mutations && (mutations.systemPrompt || mutations.tools) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MutationStrip, { mutations, iter }),
584
857
  showContext && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
585
858
  ContextDrawer,
586
859
  {
@@ -593,6 +866,448 @@ function IterationBlock({
593
866
  }
594
867
  );
595
868
  }
869
+ function ReasoningBubble({ text }) {
870
+ const t = useLensTheme();
871
+ const [open, setOpen] = (0, import_react.useState)(false);
872
+ const PREVIEW_LEN = 140;
873
+ const flat = text.replace(/\s+/g, " ").trim();
874
+ const needsToggle = flat.length > PREVIEW_LEN;
875
+ const preview = needsToggle ? flat.slice(0, PREVIEW_LEN - 1) + "\u2026" : flat;
876
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
877
+ "div",
878
+ {
879
+ style: {
880
+ background: t.bgElev,
881
+ border: `1px solid ${t.border}`,
882
+ borderRadius: `2px ${t.radius} ${t.radius} ${t.radius}`,
883
+ padding: "10px 14px",
884
+ maxWidth: 820,
885
+ whiteSpace: "pre-wrap",
886
+ fontSize: 13,
887
+ lineHeight: 1.55
888
+ },
889
+ children: needsToggle && !open ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
890
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: preview }),
891
+ " ",
892
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
893
+ "button",
894
+ {
895
+ onClick: () => setOpen(true),
896
+ style: {
897
+ background: "transparent",
898
+ border: "none",
899
+ padding: 0,
900
+ color: t.accent,
901
+ cursor: "pointer",
902
+ fontSize: 12,
903
+ fontWeight: 600,
904
+ width: "auto"
905
+ },
906
+ children: "\u25B8 Show full reasoning"
907
+ }
908
+ )
909
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
910
+ text,
911
+ needsToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
912
+ "button",
913
+ {
914
+ onClick: () => setOpen(false),
915
+ style: {
916
+ background: "transparent",
917
+ border: "none",
918
+ padding: 0,
919
+ color: t.textMuted,
920
+ cursor: "pointer",
921
+ fontSize: 12,
922
+ fontWeight: 500,
923
+ width: "auto"
924
+ },
925
+ children: "\u25BE Collapse"
926
+ }
927
+ ) })
928
+ ] })
929
+ }
930
+ );
931
+ }
932
+ function MetaPill({
933
+ t,
934
+ children,
935
+ title,
936
+ accent
937
+ }) {
938
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
939
+ "span",
940
+ {
941
+ title,
942
+ style: {
943
+ padding: "1px 6px",
944
+ borderRadius: 3,
945
+ background: accent ? `color-mix(in srgb, ${t.warning} 18%, transparent)` : t.bgElev,
946
+ color: accent ? t.warning : t.textSubtle,
947
+ fontWeight: accent ? 600 : 500,
948
+ letterSpacing: "0.02em",
949
+ whiteSpace: "nowrap"
950
+ },
951
+ children
952
+ }
953
+ );
954
+ }
955
+ function MutationStrip({
956
+ mutations,
957
+ iter
958
+ }) {
959
+ const t = useLensTheme();
960
+ const [open, setOpen] = (0, import_react.useState)(false);
961
+ const fallbackReadSkill = mutations.systemPromptAdded ? void 0 : iter.toolCalls.find((tc) => tc.name === "read_skill");
962
+ const skillId = mutations.activatedSkillId || (fallbackReadSkill?.arguments?.id ?? "");
963
+ const skillBody = mutations.systemPromptAdded || fallbackReadSkill?.result || "";
964
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
965
+ "div",
966
+ {
967
+ style: {
968
+ display: "flex",
969
+ flexDirection: "column",
970
+ gap: 6,
971
+ paddingLeft: 12,
972
+ fontSize: 11,
973
+ color: t.textMuted,
974
+ fontFamily: t.fontSans
975
+ },
976
+ children: [
977
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
978
+ "button",
979
+ {
980
+ onClick: () => setOpen((v) => !v),
981
+ style: {
982
+ display: "flex",
983
+ flexWrap: "wrap",
984
+ alignItems: "center",
985
+ gap: 6,
986
+ background: "transparent",
987
+ border: "none",
988
+ padding: 0,
989
+ margin: 0,
990
+ cursor: "pointer",
991
+ color: "inherit",
992
+ font: "inherit",
993
+ width: "auto",
994
+ textAlign: "left"
995
+ },
996
+ title: "Click to see the actual diff",
997
+ children: [
998
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10, color: t.textSubtle }, children: open ? "\u25BE" : "\u25B8" }),
999
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1000
+ "span",
1001
+ {
1002
+ style: {
1003
+ fontWeight: 700,
1004
+ color: t.accent,
1005
+ textTransform: "uppercase",
1006
+ letterSpacing: "0.06em",
1007
+ fontSize: 10
1008
+ },
1009
+ children: "\u270E Changed"
1010
+ }
1011
+ ),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "in Neo's input:" }),
1013
+ mutations.systemPrompt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1014
+ "span",
1015
+ {
1016
+ style: {
1017
+ padding: "1px 7px",
1018
+ borderRadius: 3,
1019
+ background: `color-mix(in srgb, ${t.accent} 18%, transparent)`,
1020
+ color: t.accent,
1021
+ fontWeight: 600,
1022
+ letterSpacing: "0.02em"
1023
+ },
1024
+ children: [
1025
+ "System Prompt",
1026
+ mutations.systemPromptDeltaChars > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontWeight: 400, marginLeft: 4 }, children: [
1027
+ "+",
1028
+ mutations.systemPromptDeltaChars.toLocaleString(),
1029
+ " chars"
1030
+ ] })
1031
+ ]
1032
+ }
1033
+ ),
1034
+ mutations.tools && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1035
+ "span",
1036
+ {
1037
+ style: {
1038
+ padding: "1px 7px",
1039
+ borderRadius: 3,
1040
+ background: `color-mix(in srgb, ${t.accent} 18%, transparent)`,
1041
+ color: t.accent,
1042
+ fontWeight: 600,
1043
+ letterSpacing: "0.02em"
1044
+ },
1045
+ children: [
1046
+ "Tools",
1047
+ (mutations.toolsAdded > 0 || mutations.toolsRemoved > 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontWeight: 400, marginLeft: 4 }, children: [
1048
+ mutations.toolsAdded > 0 ? `+${mutations.toolsAdded}` : "",
1049
+ mutations.toolsRemoved > 0 ? ` -${mutations.toolsRemoved}` : ""
1050
+ ] })
1051
+ ]
1052
+ }
1053
+ )
1054
+ ]
1055
+ }
1056
+ ),
1057
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1058
+ MutationDiffModal,
1059
+ {
1060
+ mutations,
1061
+ skillId,
1062
+ skillBody,
1063
+ visibleTools: iter.visibleTools,
1064
+ onClose: () => setOpen(false)
1065
+ }
1066
+ )
1067
+ ]
1068
+ }
1069
+ );
1070
+ }
1071
+ function MutationDiffModal({
1072
+ mutations,
1073
+ skillId,
1074
+ skillBody,
1075
+ visibleTools,
1076
+ onClose
1077
+ }) {
1078
+ const t = useLensTheme();
1079
+ (0, import_react.useEffect)(() => {
1080
+ const onKey = (e) => {
1081
+ if (e.key === "Escape") onClose();
1082
+ };
1083
+ window.addEventListener("keydown", onKey);
1084
+ return () => window.removeEventListener("keydown", onKey);
1085
+ }, [onClose]);
1086
+ const toolsToShow = mutations.toolsAddedList.length > 0 ? mutations.toolsAddedList : visibleTools;
1087
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1088
+ "div",
1089
+ {
1090
+ onClick: onClose,
1091
+ role: "dialog",
1092
+ "aria-modal": "true",
1093
+ style: {
1094
+ position: "fixed",
1095
+ inset: 0,
1096
+ background: "rgba(0, 0, 0, 0.55)",
1097
+ backdropFilter: "blur(4px)",
1098
+ WebkitBackdropFilter: "blur(4px)",
1099
+ display: "flex",
1100
+ alignItems: "center",
1101
+ justifyContent: "center",
1102
+ zIndex: 1e3,
1103
+ padding: 24
1104
+ },
1105
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1106
+ "div",
1107
+ {
1108
+ onClick: (e) => e.stopPropagation(),
1109
+ style: {
1110
+ background: t.bg,
1111
+ color: t.text,
1112
+ border: `1px solid ${t.border}`,
1113
+ borderRadius: 12,
1114
+ boxShadow: "0 24px 64px rgba(0, 0, 0, 0.45)",
1115
+ width: "min(880px, 100%)",
1116
+ maxHeight: "min(80dvh, 800px)",
1117
+ display: "flex",
1118
+ flexDirection: "column",
1119
+ fontFamily: t.fontSans
1120
+ },
1121
+ children: [
1122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1123
+ "div",
1124
+ {
1125
+ style: {
1126
+ display: "flex",
1127
+ alignItems: "center",
1128
+ padding: "14px 18px",
1129
+ borderBottom: `1px solid ${t.border}`
1130
+ },
1131
+ children: [
1132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1134
+ "div",
1135
+ {
1136
+ style: {
1137
+ fontSize: 10,
1138
+ color: t.textSubtle,
1139
+ textTransform: "uppercase",
1140
+ letterSpacing: "0.08em",
1141
+ fontWeight: 600
1142
+ },
1143
+ children: "\u270E Changed in Neo's input"
1144
+ }
1145
+ ),
1146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 15, fontWeight: 600, marginTop: 2 }, children: [
1147
+ "Round diff",
1148
+ skillId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1149
+ " \xB7 ",
1150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1151
+ "code",
1152
+ {
1153
+ style: {
1154
+ fontFamily: t.fontMono,
1155
+ color: t.accent,
1156
+ fontWeight: 600
1157
+ },
1158
+ children: skillId
1159
+ }
1160
+ )
1161
+ ] })
1162
+ ] })
1163
+ ] }),
1164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
1165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1166
+ "button",
1167
+ {
1168
+ onClick: onClose,
1169
+ title: "Close (Esc)",
1170
+ style: {
1171
+ background: "transparent",
1172
+ border: `1px solid ${t.border}`,
1173
+ color: t.textMuted,
1174
+ padding: "4px 10px",
1175
+ borderRadius: 6,
1176
+ cursor: "pointer",
1177
+ fontSize: 12,
1178
+ width: "auto"
1179
+ },
1180
+ children: "Esc"
1181
+ }
1182
+ )
1183
+ ]
1184
+ }
1185
+ ),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1187
+ "div",
1188
+ {
1189
+ style: {
1190
+ padding: 18,
1191
+ overflow: "auto",
1192
+ display: "flex",
1193
+ flexDirection: "column",
1194
+ gap: 16
1195
+ },
1196
+ children: [
1197
+ mutations.systemPrompt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
1198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1199
+ "div",
1200
+ {
1201
+ style: {
1202
+ fontSize: 11,
1203
+ color: t.textSubtle,
1204
+ textTransform: "uppercase",
1205
+ letterSpacing: "0.08em",
1206
+ fontWeight: 600,
1207
+ marginBottom: 8
1208
+ },
1209
+ children: [
1210
+ "System Prompt",
1211
+ mutations.systemPromptDeltaChars > 0 && ` \xB7 +${mutations.systemPromptDeltaChars.toLocaleString()} chars`
1212
+ ]
1213
+ }
1214
+ ),
1215
+ skillBody ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1216
+ "pre",
1217
+ {
1218
+ style: {
1219
+ margin: 0,
1220
+ padding: "12px 14px",
1221
+ background: t.bgElev,
1222
+ border: `1px solid ${t.border}`,
1223
+ borderLeft: `3px solid ${t.accent}`,
1224
+ borderRadius: 6,
1225
+ fontSize: 12,
1226
+ lineHeight: 1.55,
1227
+ fontFamily: t.fontMono,
1228
+ color: t.text,
1229
+ whiteSpace: "pre-wrap"
1230
+ },
1231
+ children: skillBody
1232
+ }
1233
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1234
+ "div",
1235
+ {
1236
+ style: {
1237
+ padding: "10px 12px",
1238
+ border: `1px dashed ${t.border}`,
1239
+ borderRadius: 6,
1240
+ color: t.textSubtle,
1241
+ fontStyle: "italic",
1242
+ fontSize: 12
1243
+ },
1244
+ children: [
1245
+ "System Prompt grew by ",
1246
+ mutations.systemPromptDeltaChars.toLocaleString(),
1247
+ " ",
1248
+ "chars but the source text isn't flowing through the adapter for this round."
1249
+ ]
1250
+ }
1251
+ )
1252
+ ] }),
1253
+ mutations.tools && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
1254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1255
+ "div",
1256
+ {
1257
+ style: {
1258
+ fontSize: 11,
1259
+ color: t.textSubtle,
1260
+ textTransform: "uppercase",
1261
+ letterSpacing: "0.08em",
1262
+ fontWeight: 600,
1263
+ marginBottom: 8
1264
+ },
1265
+ children: [
1266
+ "Tools",
1267
+ mutations.toolsAdded > 0 && ` \xB7 +${mutations.toolsAdded} added`,
1268
+ mutations.toolsRemoved > 0 && ` \xB7 -${mutations.toolsRemoved} removed`
1269
+ ]
1270
+ }
1271
+ ),
1272
+ toolsToShow.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: toolsToShow.map((name) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1273
+ "span",
1274
+ {
1275
+ style: {
1276
+ padding: "3px 9px",
1277
+ borderRadius: 4,
1278
+ background: t.bgElev,
1279
+ border: `1px solid ${t.border}`,
1280
+ color: t.text,
1281
+ fontFamily: t.fontMono,
1282
+ fontSize: 11
1283
+ },
1284
+ children: name
1285
+ },
1286
+ name
1287
+ )) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1288
+ "div",
1289
+ {
1290
+ style: {
1291
+ padding: "10px 12px",
1292
+ border: `1px dashed ${t.border}`,
1293
+ borderRadius: 6,
1294
+ color: t.textSubtle,
1295
+ fontStyle: "italic",
1296
+ fontSize: 12
1297
+ },
1298
+ children: "The tool list that came online in this round isn't flowing through the adapter yet \u2014 counts are available, names aren't."
1299
+ }
1300
+ )
1301
+ ] })
1302
+ ]
1303
+ }
1304
+ )
1305
+ ]
1306
+ }
1307
+ )
1308
+ }
1309
+ );
1310
+ }
596
1311
  function ContextDrawer({
597
1312
  messagesSentCount,
598
1313
  contextMessages,
@@ -679,19 +1394,21 @@ function summarizeMessage(m) {
679
1394
  }
680
1395
  function ToolCallCard({
681
1396
  invocation,
682
- onClick
1397
+ onClick,
1398
+ resultRevealed = true
683
1399
  }) {
684
1400
  const t = useLensTheme();
685
1401
  const [open, setOpen] = (0, import_react.useState)(false);
686
1402
  const preview = shortArgs(invocation.arguments);
687
1403
  const errored = invocation.error === true;
688
1404
  const friendlyVerb = toolVerb(invocation);
1405
+ const borderStyle = resultRevealed ? "solid" : "dashed";
689
1406
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
690
1407
  "div",
691
1408
  {
692
1409
  style: {
693
- border: `1px solid ${errored ? t.error : t.border}`,
694
- borderLeft: `3px solid ${errored ? t.error : t.accent}`,
1410
+ border: `1px ${borderStyle} ${errored ? t.error : t.border}`,
1411
+ borderLeft: `3px ${borderStyle} ${errored ? t.error : t.accent}`,
695
1412
  borderRadius: 6,
696
1413
  background: t.bg,
697
1414
  overflow: "hidden"
@@ -721,6 +1438,23 @@ function ToolCallCard({
721
1438
  ")"
722
1439
  ] }),
723
1440
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
1441
+ !resultRevealed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1442
+ "span",
1443
+ {
1444
+ style: {
1445
+ fontSize: 10,
1446
+ padding: "1px 6px",
1447
+ borderRadius: 3,
1448
+ background: `color-mix(in srgb, ${t.accent} 18%, transparent)`,
1449
+ color: t.accent,
1450
+ fontWeight: 600,
1451
+ textTransform: "uppercase",
1452
+ letterSpacing: "0.04em"
1453
+ },
1454
+ title: "Args sent; waiting for the tool to return",
1455
+ children: "in flight"
1456
+ }
1457
+ ),
724
1458
  invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
725
1459
  "span",
726
1460
  {
@@ -744,7 +1478,7 @@ function ToolCallCard({
744
1478
  open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "8px 12px", borderTop: `1px solid ${t.border}` }, children: [
745
1479
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, children: "What Neo asked for" }),
746
1480
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.arguments }),
747
- invocation.result && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1481
+ resultRevealed && invocation.result && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
748
1482
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "What the tool returned" }),
749
1483
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
750
1484
  "pre",
@@ -765,7 +1499,22 @@ function ToolCallCard({
765
1499
  }
766
1500
  )
767
1501
  ] }),
768
- invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1502
+ !resultRevealed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1503
+ "div",
1504
+ {
1505
+ style: {
1506
+ marginTop: 10,
1507
+ padding: "10px 12px",
1508
+ border: `1px dashed ${t.border}`,
1509
+ borderRadius: 4,
1510
+ fontSize: 12,
1511
+ color: t.textSubtle,
1512
+ fontStyle: "italic"
1513
+ },
1514
+ children: "Neo has sent the args; advance the slider to see the result this tool returned."
1515
+ }
1516
+ ),
1517
+ resultRevealed && invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
769
1518
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "What changed in Neo's state" }),
770
1519
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.decisionUpdate })
771
1520
  ] })
@@ -777,7 +1526,8 @@ function ToolCallCard({
777
1526
  function toolVerb(inv) {
778
1527
  if (inv.name === "list_skills") return "Asked for";
779
1528
  if (inv.name === "read_skill") return "Activated";
780
- return "Called";
1529
+ if (inv.name === "ask_human" || inv.name === "ask_user") return "Asked user for";
1530
+ return "Called tool";
781
1531
  }
782
1532
  function Label({
783
1533
  t,
@@ -829,10 +1579,1985 @@ function shortArgs(args) {
829
1579
  }
830
1580
  return keys.join(", ");
831
1581
  }
1582
+ function useIterationStageRanges(stages) {
1583
+ return import_react.default.useMemo(() => {
1584
+ const map = /* @__PURE__ */ new Map();
1585
+ if (!stages?.length) return map;
1586
+ const turnIters = /* @__PURE__ */ new Map();
1587
+ for (const s of stages) {
1588
+ if (s.iterIndex === void 0) continue;
1589
+ const list = turnIters.get(s.turnIndex) ?? [];
1590
+ if (!list.includes(s.iterIndex)) list.push(s.iterIndex);
1591
+ turnIters.set(s.turnIndex, list);
1592
+ }
1593
+ stages.forEach((s, idx) => {
1594
+ let iter = s.iterIndex;
1595
+ if (iter === void 0) {
1596
+ const iters = turnIters.get(s.turnIndex);
1597
+ if (!iters?.length) return;
1598
+ iter = s.from === "user" ? iters[0] : iters[iters.length - 1];
1599
+ }
1600
+ const key = `${s.turnIndex}.${iter}`;
1601
+ const prev = map.get(key);
1602
+ if (!prev) {
1603
+ map.set(key, { firstStageIndex: idx, lastStageIndex: idx });
1604
+ } else {
1605
+ map.set(key, {
1606
+ firstStageIndex: Math.min(prev.firstStageIndex, idx),
1607
+ lastStageIndex: Math.max(prev.lastStageIndex, idx)
1608
+ });
1609
+ }
1610
+ });
1611
+ return map;
1612
+ }, [stages]);
1613
+ }
1614
+ function keyForStage(ranges, focusIndex) {
1615
+ for (const [key, r] of ranges) {
1616
+ if (focusIndex >= r.firstStageIndex && focusIndex <= r.lastStageIndex) return key;
1617
+ }
1618
+ return null;
1619
+ }
832
1620
 
833
- // src/panels/IterationStrip.tsx
1621
+ // src/panels/SkillsPanel.tsx
1622
+ var import_react2 = require("react");
834
1623
  var import_jsx_runtime2 = require("react/jsx-runtime");
835
- function IterationStrip({ timeline, selectedKey, onSelect }) {
1624
+ function SkillsPanel({ skills, onClose, activeSkillId }) {
1625
+ const t = useLensTheme();
1626
+ const [selectedId, setSelectedId] = (0, import_react2.useState)(
1627
+ activeSkillId ?? skills[0]?.id ?? null
1628
+ );
1629
+ const [mode, setMode] = (0, import_react2.useState)("formatted");
1630
+ const selected = skills.find((s) => s.id === selectedId) ?? null;
1631
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1632
+ "div",
1633
+ {
1634
+ onClick: onClose,
1635
+ style: {
1636
+ position: "absolute",
1637
+ inset: 0,
1638
+ background: "rgba(0, 0, 0, 0.5)",
1639
+ zIndex: 100,
1640
+ display: "flex",
1641
+ alignItems: "stretch",
1642
+ justifyContent: "stretch"
1643
+ },
1644
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1645
+ "div",
1646
+ {
1647
+ onClick: (e) => e.stopPropagation(),
1648
+ style: {
1649
+ display: "grid",
1650
+ gridTemplateColumns: "minmax(220px, 280px) 1fr",
1651
+ gridTemplateRows: "auto 1fr",
1652
+ gridTemplateAreas: '"header header" "list detail"',
1653
+ width: "100%",
1654
+ height: "100%",
1655
+ background: t.bg,
1656
+ color: t.text,
1657
+ fontFamily: t.fontSans,
1658
+ border: `1px solid ${t.border}`
1659
+ },
1660
+ children: [
1661
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1662
+ "div",
1663
+ {
1664
+ style: {
1665
+ gridArea: "header",
1666
+ padding: "10px 14px",
1667
+ borderBottom: `1px solid ${t.border}`,
1668
+ background: t.bgElev,
1669
+ display: "flex",
1670
+ alignItems: "center",
1671
+ gap: 10
1672
+ },
1673
+ children: [
1674
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { style: { color: t.text, fontSize: 13 }, children: "Skills registered with Neo" }),
1675
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { color: t.textMuted, fontSize: 12 }, children: [
1676
+ skills.length,
1677
+ " total"
1678
+ ] }),
1679
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1 } }),
1680
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1681
+ "button",
1682
+ {
1683
+ onClick: onClose,
1684
+ style: {
1685
+ background: "transparent",
1686
+ border: `1px solid ${t.border}`,
1687
+ color: t.textMuted,
1688
+ borderRadius: 4,
1689
+ padding: "2px 10px",
1690
+ cursor: "pointer",
1691
+ fontSize: 14,
1692
+ width: "auto",
1693
+ fontWeight: 400
1694
+ },
1695
+ title: "Close (Esc)",
1696
+ children: "\u2715"
1697
+ }
1698
+ )
1699
+ ]
1700
+ }
1701
+ ),
1702
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1703
+ "div",
1704
+ {
1705
+ style: {
1706
+ gridArea: "list",
1707
+ borderRight: `1px solid ${t.border}`,
1708
+ overflow: "auto",
1709
+ background: t.bg
1710
+ },
1711
+ children: [
1712
+ skills.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "No skills registered." }),
1713
+ skills.map((s) => {
1714
+ const isActive = s.id === selectedId;
1715
+ const isAgentActive = s.id === activeSkillId;
1716
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1717
+ "button",
1718
+ {
1719
+ onClick: () => setSelectedId(s.id),
1720
+ style: {
1721
+ display: "block",
1722
+ width: "100%",
1723
+ textAlign: "left",
1724
+ padding: "10px 14px",
1725
+ background: isActive ? t.bgHover : "transparent",
1726
+ border: "none",
1727
+ borderLeft: `3px solid ${isActive ? t.accent : isAgentActive ? t.success : "transparent"}`,
1728
+ borderBottom: `1px solid ${t.border}`,
1729
+ color: t.text,
1730
+ cursor: "pointer",
1731
+ fontFamily: "inherit"
1732
+ },
1733
+ children: [
1734
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1735
+ "div",
1736
+ {
1737
+ style: {
1738
+ display: "flex",
1739
+ alignItems: "baseline",
1740
+ gap: 6,
1741
+ fontSize: 13
1742
+ },
1743
+ children: [
1744
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontWeight: 600 }, children: s.title ?? s.id }),
1745
+ isAgentActive && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1746
+ "span",
1747
+ {
1748
+ style: {
1749
+ fontSize: 9,
1750
+ padding: "1px 5px",
1751
+ borderRadius: 3,
1752
+ background: `color-mix(in srgb, ${t.success} 25%, transparent)`,
1753
+ color: t.success,
1754
+ fontWeight: 600,
1755
+ textTransform: "uppercase"
1756
+ },
1757
+ children: "active"
1758
+ }
1759
+ )
1760
+ ]
1761
+ }
1762
+ ),
1763
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1764
+ "div",
1765
+ {
1766
+ style: {
1767
+ fontSize: 10,
1768
+ color: t.textSubtle,
1769
+ fontFamily: t.fontMono,
1770
+ marginTop: 1
1771
+ },
1772
+ children: [
1773
+ s.id,
1774
+ s.version && ` \xB7 v${s.version}`
1775
+ ]
1776
+ }
1777
+ ),
1778
+ s.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1779
+ "div",
1780
+ {
1781
+ style: {
1782
+ fontSize: 11,
1783
+ color: t.textMuted,
1784
+ marginTop: 4,
1785
+ lineHeight: 1.4,
1786
+ display: "-webkit-box",
1787
+ WebkitLineClamp: 3,
1788
+ WebkitBoxOrient: "vertical",
1789
+ overflow: "hidden"
1790
+ },
1791
+ children: s.description
1792
+ }
1793
+ )
1794
+ ]
1795
+ },
1796
+ s.id
1797
+ );
1798
+ })
1799
+ ]
1800
+ }
1801
+ ),
1802
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1803
+ "div",
1804
+ {
1805
+ style: {
1806
+ gridArea: "detail",
1807
+ overflow: "auto",
1808
+ padding: "14px 18px",
1809
+ fontSize: 13,
1810
+ lineHeight: 1.6
1811
+ },
1812
+ children: !selected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: t.textSubtle }, children: "Select a skill to see details." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1813
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "baseline", gap: 10, marginBottom: 10 }, children: [
1814
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: { margin: 0, fontSize: 18, color: t.text }, children: selected.title ?? selected.id }),
1815
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { color: t.textSubtle, fontFamily: t.fontMono, fontSize: 11 }, children: [
1816
+ selected.id,
1817
+ selected.version && ` \xB7 v${selected.version}`
1818
+ ] }),
1819
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { flex: 1 } }),
1820
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1821
+ "div",
1822
+ {
1823
+ role: "tablist",
1824
+ style: {
1825
+ display: "flex",
1826
+ gap: 1,
1827
+ border: `1px solid ${t.border}`,
1828
+ borderRadius: 4,
1829
+ overflow: "hidden"
1830
+ },
1831
+ children: [
1832
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1833
+ "button",
1834
+ {
1835
+ onClick: () => setMode("formatted"),
1836
+ style: {
1837
+ padding: "3px 10px",
1838
+ fontSize: 11,
1839
+ background: mode === "formatted" ? t.accent : "transparent",
1840
+ color: mode === "formatted" ? "#fff" : t.textMuted,
1841
+ border: "none",
1842
+ cursor: "pointer",
1843
+ width: "auto",
1844
+ fontWeight: 400
1845
+ },
1846
+ children: "Formatted"
1847
+ }
1848
+ ),
1849
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1850
+ "button",
1851
+ {
1852
+ onClick: () => setMode("json"),
1853
+ style: {
1854
+ padding: "3px 10px",
1855
+ fontSize: 11,
1856
+ background: mode === "json" ? t.accent : "transparent",
1857
+ color: mode === "json" ? "#fff" : t.textMuted,
1858
+ border: "none",
1859
+ cursor: "pointer",
1860
+ width: "auto",
1861
+ fontWeight: 400
1862
+ },
1863
+ children: "Raw JSON"
1864
+ }
1865
+ )
1866
+ ]
1867
+ }
1868
+ )
1869
+ ] }),
1870
+ mode === "formatted" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SkillFormatted, { skill: selected }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SkillJson, { skill: selected })
1871
+ ] })
1872
+ }
1873
+ )
1874
+ ]
1875
+ }
1876
+ )
1877
+ }
1878
+ );
1879
+ }
1880
+ function SkillFormatted({ skill }) {
1881
+ const t = useLensTheme();
1882
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
1883
+ skill.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
1884
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Label2, { t, children: "Description" }),
1885
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: t.text }, children: skill.description })
1886
+ ] }),
1887
+ skill.scope && skill.scope.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
1888
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Label2, { t, children: "Scope" }),
1889
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: skill.scope.map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1890
+ "span",
1891
+ {
1892
+ style: {
1893
+ padding: "2px 8px",
1894
+ background: t.bgElev,
1895
+ border: `1px solid ${t.border}`,
1896
+ borderRadius: 3,
1897
+ fontSize: 11,
1898
+ fontFamily: t.fontMono,
1899
+ color: t.textMuted
1900
+ },
1901
+ children: s
1902
+ },
1903
+ s
1904
+ )) })
1905
+ ] }),
1906
+ skill.tools && skill.tools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
1907
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Label2, { t, children: [
1908
+ "Tools this skill exposes \xB7 ",
1909
+ skill.tools.length
1910
+ ] }),
1911
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: skill.tools.map((id) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1912
+ "span",
1913
+ {
1914
+ style: {
1915
+ padding: "2px 8px",
1916
+ background: `color-mix(in srgb, ${t.accent} 15%, transparent)`,
1917
+ border: `1px solid ${t.border}`,
1918
+ borderRadius: 3,
1919
+ fontSize: 11,
1920
+ fontFamily: t.fontMono,
1921
+ color: t.accent
1922
+ },
1923
+ children: id
1924
+ },
1925
+ id
1926
+ )) }),
1927
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 11, color: t.textSubtle, marginTop: 4, fontStyle: "italic" }, children: "Only these tools reach the LLM while this skill is active (autoActivate)." })
1928
+ ] }),
1929
+ skill.body && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
1930
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Label2, { t, children: "Body (sent to LLM on read_skill)" }),
1931
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1932
+ "pre",
1933
+ {
1934
+ style: {
1935
+ margin: 0,
1936
+ padding: "10px 12px",
1937
+ background: t.bgElev,
1938
+ border: `1px solid ${t.border}`,
1939
+ borderRadius: 4,
1940
+ fontSize: 12,
1941
+ lineHeight: 1.55,
1942
+ fontFamily: t.fontMono,
1943
+ whiteSpace: "pre-wrap",
1944
+ color: t.text,
1945
+ maxHeight: 480,
1946
+ overflow: "auto"
1947
+ },
1948
+ children: skill.body
1949
+ }
1950
+ )
1951
+ ] })
1952
+ ] });
1953
+ }
1954
+ function SkillJson({ skill }) {
1955
+ const t = useLensTheme();
1956
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1957
+ "pre",
1958
+ {
1959
+ style: {
1960
+ margin: 0,
1961
+ padding: "10px 12px",
1962
+ background: t.bgElev,
1963
+ border: `1px solid ${t.border}`,
1964
+ borderRadius: 4,
1965
+ fontSize: 12,
1966
+ lineHeight: 1.55,
1967
+ fontFamily: t.fontMono,
1968
+ whiteSpace: "pre-wrap",
1969
+ color: t.text,
1970
+ maxHeight: "calc(100vh - 200px)",
1971
+ overflow: "auto"
1972
+ },
1973
+ children: safeJsonStringify(skill)
1974
+ }
1975
+ );
1976
+ }
1977
+ function safeJsonStringify(value) {
1978
+ const seen = /* @__PURE__ */ new WeakSet();
1979
+ try {
1980
+ return JSON.stringify(
1981
+ value,
1982
+ (_k, v) => {
1983
+ if (typeof v === "object" && v !== null) {
1984
+ if (seen.has(v)) return "[Circular]";
1985
+ seen.add(v);
1986
+ }
1987
+ if (typeof v === "function") return `[function ${v.name || "anonymous"}]`;
1988
+ return v;
1989
+ },
1990
+ 2
1991
+ );
1992
+ } catch (err) {
1993
+ return `[stringify error: ${err instanceof Error ? err.message : String(err)}]`;
1994
+ }
1995
+ }
1996
+ function Label2({
1997
+ t,
1998
+ children
1999
+ }) {
2000
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2001
+ "div",
2002
+ {
2003
+ style: {
2004
+ fontSize: 10,
2005
+ color: t.textSubtle,
2006
+ textTransform: "uppercase",
2007
+ letterSpacing: "0.08em",
2008
+ fontWeight: 600,
2009
+ marginBottom: 4
2010
+ },
2011
+ children
2012
+ }
2013
+ );
2014
+ }
2015
+
2016
+ // src/panels/StageFlow.tsx
2017
+ var import_react3 = require("react");
2018
+ var import_react4 = require("@xyflow/react");
2019
+ var import_style = require("@xyflow/react/dist/style.css");
2020
+ var import_jsx_runtime3 = require("react/jsx-runtime");
2021
+ var NODE_POSITIONS = {
2022
+ user: { x: 100, y: 20 },
2023
+ agent: { x: 100, y: 160 },
2024
+ tool: { x: 100, y: 380 },
2025
+ skill: { x: 320, y: 380 }
2026
+ };
2027
+ function pickHandles(from, to) {
2028
+ if (from === "user" && to === "agent") return { sourceHandle: "b-out", targetHandle: "t-in" };
2029
+ if (from === "agent" && to === "user") return { sourceHandle: "t-out", targetHandle: "b-in" };
2030
+ if (from === "agent" && to === "tool") return { sourceHandle: "b-out", targetHandle: "t-in" };
2031
+ if (from === "tool" && to === "agent") return { sourceHandle: "t-out", targetHandle: "b-in" };
2032
+ if (from === "agent" && to === "skill") return { sourceHandle: "r-out", targetHandle: "l-in" };
2033
+ if (from === "skill" && to === "agent") return { sourceHandle: "l-out", targetHandle: "r-in" };
2034
+ return { sourceHandle: "r-out", targetHandle: "l-in" };
2035
+ }
2036
+ var NODE_LABELS = {
2037
+ user: "User",
2038
+ agent: "Agent",
2039
+ tool: "Tool",
2040
+ skill: "Skill"
2041
+ };
2042
+ var NODE_SUBLABELS = {
2043
+ user: "You",
2044
+ agent: "The LLM",
2045
+ tool: "Data source / action",
2046
+ // "Adds context + tools" is what the user actually observes when a
2047
+ // skill activates — the skill body lands in System Prompt and its
2048
+ // tool list surfaces in Tools. That's the whole effect; no need for
2049
+ // jargon like "bracket over a primitive."
2050
+ skill: "Adds context + tools"
2051
+ };
2052
+ function StageFlow({
2053
+ stages,
2054
+ focusIndex,
2055
+ onEdgeClick,
2056
+ height = 460,
2057
+ activeSkillId
2058
+ }) {
2059
+ const t = useLensTheme();
2060
+ const focus = focusIndex !== void 0 && focusIndex >= 0 ? focusIndex : stages.length - 1;
2061
+ const visible = (0, import_react3.useMemo)(() => stages.slice(0, focus + 1), [stages, focus]);
2062
+ const activeStage = visible[visible.length - 1];
2063
+ const touched = (0, import_react3.useMemo)(() => {
2064
+ const set = /* @__PURE__ */ new Set();
2065
+ for (const s of visible) {
2066
+ set.add(s.from);
2067
+ set.add(s.to);
2068
+ if (s.alsoLights) set.add(s.alsoLights);
2069
+ }
2070
+ return set;
2071
+ }, [visible]);
2072
+ const activeNodes = (0, import_react3.useMemo)(() => {
2073
+ const s = /* @__PURE__ */ new Set();
2074
+ if (activeStage) {
2075
+ s.add(activeStage.to);
2076
+ if (activeStage.alsoLights) s.add(activeStage.alsoLights);
2077
+ }
2078
+ return s;
2079
+ }, [activeStage]);
2080
+ const edges = (0, import_react3.useMemo)(() => {
2081
+ const byKey = /* @__PURE__ */ new Map();
2082
+ visible.forEach((s) => {
2083
+ byKey.set(`${s.from}\u2192${s.to}`, { from: s.from, to: s.to, lastStage: s });
2084
+ });
2085
+ return [...byKey.values()].map(({ from, to, lastStage }) => {
2086
+ const isActive = activeStage !== void 0 && from === activeStage.from && to === activeStage.to;
2087
+ const { sourceHandle, targetHandle } = pickHandles(from, to);
2088
+ const isLoop = to === "agent" && (from === "tool" || from === "skill");
2089
+ return {
2090
+ id: `${from}\u2192${to}`,
2091
+ source: from,
2092
+ target: to,
2093
+ sourceHandle,
2094
+ targetHandle,
2095
+ type: "labelled",
2096
+ // Only loop edges get the marching-ants animation.
2097
+ animated: isLoop,
2098
+ data: {
2099
+ primitive: lastStage.primitive,
2100
+ active: isActive,
2101
+ isLoop,
2102
+ stage: lastStage
2103
+ }
2104
+ };
2105
+ });
2106
+ }, [visible, activeStage]);
2107
+ const nodes = (0, import_react3.useMemo)(() => {
2108
+ return Object.keys(NODE_POSITIONS).filter((id) => {
2109
+ if (id === "skill") return touched.has("skill");
2110
+ return true;
2111
+ }).map((id) => ({
2112
+ id,
2113
+ type: "lens",
2114
+ position: NODE_POSITIONS[id],
2115
+ data: {
2116
+ id,
2117
+ active: activeNodes.has(id),
2118
+ touched: touched.has(id),
2119
+ // Which of the Agent's three ports actually MUTATED this step.
2120
+ // Multiple can be true at once (read_skill touches all three).
2121
+ ...id === "agent" && activeStage ? { activeMutations: activeStage.mutations } : {},
2122
+ // Skill annotation on the Agent node — tells the user which
2123
+ // skill is governing the current System Prompt + Tools. Pure
2124
+ // context signal; doesn't affect layout.
2125
+ ...id === "agent" && activeSkillId ? { activeSkillId } : {},
2126
+ // Tool node shows the SPECIFIC tool name that's currently
2127
+ // being called — debugging without this is guesswork ("we
2128
+ // called a tool — but which one?"). Only surfaces when the
2129
+ // active stage actually references a tool name.
2130
+ ...id === "tool" && activeStage?.toolName && (activeStage.from === "tool" || activeStage.to === "tool") ? {
2131
+ activeLabel: activeStage.toolName,
2132
+ ...activeStage.parallelCount ? { parallelCount: activeStage.parallelCount } : {}
2133
+ } : {}
2134
+ },
2135
+ draggable: false
2136
+ }));
2137
+ }, [activeNodes, touched, activeStage, activeSkillId]);
2138
+ const nodeTypes = (0, import_react3.useMemo)(() => ({ lens: LensNode }), []);
2139
+ const edgeTypes = (0, import_react3.useMemo)(() => ({ labelled: LensEdge(onEdgeClick) }), [onEdgeClick]);
2140
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2141
+ "div",
2142
+ {
2143
+ "data-fp-lens": "stage-flow",
2144
+ style: {
2145
+ height,
2146
+ background: t.bg,
2147
+ borderBottom: `1px solid ${t.border}`
2148
+ },
2149
+ children: [
2150
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EdgeMarkerDefs, {}),
2151
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2152
+ import_react4.ReactFlow,
2153
+ {
2154
+ nodes,
2155
+ edges,
2156
+ nodeTypes,
2157
+ edgeTypes,
2158
+ fitView: true,
2159
+ fitViewOptions: { padding: 0.15 },
2160
+ proOptions: { hideAttribution: true },
2161
+ nodesDraggable: false,
2162
+ nodesConnectable: false,
2163
+ elementsSelectable: false,
2164
+ panOnDrag: false,
2165
+ zoomOnScroll: false,
2166
+ zoomOnPinch: false,
2167
+ zoomOnDoubleClick: false,
2168
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2169
+ import_react4.Background,
2170
+ {
2171
+ variant: import_react4.BackgroundVariant.Dots,
2172
+ gap: 18,
2173
+ size: 1,
2174
+ color: t.border
2175
+ }
2176
+ )
2177
+ }
2178
+ )
2179
+ ]
2180
+ }
2181
+ );
2182
+ }
2183
+ function EdgeMarkerDefs() {
2184
+ const t = useLensTheme();
2185
+ const active = t.accent;
2186
+ const loop = `color-mix(in srgb, ${t.accent} 55%, ${t.border})`;
2187
+ const dim = t.border;
2188
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2189
+ "svg",
2190
+ {
2191
+ "aria-hidden": "true",
2192
+ style: { position: "absolute", width: 0, height: 0, pointerEvents: "none" },
2193
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("defs", { children: [
2194
+ { id: "lens-arrow-active", fill: active },
2195
+ { id: "lens-arrow-loop", fill: loop },
2196
+ { id: "lens-arrow-dim", fill: dim }
2197
+ ].map((m) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2198
+ "marker",
2199
+ {
2200
+ id: m.id,
2201
+ viewBox: "0 0 10 10",
2202
+ refX: "9",
2203
+ refY: "5",
2204
+ markerWidth: "6",
2205
+ markerHeight: "6",
2206
+ orient: "auto-start-reverse",
2207
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: m.fill })
2208
+ },
2209
+ m.id
2210
+ )) })
2211
+ }
2212
+ );
2213
+ }
2214
+ var NODE_KEYFRAMES_ID = "fp-lens-node-keyframes";
2215
+ var NODE_KEYFRAMES_CSS = `
2216
+ @media (prefers-reduced-motion: no-preference) {
2217
+ @keyframes fp-lens-pulse {
2218
+ 0%, 100% { opacity: 0.4; transform: scale(1); }
2219
+ 50% { opacity: 0.12; transform: scale(1.08); }
2220
+ }
2221
+ }
2222
+ @media (prefers-reduced-motion: reduce) {
2223
+ @keyframes fp-lens-pulse { 0%, 100% { opacity: 0.3; } }
2224
+ }
2225
+ `;
2226
+ function injectNodeKeyframes() {
2227
+ if (typeof document === "undefined") return;
2228
+ if (document.getElementById(NODE_KEYFRAMES_ID)) return;
2229
+ const el = document.createElement("style");
2230
+ el.id = NODE_KEYFRAMES_ID;
2231
+ el.textContent = NODE_KEYFRAMES_CSS;
2232
+ document.head.appendChild(el);
2233
+ }
2234
+ function LensNode({ data }) {
2235
+ const t = useLensTheme();
2236
+ const d = data;
2237
+ injectNodeKeyframes();
2238
+ const isActive = d.active;
2239
+ const isDone = !d.active && d.touched;
2240
+ const bg = isActive ? t.accent : isDone ? t.bgElev : t.bg;
2241
+ const border = isActive ? t.accent : isDone ? t.border : t.border;
2242
+ const textColor = isActive ? "#ffffff" : isDone ? t.text : t.textSubtle;
2243
+ 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)`;
2244
+ const label = NODE_LABELS[d.id];
2245
+ const sub = isActive && d.activeLabel ? d.activeLabel : NODE_SUBLABELS[d.id];
2246
+ const isAgent = d.id === "agent";
2247
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative", display: "inline-block" }, children: [
2248
+ isActive && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2249
+ "div",
2250
+ {
2251
+ style: {
2252
+ position: "absolute",
2253
+ inset: -6,
2254
+ borderRadius: 14,
2255
+ border: `2px solid ${t.accent}`,
2256
+ opacity: 0.35,
2257
+ pointerEvents: "none",
2258
+ animation: "fp-lens-pulse 1.6s ease-out infinite"
2259
+ }
2260
+ }
2261
+ ),
2262
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2263
+ "div",
2264
+ {
2265
+ style: {
2266
+ width: isAgent ? 200 : 150,
2267
+ padding: "12px 16px",
2268
+ borderRadius: 10,
2269
+ background: bg,
2270
+ border: `2px solid ${border}`,
2271
+ color: textColor,
2272
+ fontFamily: t.fontSans,
2273
+ textAlign: "center",
2274
+ boxShadow: shadow,
2275
+ transition: "background 220ms ease, border-color 220ms ease, box-shadow 220ms ease, color 220ms ease"
2276
+ },
2277
+ children: [
2278
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }, children: [
2279
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(NodeIcon, { id: d.id, color: textColor }),
2280
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 13, fontWeight: 600 }, children: label })
2281
+ ] }),
2282
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2283
+ "div",
2284
+ {
2285
+ style: {
2286
+ fontSize: isActive && d.activeLabel ? 11 : 10,
2287
+ color: isActive ? "rgba(255,255,255,0.95)" : t.textSubtle,
2288
+ marginTop: 2,
2289
+ fontWeight: isActive && d.activeLabel ? 600 : 400,
2290
+ fontFamily: isActive && d.activeLabel ? t.fontMono : t.fontSans,
2291
+ maxWidth: isAgent ? 180 : 130,
2292
+ overflow: "hidden",
2293
+ textOverflow: "ellipsis",
2294
+ whiteSpace: "nowrap",
2295
+ margin: "2px auto 0"
2296
+ },
2297
+ title: sub,
2298
+ children: sub
2299
+ }
2300
+ ),
2301
+ d.id === "tool" && isActive && (d.parallelCount ?? 0) > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2302
+ "div",
2303
+ {
2304
+ title: `This tool is one of ${d.parallelCount} called in parallel this round`,
2305
+ style: {
2306
+ marginTop: 4,
2307
+ alignSelf: "center",
2308
+ display: "inline-block",
2309
+ padding: "1px 7px",
2310
+ borderRadius: 999,
2311
+ background: "rgba(255,255,255,0.25)",
2312
+ color: "#ffffff",
2313
+ fontSize: 9,
2314
+ fontWeight: 700,
2315
+ letterSpacing: "0.08em",
2316
+ textTransform: "uppercase"
2317
+ },
2318
+ children: [
2319
+ "\u26A1 Parallel \xB7 ",
2320
+ d.parallelCount
2321
+ ]
2322
+ }
2323
+ ),
2324
+ isAgent && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2325
+ d.activeSkillId && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2326
+ "div",
2327
+ {
2328
+ title: `System Prompt + Tools are currently governed by the ${d.activeSkillId} skill`,
2329
+ style: {
2330
+ marginTop: 8,
2331
+ padding: "3px 9px",
2332
+ borderRadius: 999,
2333
+ background: isActive ? "rgba(255,255,255,0.18)" : t.bgElev,
2334
+ border: `1px solid ${isActive ? "rgba(255,255,255,0.35)" : t.border}`,
2335
+ color: isActive ? "#ffffff" : t.accent,
2336
+ fontSize: 10,
2337
+ fontFamily: t.fontMono,
2338
+ fontWeight: 600,
2339
+ whiteSpace: "nowrap",
2340
+ maxWidth: 180,
2341
+ overflow: "hidden",
2342
+ textOverflow: "ellipsis",
2343
+ display: "inline-block"
2344
+ },
2345
+ children: [
2346
+ "\u{1F4DA} ",
2347
+ d.activeSkillId
2348
+ ]
2349
+ }
2350
+ ),
2351
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2352
+ AgentPorts,
2353
+ {
2354
+ active: d.active,
2355
+ mutations: d.activeMutations,
2356
+ filledCard: isActive
2357
+ }
2358
+ )
2359
+ ] }),
2360
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Handle, { type: "source", position: import_react4.Position.Top, id: "t-out", style: handleStyle(-14, 0) }),
2361
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Handle, { type: "target", position: import_react4.Position.Top, id: "t-in", style: handleStyle(14, 0) }),
2362
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Handle, { type: "source", position: import_react4.Position.Bottom, id: "b-out", style: handleStyle(14, 0) }),
2363
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Handle, { type: "target", position: import_react4.Position.Bottom, id: "b-in", style: handleStyle(-14, 0) }),
2364
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Handle, { type: "source", position: import_react4.Position.Left, id: "l-out", style: handleStyle(0, 10) }),
2365
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Handle, { type: "target", position: import_react4.Position.Left, id: "l-in", style: handleStyle(0, -10) }),
2366
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Handle, { type: "source", position: import_react4.Position.Right, id: "r-out", style: handleStyle(0, -10) }),
2367
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Handle, { type: "target", position: import_react4.Position.Right, id: "r-in", style: handleStyle(0, 10) })
2368
+ ]
2369
+ }
2370
+ )
2371
+ ] });
2372
+ }
2373
+ function NodeIcon({ id, color }) {
2374
+ const size = 16;
2375
+ const props = {
2376
+ width: size,
2377
+ height: size,
2378
+ viewBox: `0 0 ${size} ${size}`,
2379
+ fill: "none",
2380
+ style: { flexShrink: 0 }
2381
+ };
2382
+ if (id === "user") {
2383
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { ...props, children: [
2384
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8", cy: "5", r: "2.5", stroke: color, strokeWidth: "1.5" }),
2385
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3.5 14C3.5 11 5.5 9 8 9S12.5 11 12.5 14", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" })
2386
+ ] });
2387
+ }
2388
+ if (id === "agent") {
2389
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { ...props, children: [
2390
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8", cy: "8", r: "6", stroke: color, strokeWidth: "1.5" }),
2391
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }),
2392
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8", cy: "9.5", r: "1", fill: color }),
2393
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "3.5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
2394
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12.5", y1: "4", x2: "11.2", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
2395
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "3.5", y1: "4", x2: "4.8", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
2396
+ ] });
2397
+ }
2398
+ if (id === "tool") {
2399
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { ...props, children: [
2400
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8", cy: "8", r: "3", stroke: color, strokeWidth: "1.5" }),
2401
+ [0, 45, 90, 135, 180, 225, 270, 315].map((angle) => {
2402
+ const rad = angle * Math.PI / 180;
2403
+ const x1 = 8 + Math.cos(rad) * 4.5;
2404
+ const y1 = 8 + Math.sin(rad) * 4.5;
2405
+ const x2 = 8 + Math.cos(rad) * 6;
2406
+ const y2 = 8 + Math.sin(rad) * 6;
2407
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2408
+ "line",
2409
+ {
2410
+ x1,
2411
+ y1,
2412
+ x2,
2413
+ y2,
2414
+ stroke: color,
2415
+ strokeWidth: "1.5",
2416
+ strokeLinecap: "round"
2417
+ },
2418
+ angle
2419
+ );
2420
+ })
2421
+ ] });
2422
+ }
2423
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { ...props, children: [
2424
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }),
2425
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5.5 7h4M5.5 9.5h4", stroke: color, strokeWidth: "1.2", strokeLinecap: "round" })
2426
+ ] });
2427
+ }
2428
+ function handleStyle(dx, dy) {
2429
+ return {
2430
+ ...HANDLE_STYLE,
2431
+ transform: `translate(${dx}px, ${dy}px)`
2432
+ };
2433
+ }
2434
+ function AgentPorts({
2435
+ active,
2436
+ mutations,
2437
+ filledCard
2438
+ }) {
2439
+ const t = useLensTheme();
2440
+ const litSP = active && mutations?.systemPrompt === true;
2441
+ const litMsg = active && mutations?.messages === true;
2442
+ const litTools = active && mutations?.tools === true;
2443
+ const spBadge = mutations?.systemPromptDeltaChars !== void 0 ? `+${mutations.systemPromptDeltaChars.toLocaleString()} chars` : null;
2444
+ const toolsBadge = (() => {
2445
+ const added = mutations?.toolsAdded ?? 0;
2446
+ const removed = mutations?.toolsRemoved ?? 0;
2447
+ if (added === 0 && removed === 0) return null;
2448
+ const bits = [];
2449
+ if (added > 0) bits.push(`+${added}`);
2450
+ if (removed > 0) bits.push(`-${removed}`);
2451
+ return bits.join(" / ");
2452
+ })();
2453
+ const ports = [
2454
+ {
2455
+ key: "system-prompt",
2456
+ label: "System Prompt",
2457
+ hint: "Instructions Neo runs on",
2458
+ lit: litSP,
2459
+ badge: litSP ? spBadge : null
2460
+ },
2461
+ {
2462
+ key: "message",
2463
+ label: "Messages",
2464
+ hint: "Conversation so far",
2465
+ lit: litMsg,
2466
+ badge: null
2467
+ },
2468
+ {
2469
+ key: "tool",
2470
+ label: "Tools",
2471
+ hint: "What Neo can call",
2472
+ lit: litTools,
2473
+ badge: litTools ? toolsBadge : null
2474
+ }
2475
+ ];
2476
+ const boxBg = filledCard ? "rgba(255,255,255,0.12)" : t.bg;
2477
+ const boxBorder = filledCard ? "rgba(255,255,255,0.25)" : t.border;
2478
+ const portIdle = filledCard ? "rgba(255,255,255,0.7)" : t.textMuted;
2479
+ const portLitBg = filledCard ? "rgba(255,255,255,0.25)" : `color-mix(in srgb, ${t.accent} 30%, transparent)`;
2480
+ const portLitColor = filledCard ? "#ffffff" : t.accent;
2481
+ const portLitBorder = filledCard ? "#ffffff" : t.accent;
2482
+ const portIdleBorder = filledCard ? "rgba(255,255,255,0.3)" : t.border;
2483
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2484
+ "div",
2485
+ {
2486
+ style: {
2487
+ display: "flex",
2488
+ flexDirection: "column",
2489
+ gap: 3,
2490
+ marginTop: 8,
2491
+ padding: 4,
2492
+ background: boxBg,
2493
+ border: `1px solid ${boxBorder}`,
2494
+ borderRadius: 6
2495
+ },
2496
+ children: ports.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2497
+ "div",
2498
+ {
2499
+ title: p.hint,
2500
+ style: {
2501
+ padding: "2px 8px",
2502
+ borderRadius: 3,
2503
+ background: p.lit ? portLitBg : "transparent",
2504
+ color: p.lit ? portLitColor : portIdle,
2505
+ fontSize: 10,
2506
+ fontWeight: p.lit ? 600 : 500,
2507
+ letterSpacing: "0.02em",
2508
+ textAlign: "left",
2509
+ fontFamily: t.fontSans,
2510
+ display: "flex",
2511
+ alignItems: "center",
2512
+ gap: 6,
2513
+ borderLeft: `2px solid ${p.lit ? portLitBorder : portIdleBorder}`
2514
+ },
2515
+ children: [
2516
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 8 }, children: "\u25B8" }),
2517
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { flex: 1 }, children: p.label }),
2518
+ p.badge && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2519
+ "span",
2520
+ {
2521
+ style: {
2522
+ fontSize: 9,
2523
+ padding: "0 4px",
2524
+ borderRadius: 3,
2525
+ background: filledCard ? "rgba(255,255,255,0.22)" : t.bg,
2526
+ color: p.lit ? portLitColor : portIdle,
2527
+ fontFamily: t.fontMono,
2528
+ fontWeight: 600
2529
+ },
2530
+ children: p.badge
2531
+ }
2532
+ )
2533
+ ]
2534
+ },
2535
+ p.key
2536
+ ))
2537
+ }
2538
+ );
2539
+ }
2540
+ var HANDLE_STYLE = {
2541
+ opacity: 0,
2542
+ pointerEvents: "none",
2543
+ width: 1,
2544
+ height: 1
2545
+ };
2546
+ function LensEdge(onEdgeClick) {
2547
+ return function LensEdgeInner({
2548
+ id,
2549
+ sourceX,
2550
+ sourceY,
2551
+ targetX,
2552
+ targetY,
2553
+ sourcePosition,
2554
+ targetPosition,
2555
+ data
2556
+ }) {
2557
+ const t = useLensTheme();
2558
+ const d = data;
2559
+ const [edgePath] = (0, import_react4.getSmoothStepPath)({
2560
+ sourceX,
2561
+ sourceY,
2562
+ sourcePosition,
2563
+ targetX,
2564
+ targetY,
2565
+ targetPosition,
2566
+ borderRadius: 10
2567
+ });
2568
+ const stroke = d.active ? t.accent : d.isLoop ? `color-mix(in srgb, ${t.accent} 55%, ${t.border})` : t.border;
2569
+ const strokeWidth = d.active ? 2.25 : d.isLoop ? 1.75 : 1.5;
2570
+ const strokeDasharray = d.isLoop ? "5 4" : void 0;
2571
+ const markerId = d.active ? "lens-arrow-active" : d.isLoop ? "lens-arrow-loop" : "lens-arrow-dim";
2572
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2573
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2574
+ import_react4.BaseEdge,
2575
+ {
2576
+ id,
2577
+ path: edgePath,
2578
+ style: {
2579
+ stroke,
2580
+ strokeWidth,
2581
+ strokeDasharray,
2582
+ filter: d.active ? `drop-shadow(0 0 6px color-mix(in srgb, ${t.accent} 50%, transparent))` : void 0,
2583
+ cursor: onEdgeClick ? "pointer" : "default"
2584
+ },
2585
+ markerEnd: `url(#${markerId})`
2586
+ }
2587
+ ),
2588
+ onEdgeClick && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2589
+ "path",
2590
+ {
2591
+ d: edgePath,
2592
+ fill: "none",
2593
+ stroke: "transparent",
2594
+ strokeWidth: 12,
2595
+ style: { cursor: "pointer" },
2596
+ onClick: () => onEdgeClick(d.stage)
2597
+ }
2598
+ )
2599
+ ] });
2600
+ };
2601
+ }
2602
+
2603
+ // src/panels/TimeTravel.tsx
2604
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2605
+ function TimeTravel({
2606
+ stages,
2607
+ focusIndex,
2608
+ onFocusChange,
2609
+ isLive
2610
+ }) {
2611
+ const t = useLensTheme();
2612
+ const max = Math.max(0, stages.length - 1);
2613
+ function step(delta) {
2614
+ const next = Math.min(max, Math.max(0, focusIndex + delta));
2615
+ onFocusChange(next);
2616
+ }
2617
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2618
+ "div",
2619
+ {
2620
+ "data-fp-lens": "time-travel",
2621
+ style: {
2622
+ // Frosted-glass oval pill, floating clear of the surrounding
2623
+ // surfaces. `color-mix` gives a translucent tint using the
2624
+ // current theme's elevated bg; `backdrop-filter` blurs
2625
+ // whatever's behind (the graph + ask card show through
2626
+ // softly). Margin creates air around the pill so it reads as
2627
+ // a distinct floating control, not a toolbar bar.
2628
+ display: "flex",
2629
+ alignItems: "center",
2630
+ gap: 10,
2631
+ padding: "8px 14px",
2632
+ margin: "10px 14px",
2633
+ background: `color-mix(in srgb, ${t.bgElev} 55%, transparent)`,
2634
+ backdropFilter: "blur(14px) saturate(140%)",
2635
+ WebkitBackdropFilter: "blur(14px) saturate(140%)",
2636
+ border: `1px solid color-mix(in srgb, ${t.border} 70%, transparent)`,
2637
+ borderRadius: 999,
2638
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.06)",
2639
+ fontFamily: t.fontSans
2640
+ },
2641
+ children: [
2642
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2643
+ "button",
2644
+ {
2645
+ onClick: () => step(-1),
2646
+ disabled: focusIndex <= 0 || stages.length === 0,
2647
+ style: btnStyle(t, false),
2648
+ title: "Previous step (\u2190)",
2649
+ children: "\u25C0"
2650
+ }
2651
+ ),
2652
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2653
+ "button",
2654
+ {
2655
+ onClick: () => step(1),
2656
+ disabled: focusIndex >= max || stages.length === 0,
2657
+ style: btnStyle(t, false),
2658
+ title: "Next step (\u2192)",
2659
+ children: "\u25B6"
2660
+ }
2661
+ ),
2662
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2663
+ "button",
2664
+ {
2665
+ onClick: () => onFocusChange(max),
2666
+ disabled: stages.length === 0 || isLive === true,
2667
+ style: btnStyle(t, isLive !== true && stages.length > 0),
2668
+ title: "Jump to latest step",
2669
+ children: "\u27F3 Live"
2670
+ }
2671
+ ),
2672
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2673
+ "input",
2674
+ {
2675
+ type: "range",
2676
+ min: 0,
2677
+ max,
2678
+ value: Math.min(focusIndex, max),
2679
+ onChange: (e) => onFocusChange(Number(e.target.value)),
2680
+ disabled: stages.length <= 1,
2681
+ style: { flex: 1, accentColor: t.accent, minWidth: 120 }
2682
+ }
2683
+ ),
2684
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2685
+ "div",
2686
+ {
2687
+ style: {
2688
+ fontSize: 11,
2689
+ color: t.textMuted,
2690
+ fontFamily: t.fontMono,
2691
+ whiteSpace: "nowrap",
2692
+ minWidth: 80,
2693
+ textAlign: "right"
2694
+ },
2695
+ children: stages.length === 0 ? "no steps yet" : `Step ${focusIndex + 1} / ${stages.length}`
2696
+ }
2697
+ )
2698
+ ]
2699
+ }
2700
+ );
2701
+ }
2702
+ function btnStyle(t, highlighted) {
2703
+ return {
2704
+ background: highlighted ? t.accent : `color-mix(in srgb, ${t.bg} 40%, transparent)`,
2705
+ color: highlighted ? "#fff" : t.textMuted,
2706
+ border: `1px solid color-mix(in srgb, ${t.border} 60%, transparent)`,
2707
+ borderRadius: 999,
2708
+ padding: "3px 12px",
2709
+ fontSize: 12,
2710
+ cursor: "pointer",
2711
+ width: "auto",
2712
+ fontWeight: 500,
2713
+ whiteSpace: "nowrap",
2714
+ transition: "background 140ms ease, border-color 140ms ease, color 140ms ease"
2715
+ };
2716
+ }
2717
+
2718
+ // src/panels/AskCard.tsx
2719
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2720
+ function AskCard({ timeline, focusIndex, stages }) {
2721
+ const t = useLensTheme();
2722
+ const currentStage = stages[focusIndex];
2723
+ const currentTurn = currentStage !== void 0 ? timeline.turns[currentStage.turnIndex] : timeline.turns[0];
2724
+ const currentIter = currentStage?.iterIndex !== void 0 ? currentTurn?.iterations.find((it) => it.index === currentStage.iterIndex) : void 0;
2725
+ const currentTool = currentStage?.toolName && currentIter ? currentIter.toolCalls.find((tc) => tc.name === currentStage.toolName) : void 0;
2726
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2727
+ "div",
2728
+ {
2729
+ "data-fp-lens": "ask-card",
2730
+ style: {
2731
+ display: "flex",
2732
+ flexDirection: "column",
2733
+ gap: 12,
2734
+ padding: 14,
2735
+ background: t.bg,
2736
+ fontFamily: t.fontSans,
2737
+ color: t.text,
2738
+ overflow: "auto"
2739
+ },
2740
+ children: [
2741
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { children: [
2742
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label3, { t, children: "Your question" }),
2743
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2744
+ "div",
2745
+ {
2746
+ style: {
2747
+ marginTop: 4,
2748
+ padding: "10px 12px",
2749
+ background: `color-mix(in srgb, ${t.accent} 12%, ${t.bgElev})`,
2750
+ border: `1px solid ${t.border}`,
2751
+ borderRadius: 6,
2752
+ fontSize: 13,
2753
+ lineHeight: 1.5
2754
+ },
2755
+ children: currentTurn?.userPrompt ?? "No question yet."
2756
+ }
2757
+ )
2758
+ ] }),
2759
+ currentStage && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { children: [
2760
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Label3, { t, children: [
2761
+ "Step ",
2762
+ focusIndex + 1,
2763
+ " / ",
2764
+ stages.length
2765
+ ] }),
2766
+ !(currentTool && currentStage.from === "tool") && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginTop: 4, fontSize: 13, color: t.text, lineHeight: 1.5 }, children: currentStage.label }),
2767
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2768
+ "div",
2769
+ {
2770
+ style: {
2771
+ marginTop: 8,
2772
+ display: "flex",
2773
+ gap: 6,
2774
+ flexWrap: "wrap",
2775
+ fontSize: 10,
2776
+ color: t.textSubtle
2777
+ },
2778
+ children: [
2779
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Pill, { t, children: primitivePill(currentStage) }),
2780
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Pill, { t, children: [
2781
+ friendlyNode(currentStage.from),
2782
+ " \u2192 ",
2783
+ friendlyNode(currentStage.to)
2784
+ ] }),
2785
+ currentStage.toolName && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Pill, { t, children: currentStage.toolName })
2786
+ ]
2787
+ }
2788
+ )
2789
+ ] }),
2790
+ currentStage?.from === "agent" && currentIter?.assistantContent && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { children: [
2791
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label3, { t, children: "Agent reasoning" }),
2792
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2793
+ "div",
2794
+ {
2795
+ style: {
2796
+ marginTop: 4,
2797
+ padding: "10px 12px",
2798
+ background: t.bgElev,
2799
+ border: `1px solid ${t.border}`,
2800
+ borderLeft: `3px solid ${t.accent}`,
2801
+ borderRadius: 6,
2802
+ fontSize: 12,
2803
+ lineHeight: 1.55,
2804
+ whiteSpace: "pre-wrap",
2805
+ maxHeight: 220,
2806
+ overflow: "auto"
2807
+ },
2808
+ children: currentIter.assistantContent
2809
+ }
2810
+ )
2811
+ ] }),
2812
+ currentTool && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { children: [
2813
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label3, { t, children: currentStage?.to === "tool" ? "Arguments" : "Tool returned" }),
2814
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2815
+ "pre",
2816
+ {
2817
+ style: {
2818
+ marginTop: 4,
2819
+ padding: "8px 10px",
2820
+ background: t.bgElev,
2821
+ border: `1px solid ${t.border}`,
2822
+ borderRadius: 6,
2823
+ fontSize: 11,
2824
+ fontFamily: t.fontMono,
2825
+ color: currentTool.error ? t.error : t.text,
2826
+ whiteSpace: "pre-wrap",
2827
+ maxHeight: 180,
2828
+ overflow: "auto",
2829
+ margin: 0
2830
+ },
2831
+ children: currentStage?.to === "tool" ? JSON.stringify(currentTool.arguments, null, 2) : currentTool.result
2832
+ }
2833
+ )
2834
+ ] }),
2835
+ timeline.turns.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { children: [
2836
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label3, { t, children: "Conversation" }),
2837
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { marginTop: 4, fontSize: 12, color: t.textMuted, lineHeight: 1.5 }, children: [
2838
+ timeline.turns.length,
2839
+ " question",
2840
+ timeline.turns.length === 1 ? "" : "s",
2841
+ " so far \xB7",
2842
+ " ",
2843
+ timeline.tools.length,
2844
+ " tool call",
2845
+ timeline.tools.length === 1 ? "" : "s",
2846
+ " total"
2847
+ ] })
2848
+ ] })
2849
+ ]
2850
+ }
2851
+ );
2852
+ }
2853
+ function Label3({
2854
+ t,
2855
+ children
2856
+ }) {
2857
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2858
+ "div",
2859
+ {
2860
+ style: {
2861
+ fontSize: 10,
2862
+ color: t.textSubtle,
2863
+ textTransform: "uppercase",
2864
+ letterSpacing: "0.08em",
2865
+ fontWeight: 600
2866
+ },
2867
+ children
2868
+ }
2869
+ );
2870
+ }
2871
+ function Pill({
2872
+ t,
2873
+ children,
2874
+ warn
2875
+ }) {
2876
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2877
+ "span",
2878
+ {
2879
+ style: {
2880
+ padding: "1px 6px",
2881
+ borderRadius: 3,
2882
+ background: warn ? `color-mix(in srgb, ${t.warning} 20%, transparent)` : t.bgElev,
2883
+ color: warn ? t.warning : t.textMuted,
2884
+ fontWeight: 600,
2885
+ textTransform: "uppercase",
2886
+ letterSpacing: "0.04em"
2887
+ },
2888
+ children
2889
+ }
2890
+ );
2891
+ }
2892
+ function friendlyNode(id) {
2893
+ if (!id) return id;
2894
+ return id.charAt(0).toUpperCase() + id.slice(1);
2895
+ }
2896
+ function primitivePill(stage) {
2897
+ if (stage.primitive === "system-prompt") return "System Prompt";
2898
+ if (stage.primitive === "message") return "Message";
2899
+ if (stage.toolKind === "skill") return "Tool (Skill)";
2900
+ if (stage.toolKind === "ask-human") return "Tool (Ask user)";
2901
+ return "Tool";
2902
+ }
2903
+
2904
+ // src/panels/RunSummary.tsx
2905
+ var import_react5 = require("react");
2906
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2907
+ function RunSummary({ timeline }) {
2908
+ const t = useLensTheme();
2909
+ const [open, setOpen] = (0, import_react5.useState)(false);
2910
+ const completedTurns = timeline.turns.filter((turn) => turn.finalContent !== "");
2911
+ if (completedTurns.length === 0) return null;
2912
+ const toolCounts = /* @__PURE__ */ new Map();
2913
+ for (const tc of timeline.tools) {
2914
+ const prev = toolCounts.get(tc.name) ?? { count: 0, totalMs: 0 };
2915
+ toolCounts.set(tc.name, {
2916
+ count: prev.count + 1,
2917
+ totalMs: prev.totalMs + (tc.durationMs ?? 0)
2918
+ });
2919
+ }
2920
+ const toolList = [...toolCounts.entries()].sort((a, b) => b[1].count - a[1].count);
2921
+ const activatedSkills = /* @__PURE__ */ new Set();
2922
+ for (const tc of timeline.tools) {
2923
+ if (tc.name === "read_skill") {
2924
+ const id = tc.arguments?.id;
2925
+ if (typeof id === "string") activatedSkills.add(id);
2926
+ }
2927
+ }
2928
+ const totalIn = timeline.turns.reduce((s, turn) => s + turn.totalInputTokens, 0);
2929
+ const totalOut = timeline.turns.reduce((s, turn) => s + turn.totalOutputTokens, 0);
2930
+ const totalMs = timeline.turns.reduce((s, turn) => s + turn.totalDurationMs, 0);
2931
+ const totalIters = timeline.turns.reduce((s, turn) => s + turn.iterations.length, 0);
2932
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2933
+ "div",
2934
+ {
2935
+ "data-fp-lens": "run-summary",
2936
+ style: {
2937
+ borderTop: `1px solid ${t.border}`,
2938
+ background: t.bgElev,
2939
+ fontFamily: t.fontSans
2940
+ },
2941
+ children: [
2942
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2943
+ "button",
2944
+ {
2945
+ onClick: () => setOpen((v) => !v),
2946
+ style: {
2947
+ display: "flex",
2948
+ alignItems: "center",
2949
+ gap: 8,
2950
+ width: "100%",
2951
+ padding: "8px 14px",
2952
+ background: "transparent",
2953
+ border: "none",
2954
+ color: t.textMuted,
2955
+ fontFamily: "inherit",
2956
+ fontSize: 12,
2957
+ cursor: "pointer",
2958
+ textAlign: "left",
2959
+ fontWeight: 400
2960
+ },
2961
+ children: [
2962
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { fontSize: 10 }, children: open ? "\u25BE" : "\u25B8" }),
2963
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2964
+ "span",
2965
+ {
2966
+ style: {
2967
+ fontSize: 10,
2968
+ color: t.textSubtle,
2969
+ textTransform: "uppercase",
2970
+ letterSpacing: "0.08em",
2971
+ fontWeight: 600
2972
+ },
2973
+ children: "Run summary"
2974
+ }
2975
+ ),
2976
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { flex: 1 } }),
2977
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: 11, color: t.textMuted }, children: [
2978
+ timeline.tools.length,
2979
+ " tool call",
2980
+ timeline.tools.length === 1 ? "" : "s",
2981
+ " \xB7",
2982
+ " ",
2983
+ activatedSkills.size,
2984
+ " skill",
2985
+ activatedSkills.size === 1 ? "" : "s",
2986
+ " \xB7",
2987
+ " ",
2988
+ totalIn.toLocaleString(),
2989
+ "\u2192",
2990
+ totalOut.toLocaleString(),
2991
+ " tok \xB7",
2992
+ " ",
2993
+ (totalMs / 1e3).toFixed(1),
2994
+ "s"
2995
+ ] })
2996
+ ]
2997
+ }
2998
+ ),
2999
+ open && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3000
+ "div",
3001
+ {
3002
+ style: {
3003
+ padding: "6px 14px 14px",
3004
+ display: "grid",
3005
+ gridTemplateColumns: "1fr 1fr",
3006
+ gap: 14,
3007
+ fontSize: 12,
3008
+ color: t.text
3009
+ },
3010
+ children: [
3011
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { children: [
3012
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Label4, { t, children: [
3013
+ "Tools used \xB7 ",
3014
+ timeline.tools.length
3015
+ ] }),
3016
+ toolList.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { color: t.textSubtle, fontSize: 11, fontStyle: "italic" }, children: "None." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { style: { margin: 0, padding: 0, listStyle: "none" }, children: toolList.map(([name, stats]) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3017
+ "li",
3018
+ {
3019
+ style: {
3020
+ display: "flex",
3021
+ gap: 8,
3022
+ padding: "3px 0",
3023
+ fontFamily: t.fontMono,
3024
+ fontSize: 11
3025
+ },
3026
+ children: [
3027
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: t.accent, flex: 1 }, children: name }),
3028
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { color: t.textMuted }, children: [
3029
+ "\xD7",
3030
+ stats.count
3031
+ ] }),
3032
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: t.textSubtle, minWidth: 60, textAlign: "right" }, children: stats.totalMs > 0 ? `${Math.round(stats.totalMs)}ms` : "\u2014" })
3033
+ ]
3034
+ },
3035
+ name
3036
+ )) })
3037
+ ] }),
3038
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { children: [
3039
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Label4, { t, children: [
3040
+ "Skills activated \xB7 ",
3041
+ activatedSkills.size
3042
+ ] }),
3043
+ activatedSkills.size === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { color: t.textSubtle, fontSize: 11, fontStyle: "italic" }, children: "None." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { style: { margin: 0, padding: 0, listStyle: "none" }, children: [...activatedSkills].map((id) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3044
+ "li",
3045
+ {
3046
+ style: {
3047
+ padding: "3px 0",
3048
+ fontFamily: t.fontMono,
3049
+ fontSize: 11,
3050
+ color: t.text
3051
+ },
3052
+ children: id
3053
+ },
3054
+ id
3055
+ )) }),
3056
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { marginTop: 10 }, children: [
3057
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Label4, { t, children: "Totals" }),
3058
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3059
+ "div",
3060
+ {
3061
+ style: {
3062
+ display: "grid",
3063
+ gridTemplateColumns: "auto 1fr",
3064
+ gap: "3px 10px",
3065
+ marginTop: 4,
3066
+ fontSize: 11,
3067
+ color: t.textMuted
3068
+ },
3069
+ children: [
3070
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Tokens" }),
3071
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { color: t.text, fontFamily: t.fontMono }, children: [
3072
+ totalIn.toLocaleString(),
3073
+ " \u2192 ",
3074
+ totalOut.toLocaleString()
3075
+ ] }),
3076
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Wall time" }),
3077
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { color: t.text, fontFamily: t.fontMono }, children: [
3078
+ (totalMs / 1e3).toFixed(2),
3079
+ "s"
3080
+ ] }),
3081
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "LLM calls" }),
3082
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: t.text, fontFamily: t.fontMono }, children: totalIters }),
3083
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Turns" }),
3084
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { color: t.text, fontFamily: t.fontMono }, children: completedTurns.length })
3085
+ ]
3086
+ }
3087
+ )
3088
+ ] })
3089
+ ] })
3090
+ ]
3091
+ }
3092
+ )
3093
+ ]
3094
+ }
3095
+ );
3096
+ }
3097
+ function Label4({
3098
+ t,
3099
+ children
3100
+ }) {
3101
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3102
+ "div",
3103
+ {
3104
+ style: {
3105
+ fontSize: 10,
3106
+ color: t.textSubtle,
3107
+ textTransform: "uppercase",
3108
+ letterSpacing: "0.08em",
3109
+ fontWeight: 600,
3110
+ marginBottom: 4
3111
+ },
3112
+ children
3113
+ }
3114
+ );
3115
+ }
3116
+
3117
+ // src/adapters/deriveStages.ts
3118
+ function deriveStages(timeline) {
3119
+ const stages = [];
3120
+ let idx = 0;
3121
+ const push = (s) => {
3122
+ stages.push({ ...s, index: idx++ });
3123
+ };
3124
+ let pending = null;
3125
+ const consumePending = (base) => {
3126
+ if (!pending) return base;
3127
+ const merged = { ...base, ...pending };
3128
+ pending = null;
3129
+ return merged;
3130
+ };
3131
+ for (const turn of timeline.turns) {
3132
+ push({
3133
+ from: "user",
3134
+ to: "agent",
3135
+ label: truncate(turn.userPrompt, 60),
3136
+ primitive: "message",
3137
+ turnIndex: turn.index,
3138
+ mutations: consumePending({ messages: true })
3139
+ });
3140
+ for (const iter of turn.iterations) {
3141
+ const parallelCount = iter.toolCalls.length > 1 ? iter.toolCalls.length : void 0;
3142
+ for (const tc of iter.toolCalls) {
3143
+ const kind = classifyTool(tc);
3144
+ if (kind === "skill-management") {
3145
+ push({
3146
+ from: "agent",
3147
+ to: "tool",
3148
+ alsoLights: "skill",
3149
+ label: skillManagementLabel(tc),
3150
+ primitive: "tool",
3151
+ turnIndex: turn.index,
3152
+ iterIndex: iter.index,
3153
+ toolName: tc.name,
3154
+ toolKind: "skill",
3155
+ mutations: consumePending({ messages: true })
3156
+ });
3157
+ const isReadSkill = tc.name === "read_skill";
3158
+ push({
3159
+ from: "tool",
3160
+ to: "agent",
3161
+ alsoLights: "skill",
3162
+ label: isReadSkill ? `Skill body delivered (+${tc.result.length} chars) \u2014 will activate next step` : `Skills list (${countSkills(tc.result)} skills)`,
3163
+ primitive: "tool",
3164
+ turnIndex: turn.index,
3165
+ iterIndex: iter.index,
3166
+ toolName: tc.name,
3167
+ toolKind: "skill",
3168
+ mutations: { messages: true }
3169
+ });
3170
+ if (isReadSkill) {
3171
+ const spDelta = tc.result.length;
3172
+ const activatedSkillId = tc.arguments?.id ?? void 0;
3173
+ pending = {
3174
+ systemPrompt: true,
3175
+ tools: true,
3176
+ systemPromptDeltaChars: spDelta,
3177
+ // Stash the ACTUAL content that just entered the System
3178
+ // Prompt + the skill id that activated. These ride along
3179
+ // `pending` and land on the next outgoing LLM edge where
3180
+ // the mutation is attributed, so the UI can render a
3181
+ // real diff instead of a "not captured" placeholder.
3182
+ systemPromptAdded: tc.result,
3183
+ ...activatedSkillId ? { activatedSkillId } : {}
3184
+ };
3185
+ }
3186
+ } else if (kind === "ask-human") {
3187
+ push({
3188
+ from: "agent",
3189
+ to: "user",
3190
+ label: askHumanLabel(tc),
3191
+ primitive: "message",
3192
+ turnIndex: turn.index,
3193
+ iterIndex: iter.index,
3194
+ toolName: tc.name,
3195
+ toolKind: "ask-human",
3196
+ mutations: consumePending({ messages: true })
3197
+ });
3198
+ } else {
3199
+ push({
3200
+ from: "agent",
3201
+ to: "tool",
3202
+ label: `Called ${tc.name}`,
3203
+ primitive: "tool",
3204
+ turnIndex: turn.index,
3205
+ iterIndex: iter.index,
3206
+ toolName: tc.name,
3207
+ ...parallelCount ? { parallelCount } : {},
3208
+ mutations: consumePending({ messages: true })
3209
+ });
3210
+ push({
3211
+ from: "tool",
3212
+ to: "agent",
3213
+ label: truncate(tc.result, 80),
3214
+ primitive: "tool",
3215
+ turnIndex: turn.index,
3216
+ iterIndex: iter.index,
3217
+ toolName: tc.name,
3218
+ ...parallelCount ? { parallelCount } : {},
3219
+ mutations: { messages: true }
3220
+ });
3221
+ }
3222
+ }
3223
+ }
3224
+ if (turn.finalContent) {
3225
+ const lastIter = turn.iterations[turn.iterations.length - 1];
3226
+ push({
3227
+ from: "agent",
3228
+ to: "user",
3229
+ label: truncate(turn.finalContent, 80),
3230
+ primitive: "message",
3231
+ turnIndex: turn.index,
3232
+ ...lastIter ? { iterIndex: lastIter.index } : {},
3233
+ mutations: consumePending({ messages: true })
3234
+ });
3235
+ }
3236
+ }
3237
+ return stages;
3238
+ }
3239
+ function classifyTool(tc) {
3240
+ if (tc.name === "list_skills" || tc.name === "read_skill") return "skill-management";
3241
+ if (tc.name === "ask_human" || tc.name === "ask_user") return "ask-human";
3242
+ return "regular";
3243
+ }
3244
+ function skillManagementLabel(tc) {
3245
+ if (tc.name === "list_skills") return "Asked what skills are available";
3246
+ if (tc.name === "read_skill") {
3247
+ const id = tc.arguments?.id ?? "?";
3248
+ return `Activated the "${id}" skill`;
3249
+ }
3250
+ return `Called ${tc.name}`;
3251
+ }
3252
+ function askHumanLabel(tc) {
3253
+ const q = tc.arguments?.question ?? "";
3254
+ return q ? `Asked user: ${truncate(q, 50)}` : "Asked user for clarification";
3255
+ }
3256
+ function countSkills(result) {
3257
+ try {
3258
+ const parsed = JSON.parse(result);
3259
+ if (Array.isArray(parsed?.skills)) return parsed.skills.length;
3260
+ if (Array.isArray(parsed)) return parsed.length;
3261
+ } catch {
3262
+ }
3263
+ return 0;
3264
+ }
3265
+ function truncate(s, max) {
3266
+ const one = s.replace(/\s+/g, " ").trim();
3267
+ return one.length <= max ? one : one.slice(0, max - 1).trim() + "\u2026";
3268
+ }
3269
+
3270
+ // src/AgentLens.tsx
3271
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3272
+ function AgentLens({
3273
+ runtimeSnapshot,
3274
+ timeline: providedTimeline,
3275
+ systemPrompt,
3276
+ onToolCallClick,
3277
+ skills,
3278
+ activeSkillId
3279
+ }) {
3280
+ const t = useLensTheme();
3281
+ const timeline = (0, import_react6.useMemo)(() => {
3282
+ if (providedTimeline) return providedTimeline;
3283
+ if (!runtimeSnapshot) return null;
3284
+ return fromAgentSnapshot(runtimeSnapshot);
3285
+ }, [providedTimeline, runtimeSnapshot]);
3286
+ const [selectedIterKey, setSelectedIterKey] = (0, import_react6.useState)(null);
3287
+ const [skillsOpen, setSkillsOpen] = (0, import_react6.useState)(false);
3288
+ const derivedActiveSkill = activeSkillId ?? (typeof timeline?.finalDecision?.currentSkill === "string" ? timeline.finalDecision.currentSkill : null);
3289
+ (0, import_react6.useEffect)(() => {
3290
+ if (!skillsOpen) return;
3291
+ const onKey = (e) => {
3292
+ if (e.key === "Escape") setSkillsOpen(false);
3293
+ };
3294
+ window.addEventListener("keydown", onKey);
3295
+ return () => window.removeEventListener("keydown", onKey);
3296
+ }, [skillsOpen]);
3297
+ const handleToolClick = (0, import_react6.useCallback)(
3298
+ (inv) => {
3299
+ setSelectedIterKey(`${inv.turnIndex}.${inv.iterationIndex}`);
3300
+ onToolCallClick?.(inv);
3301
+ },
3302
+ [onToolCallClick]
3303
+ );
3304
+ if (!timeline) {
3305
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3306
+ "div",
3307
+ {
3308
+ "data-fp-lens": "empty",
3309
+ style: {
3310
+ padding: 32,
3311
+ color: t.textMuted,
3312
+ fontFamily: t.fontSans,
3313
+ textAlign: "center",
3314
+ background: t.bg
3315
+ },
3316
+ children: [
3317
+ "No agent run to show yet. Pass ",
3318
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { children: "runtimeSnapshot" }),
3319
+ " after the agent runs."
3320
+ ]
3321
+ }
3322
+ );
3323
+ }
3324
+ const derivedSystemPrompt = systemPrompt ?? (typeof timeline.rawSnapshot?.sharedState?.systemPrompt === "string" ? timeline.rawSnapshot.sharedState.systemPrompt : void 0);
3325
+ const stages = (0, import_react6.useMemo)(() => timeline ? deriveStages(timeline) : [], [timeline]);
3326
+ const [focusIndex, setFocusIndex] = (0, import_react6.useState)(-1);
3327
+ const liveIndex = stages.length - 1;
3328
+ const resolvedFocus = focusIndex === -1 ? liveIndex : Math.min(focusIndex, liveIndex);
3329
+ const isLive = focusIndex === -1;
3330
+ const liveIndexRef = (0, import_react6.useRef)(liveIndex);
3331
+ liveIndexRef.current = liveIndex;
3332
+ const handleFocusChange = (0, import_react6.useCallback)((i) => {
3333
+ setFocusIndex(i >= liveIndexRef.current ? -1 : i);
3334
+ }, []);
3335
+ (0, import_react6.useEffect)(() => {
3336
+ if (stages.length === 0) return;
3337
+ const onKey = (e) => {
3338
+ if (skillsOpen) return;
3339
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
3340
+ const tgt = e.target;
3341
+ const tag = tgt?.tagName;
3342
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || tgt?.isContentEditable) return;
3343
+ const delta = e.key === "ArrowLeft" ? -1 : 1;
3344
+ const next = Math.min(
3345
+ liveIndex,
3346
+ Math.max(0, resolvedFocus + delta)
3347
+ );
3348
+ handleFocusChange(next);
3349
+ e.preventDefault();
3350
+ };
3351
+ window.addEventListener("keydown", onKey);
3352
+ return () => window.removeEventListener("keydown", onKey);
3353
+ }, [resolvedFocus, liveIndex, stages.length, skillsOpen]);
3354
+ const handleEdgeClick = (0, import_react6.useCallback)(
3355
+ (stage) => {
3356
+ if (stage.iterIndex === void 0) return;
3357
+ setSelectedIterKey(`${stage.turnIndex}.${stage.iterIndex}`);
3358
+ handleFocusChange(stage.index);
3359
+ },
3360
+ [handleFocusChange]
3361
+ );
3362
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3363
+ "div",
3364
+ {
3365
+ "data-fp-lens": "shell",
3366
+ style: {
3367
+ display: "grid",
3368
+ // Top: TimeTravel slider (tt) + Skills button trailing on the
3369
+ // right. Middle-left: vertical StageFlow. Middle-right: AskCard
3370
+ // with current question + active step detail. Below: MessagesPanel
3371
+ // full width. Footer: RunSummary (only after first turn completes).
3372
+ gridTemplateColumns: "minmax(320px, 1fr) minmax(220px, 320px)",
3373
+ gridTemplateRows: "auto auto minmax(0, 1fr) auto",
3374
+ gridTemplateAreas: '"tt tt" "graph ask" "messages messages" "summary summary"',
3375
+ // Self-constraining sizing contract — Lens never requires the
3376
+ // host to get their flex chain exactly right.
3377
+ //
3378
+ // • `maxHeight: 100dvh` is the hard cap. Even if NOTHING
3379
+ // upstream delivers a bounded height, the shell can never
3380
+ // exceed the viewport. This is what makes the grid's
3381
+ // `minmax(0, 1fr)` messages row actually resolve to a
3382
+ // bounded number of pixels instead of expanding to content.
3383
+ // • `height: 100%` + `flex: 1 1 0%` let well-constrained
3384
+ // hosts still shrink Lens below the viewport cap when they
3385
+ // want a narrower sidebar.
3386
+ // • `overflow: hidden` clips children that would otherwise
3387
+ // blow past the grid and make scrollHeight == clientHeight
3388
+ // on MessagesPanel (the exact bug this block prevents).
3389
+ // • `minHeight: 0` unblocks grid + flex size resolution.
3390
+ flex: "1 1 0%",
3391
+ minHeight: 0,
3392
+ height: "100%",
3393
+ maxHeight: "100dvh",
3394
+ overflow: "hidden",
3395
+ background: t.bg,
3396
+ color: t.text,
3397
+ fontFamily: t.fontSans
3398
+ },
3399
+ children: [
3400
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `
3401
+ [data-iter-selected="true"] {
3402
+ outline-color: currentColor !important;
3403
+ background: color-mix(in srgb, currentColor 8%, transparent);
3404
+ }
3405
+ ` }),
3406
+ stages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3407
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3408
+ "div",
3409
+ {
3410
+ style: {
3411
+ gridArea: "tt",
3412
+ display: "flex",
3413
+ alignItems: "center"
3414
+ },
3415
+ children: [
3416
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3417
+ TimeTravel,
3418
+ {
3419
+ stages,
3420
+ focusIndex: resolvedFocus,
3421
+ onFocusChange: handleFocusChange,
3422
+ isLive
3423
+ }
3424
+ ) }),
3425
+ skills && skills.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3426
+ "button",
3427
+ {
3428
+ onClick: () => setSkillsOpen(true),
3429
+ title: "See all skills registered with the agent",
3430
+ style: {
3431
+ // Borderless — rides on the same airy feel as the
3432
+ // floating time-travel pill. A faint translucent fill
3433
+ // on hover keeps it discoverable.
3434
+ background: "transparent",
3435
+ border: "none",
3436
+ color: t.textMuted,
3437
+ padding: "0 16px",
3438
+ margin: "0 14px 0 0",
3439
+ fontSize: 12,
3440
+ cursor: "pointer",
3441
+ whiteSpace: "nowrap",
3442
+ display: "flex",
3443
+ alignItems: "center",
3444
+ gap: 6,
3445
+ fontWeight: 400,
3446
+ width: "auto"
3447
+ },
3448
+ children: [
3449
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", children: "\u{1F4DA}" }),
3450
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
3451
+ "Skills \xB7 ",
3452
+ skills.length
3453
+ ] }),
3454
+ derivedActiveSkill && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3455
+ "span",
3456
+ {
3457
+ style: {
3458
+ fontSize: 9,
3459
+ padding: "1px 5px",
3460
+ borderRadius: 3,
3461
+ background: `color-mix(in srgb, ${t.success} 25%, transparent)`,
3462
+ color: t.success,
3463
+ fontWeight: 600,
3464
+ textTransform: "uppercase"
3465
+ },
3466
+ children: derivedActiveSkill
3467
+ }
3468
+ )
3469
+ ]
3470
+ }
3471
+ )
3472
+ ]
3473
+ }
3474
+ ),
3475
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3476
+ "div",
3477
+ {
3478
+ style: {
3479
+ gridArea: "graph",
3480
+ minHeight: 0,
3481
+ overflow: "hidden",
3482
+ borderRight: `1px solid ${t.border}`
3483
+ },
3484
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3485
+ StageFlow,
3486
+ {
3487
+ stages,
3488
+ focusIndex: resolvedFocus,
3489
+ onEdgeClick: handleEdgeClick,
3490
+ activeSkillId: derivedActiveSkill
3491
+ }
3492
+ )
3493
+ }
3494
+ ),
3495
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3496
+ "div",
3497
+ {
3498
+ style: {
3499
+ gridArea: "ask",
3500
+ minHeight: 0,
3501
+ overflow: "hidden",
3502
+ background: t.bg
3503
+ },
3504
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3505
+ AskCard,
3506
+ {
3507
+ timeline,
3508
+ stages,
3509
+ focusIndex: resolvedFocus
3510
+ }
3511
+ )
3512
+ }
3513
+ )
3514
+ ] }),
3515
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3516
+ "div",
3517
+ {
3518
+ style: {
3519
+ gridArea: "messages",
3520
+ minHeight: 0,
3521
+ overflow: "hidden",
3522
+ // `position: relative` is the containing block for the
3523
+ // absolutely-positioned MessagesPanel inside. This is the
3524
+ // mechanism that forces the panel's height to match this
3525
+ // cell exactly, no matter what the upstream flex/grid
3526
+ // chain does.
3527
+ position: "relative"
3528
+ },
3529
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3530
+ MessagesPanel,
3531
+ {
3532
+ timeline,
3533
+ onToolCallClick: handleToolClick,
3534
+ selectedIterKey,
3535
+ stages,
3536
+ focusIndex: resolvedFocus,
3537
+ onFocusChange: handleFocusChange,
3538
+ isLive,
3539
+ ...derivedSystemPrompt && { systemPrompt: derivedSystemPrompt }
3540
+ }
3541
+ )
3542
+ }
3543
+ ),
3544
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { gridArea: "summary" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RunSummary, { timeline }) }),
3545
+ skillsOpen && skills && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3546
+ SkillsPanel,
3547
+ {
3548
+ skills,
3549
+ activeSkillId: derivedActiveSkill,
3550
+ onClose: () => setSkillsOpen(false)
3551
+ }
3552
+ )
3553
+ ]
3554
+ }
3555
+ );
3556
+ }
3557
+
3558
+ // src/panels/IterationStrip.tsx
3559
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3560
+ function IterationStrip({ timeline, selectedKey, onSelect }) {
836
3561
  const t = useLensTheme();
837
3562
  const chips = timeline.turns.flatMap(
838
3563
  (turn) => turn.iterations.map((iter, stepIdx) => ({
@@ -845,7 +3570,7 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
845
3570
  toolCount: iter.toolCalls.length
846
3571
  }))
847
3572
  );
848
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3573
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
849
3574
  "div",
850
3575
  {
851
3576
  "data-fp-lens": "iteration-strip",
@@ -858,12 +3583,12 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
858
3583
  background: t.bgElev
859
3584
  },
860
3585
  children: [
861
- chips.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: t.textSubtle, fontSize: 11 }, children: "No iterations yet." }),
3586
+ chips.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { color: t.textSubtle, fontSize: 11 }, children: "No iterations yet." }),
862
3587
  chips.map((c) => {
863
3588
  const active = c.key === selectedKey;
864
3589
  const isFinal = c.toolCount === 0;
865
3590
  const secs = c.durationMs >= 1e3 ? `${(c.durationMs / 1e3).toFixed(1)}s` : `${Math.round(c.durationMs)}ms`;
866
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3591
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
867
3592
  "button",
868
3593
  {
869
3594
  onClick: () => onSelect?.(c.key),
@@ -884,15 +3609,15 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
884
3609
  },
885
3610
  title: `Question ${c.turn} \xB7 Step ${c.step} \xB7 ${secs}${c.stopReason ? ` \xB7 stop: ${c.stopReason}` : ""}`,
886
3611
  children: [
887
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { fontWeight: 600 }, children: [
3612
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { style: { fontWeight: 600 }, children: [
888
3613
  "Step ",
889
3614
  c.step
890
3615
  ] }),
891
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { opacity: 0.85 }, children: [
3616
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { style: { opacity: 0.85 }, children: [
892
3617
  " \xB7 ",
893
3618
  c.label
894
3619
  ] }),
895
- isFinal && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { marginLeft: 6 }, children: "\u2713" })
3620
+ isFinal && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { marginLeft: 6 }, children: "\u2713" })
896
3621
  ]
897
3622
  },
898
3623
  c.key
@@ -911,23 +3636,26 @@ function stepHeadline(iter) {
911
3636
  const id = tc.arguments?.id;
912
3637
  return id ? `Activated ${id}` : "Activating skill";
913
3638
  }
914
- return `Called ${tc.name}`;
3639
+ if (tc.name === "ask_human" || tc.name === "ask_user") {
3640
+ return "Asked user";
3641
+ }
3642
+ return `Called tool (${tc.name})`;
915
3643
  }
916
3644
  if (iter.toolCalls.length <= 3) {
917
- return `Called ${iter.toolCalls.map((tc) => tc.name).join(", ")}`;
3645
+ return `Called ${iter.toolCalls.length} tools (${iter.toolCalls.map((tc) => tc.name).join(", ")})`;
918
3646
  }
919
3647
  return `Called ${iter.toolCalls.length} tools in parallel`;
920
3648
  }
921
3649
 
922
3650
  // src/panels/ToolCallInspector.tsx
923
- var import_jsx_runtime3 = require("react/jsx-runtime");
3651
+ var import_jsx_runtime9 = require("react/jsx-runtime");
924
3652
  function ToolCallInspector({
925
3653
  timeline,
926
3654
  selectedId,
927
3655
  onSelect
928
3656
  }) {
929
3657
  const t = useLensTheme();
930
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3658
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
931
3659
  "div",
932
3660
  {
933
3661
  "data-fp-lens": "tool-call-inspector",
@@ -941,7 +3669,7 @@ function ToolCallInspector({
941
3669
  overflow: "hidden"
942
3670
  },
943
3671
  children: [
944
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3672
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
945
3673
  "div",
946
3674
  {
947
3675
  style: {
@@ -960,12 +3688,12 @@ function ToolCallInspector({
960
3688
  ]
961
3689
  }
962
3690
  ),
963
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { overflow: "auto", flex: 1 }, children: [
964
- timeline.tools.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "Neo hasn't called any tools yet." }),
3691
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { overflow: "auto", flex: 1 }, children: [
3692
+ timeline.tools.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "Neo hasn't called any tools yet." }),
965
3693
  timeline.tools.map((tc) => {
966
3694
  const active = tc.id === selectedId;
967
3695
  const errored = tc.error === true;
968
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3696
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
969
3697
  "button",
970
3698
  {
971
3699
  onClick: () => onSelect?.(tc),
@@ -985,7 +3713,7 @@ function ToolCallInspector({
985
3713
  fontFamily: "inherit"
986
3714
  },
987
3715
  children: [
988
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3716
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
989
3717
  "div",
990
3718
  {
991
3719
  style: {
@@ -996,8 +3724,8 @@ function ToolCallInspector({
996
3724
  fontFamily: t.fontMono
997
3725
  },
998
3726
  children: [
999
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: tc.name }),
1000
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3727
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: tc.name }),
3728
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1001
3729
  "span",
1002
3730
  {
1003
3731
  style: { color: t.textSubtle, fontSize: 10, marginLeft: "auto" },
@@ -1013,7 +3741,7 @@ function ToolCallInspector({
1013
3741
  ]
1014
3742
  }
1015
3743
  ),
1016
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3744
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1017
3745
  "div",
1018
3746
  {
1019
3747
  style: {
@@ -1049,111 +3777,6 @@ function shortArgs2(args) {
1049
3777
  }).join(", ");
1050
3778
  }
1051
3779
 
1052
- // src/AgentLens.tsx
1053
- var import_jsx_runtime4 = require("react/jsx-runtime");
1054
- function AgentLens({
1055
- runtimeSnapshot,
1056
- timeline: providedTimeline,
1057
- systemPrompt,
1058
- onToolCallClick
1059
- }) {
1060
- const t = useLensTheme();
1061
- const timeline = (0, import_react2.useMemo)(() => {
1062
- if (providedTimeline) return providedTimeline;
1063
- if (!runtimeSnapshot) return null;
1064
- return fromAgentSnapshot(runtimeSnapshot);
1065
- }, [providedTimeline, runtimeSnapshot]);
1066
- const [selectedToolId, setSelectedToolId] = (0, import_react2.useState)(null);
1067
- const [selectedIterKey, setSelectedIterKey] = (0, import_react2.useState)(null);
1068
- function handleToolClick(inv) {
1069
- setSelectedToolId(inv.id);
1070
- setSelectedIterKey(`${inv.turnIndex}.${inv.iterationIndex}`);
1071
- onToolCallClick?.(inv);
1072
- }
1073
- if (!timeline) {
1074
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1075
- "div",
1076
- {
1077
- "data-fp-lens": "empty",
1078
- style: {
1079
- padding: 32,
1080
- color: t.textMuted,
1081
- fontFamily: t.fontSans,
1082
- textAlign: "center",
1083
- background: t.bg
1084
- },
1085
- children: [
1086
- "No agent run to show yet. Pass ",
1087
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: "runtimeSnapshot" }),
1088
- " after the agent runs."
1089
- ]
1090
- }
1091
- );
1092
- }
1093
- const derivedSystemPrompt = systemPrompt ?? (typeof timeline.rawSnapshot?.sharedState?.systemPrompt === "string" ? timeline.rawSnapshot.sharedState.systemPrompt : void 0);
1094
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1095
- "div",
1096
- {
1097
- "data-fp-lens": "shell",
1098
- style: {
1099
- display: "grid",
1100
- gridTemplateColumns: "1fr 320px",
1101
- gridTemplateRows: "auto 1fr",
1102
- gridTemplateAreas: '"strip strip" "messages inspector"',
1103
- height: "100%",
1104
- minHeight: 0,
1105
- background: t.bg,
1106
- color: t.text,
1107
- fontFamily: t.fontSans
1108
- },
1109
- children: [
1110
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
1111
- [data-iter-selected="true"] {
1112
- outline-color: currentColor !important;
1113
- background: color-mix(in srgb, currentColor 8%, transparent);
1114
- }
1115
- ` }),
1116
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "strip" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1117
- IterationStrip,
1118
- {
1119
- timeline,
1120
- selectedKey: selectedIterKey,
1121
- onSelect: setSelectedIterKey
1122
- }
1123
- ) }),
1124
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "messages", minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1125
- MessagesPanel,
1126
- {
1127
- timeline,
1128
- onToolCallClick: handleToolClick,
1129
- selectedIterKey,
1130
- ...derivedSystemPrompt && { systemPrompt: derivedSystemPrompt }
1131
- }
1132
- ) }),
1133
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1134
- "div",
1135
- {
1136
- style: {
1137
- gridArea: "inspector",
1138
- minHeight: 0,
1139
- overflow: "hidden",
1140
- borderLeft: `1px solid ${t.border}`
1141
- },
1142
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1143
- ToolCallInspector,
1144
- {
1145
- timeline,
1146
- selectedId: selectedToolId,
1147
- onSelect: handleToolClick
1148
- }
1149
- )
1150
- }
1151
- )
1152
- ]
1153
- }
1154
- );
1155
- }
1156
-
1157
3780
  // src/adapters/LiveTimelineBuilder.ts
1158
3781
  var LiveTimelineBuilder = class {
1159
3782
  constructor() {
@@ -1338,48 +3961,48 @@ var LiveTimelineBuilder = class {
1338
3961
  };
1339
3962
 
1340
3963
  // src/adapters/useLiveTimeline.ts
1341
- var import_react3 = require("react");
3964
+ var import_react7 = require("react");
1342
3965
  function useLiveTimeline() {
1343
- const builderRef = (0, import_react3.useRef)(null);
3966
+ const builderRef = (0, import_react7.useRef)(null);
1344
3967
  if (!builderRef.current) builderRef.current = new LiveTimelineBuilder();
1345
3968
  const builder = builderRef.current;
1346
- const [timeline, setTimeline] = (0, import_react3.useState)(() => builder.getTimeline());
1347
- const sync = (0, import_react3.useCallback)(() => {
3969
+ const [timeline, setTimeline] = (0, import_react7.useState)(() => builder.getTimeline());
3970
+ const sync = (0, import_react7.useCallback)(() => {
1348
3971
  setTimeline(builder.getTimeline());
1349
3972
  }, [builder]);
1350
- const ingest = (0, import_react3.useCallback)(
3973
+ const ingest = (0, import_react7.useCallback)(
1351
3974
  (event) => {
1352
3975
  builder.ingest(event);
1353
3976
  sync();
1354
3977
  },
1355
3978
  [builder, sync]
1356
3979
  );
1357
- const startTurn = (0, import_react3.useCallback)(
3980
+ const startTurn = (0, import_react7.useCallback)(
1358
3981
  (userPrompt) => {
1359
3982
  builder.startTurn(userPrompt);
1360
3983
  sync();
1361
3984
  },
1362
3985
  [builder, sync]
1363
3986
  );
1364
- const setSystemPrompt = (0, import_react3.useCallback)(
3987
+ const setSystemPrompt = (0, import_react7.useCallback)(
1365
3988
  (prompt) => {
1366
3989
  builder.setSystemPrompt(prompt);
1367
3990
  sync();
1368
3991
  },
1369
3992
  [builder, sync]
1370
3993
  );
1371
- const setFinalDecision = (0, import_react3.useCallback)(
3994
+ const setFinalDecision = (0, import_react7.useCallback)(
1372
3995
  (decision) => {
1373
3996
  builder.setFinalDecision(decision);
1374
3997
  sync();
1375
3998
  },
1376
3999
  [builder, sync]
1377
4000
  );
1378
- const reset = (0, import_react3.useCallback)(() => {
4001
+ const reset = (0, import_react7.useCallback)(() => {
1379
4002
  builder.reset();
1380
4003
  sync();
1381
4004
  }, [builder, sync]);
1382
- return (0, import_react3.useMemo)(
4005
+ return (0, import_react7.useMemo)(
1383
4006
  () => ({ timeline, ingest, startTurn, setSystemPrompt, setFinalDecision, reset, builder }),
1384
4007
  [timeline, ingest, startTurn, setSystemPrompt, setFinalDecision, reset, builder]
1385
4008
  );
@@ -1387,10 +4010,16 @@ function useLiveTimeline() {
1387
4010
  // Annotate the CommonJS export names for ESM import in node:
1388
4011
  0 && (module.exports = {
1389
4012
  AgentLens,
4013
+ AskCard,
1390
4014
  IterationStrip,
1391
4015
  LiveTimelineBuilder,
1392
4016
  MessagesPanel,
4017
+ RunSummary,
4018
+ SkillsPanel,
4019
+ StageFlow,
4020
+ TimeTravel,
1393
4021
  ToolCallInspector,
4022
+ deriveStages,
1394
4023
  fromAgentSnapshot,
1395
4024
  resolveLensTheme,
1396
4025
  useLensTheme,