agentfootprint-lens 0.2.1 → 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) {
@@ -193,8 +209,10 @@ function assembleTurns(messages, llmCalls, toolExecs, instructionEvals, toolReso
193
209
  ...tc,
194
210
  turnIndex: currentTurn.index
195
211
  }));
212
+ const msgsBefore = messages.indexOf(msg);
196
213
  const iteration = {
197
214
  index: iterIndex,
215
+ messagesSentCount: msgsBefore >= 0 ? msgsBefore : 0,
198
216
  ...call?.model && { model: call.model },
199
217
  ...call?.inputTokens !== void 0 && { inputTokens: call.inputTokens },
200
218
  ...call?.outputTokens !== void 0 && { outputTokens: call.outputTokens },
@@ -239,7 +257,7 @@ function finalizeTurn(t) {
239
257
  }
240
258
 
241
259
  // src/panels/MessagesPanel.tsx
242
- var import_react = require("react");
260
+ var import_react = __toESM(require("react"), 1);
243
261
 
244
262
  // src/theme/useLensTheme.ts
245
263
  var import_footprint_explainable_ui = require("footprint-explainable-ui");
@@ -276,27 +294,105 @@ function MessagesPanel({
276
294
  timeline,
277
295
  onToolCallClick,
278
296
  systemPrompt,
279
- selectedIterKey
297
+ selectedIterKey,
298
+ stages,
299
+ focusIndex,
300
+ onFocusChange,
301
+ isLive
280
302
  }) {
281
303
  const t = useLensTheme();
282
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]);
283
327
  (0, import_react.useEffect)(() => {
284
- 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
+ }
285
360
  const target = scrollRef.current.querySelector(
286
- `[data-iter-key="${CSS.escape(selectedIterKey)}"]`
361
+ `[data-iter-key="${CSS.escape(key)}"]`
287
362
  );
288
- 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;
289
375
  target.scrollIntoView({ block: "start", behavior: "smooth" });
290
- target.setAttribute("data-iter-selected", "true");
291
- const h = window.setTimeout(() => target.removeAttribute("data-iter-selected"), 1200);
292
- return () => window.clearTimeout(h);
293
- }, [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]);
294
381
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
295
382
  "div",
296
383
  {
297
384
  ref: scrollRef,
298
385
  "data-fp-lens": "messages-panel",
299
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,
300
396
  display: "flex",
301
397
  flexDirection: "column",
302
398
  gap: 16,
@@ -311,7 +407,19 @@ function MessagesPanel({
311
407
  },
312
408
  children: [
313
409
  systemPrompt && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SystemBubble, { text: systemPrompt }),
314
- timeline.turns.map((turn) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TurnBlock, { turn, onToolCallClick }, turn.index))
410
+ timeline.turns.map((turn) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
411
+ TurnBlock,
412
+ {
413
+ turn,
414
+ allMessages: timeline.messages,
415
+ onToolCallClick,
416
+ iterRanges,
417
+ focusIndex,
418
+ stages,
419
+ onFocusChange
420
+ },
421
+ turn.index
422
+ ))
315
423
  ]
316
424
  }
317
425
  );
@@ -348,7 +456,7 @@ function SystemBubble({ text }) {
348
456
  font: "inherit"
349
457
  },
350
458
  children: [
351
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "SYSTEM" }),
459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "How Neo is configured" }),
352
460
  " ",
353
461
  open ? "\u25BE" : "\u25B8",
354
462
  " ",
@@ -375,31 +483,55 @@ function SystemBubble({ text }) {
375
483
  }
376
484
  function TurnBlock({
377
485
  turn,
378
- onToolCallClick
486
+ allMessages,
487
+ onToolCallClick,
488
+ iterRanges,
489
+ focusIndex,
490
+ stages,
491
+ onFocusChange
379
492
  }) {
380
493
  const t = useLensTheme();
381
494
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
382
495
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TurnHeader, { turn }),
383
496
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UserBubble, { text: turn.userPrompt }),
384
- turn.iterations.map((iter) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
385
- IterationBlock,
386
- {
387
- iter,
388
- turnIndex: turn.index,
389
- onToolCallClick
390
- },
391
- iter.index
392
- )),
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
+ }),
393
525
  turn.finalContent && turn.iterations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11, color: t.textSubtle, textAlign: "center" }, children: [
394
- "turn ",
395
- turn.index + 1,
396
- " final \xB7 ",
526
+ "Answer compiled \xB7 ",
397
527
  turn.iterations.length,
398
- " iter \xB7 ",
528
+ " step",
529
+ turn.iterations.length === 1 ? "" : "s",
530
+ " \xB7 ",
399
531
  turn.totalInputTokens,
400
532
  "\u2192",
401
533
  turn.totalOutputTokens,
402
- " tok \xB7 ",
534
+ " tokens \xB7 ",
403
535
  (turn.totalDurationMs / 1e3).toFixed(1),
404
536
  "s"
405
537
  ] })
@@ -423,7 +555,7 @@ function TurnHeader({ turn }) {
423
555
  children: [
424
556
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, height: 1, background: t.border } }),
425
557
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
426
- "Turn ",
558
+ "Your question ",
427
559
  turn.index + 1
428
560
  ] }),
429
561
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, height: 1, background: t.border } })
@@ -449,13 +581,110 @@ function UserBubble({ text }) {
449
581
  }
450
582
  ) });
451
583
  }
584
+ function iterationHeadline(iter) {
585
+ if (iter.toolCalls.length === 0) {
586
+ return "Neo is ready to answer";
587
+ }
588
+ if (iter.toolCalls.length === 1) {
589
+ return singleToolHeadline(iter.toolCalls[0]);
590
+ }
591
+ const names = iter.toolCalls.map((tc) => tc.name);
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})`;
607
+ }
452
608
  function IterationBlock({
453
609
  iter,
610
+ iterPositionInTurn,
454
611
  turnIndex,
455
- onToolCallClick
612
+ allMessages,
613
+ onToolCallClick,
614
+ state = "active",
615
+ stages,
616
+ range,
617
+ focusIndex,
618
+ onClick
456
619
  }) {
457
620
  const t = useLensTheme();
621
+ const [showContext, setShowContext] = (0, import_react.useState)(false);
458
622
  const key = `${turnIndex}.${iter.index}`;
623
+ const headline = iterationHeadline(iter);
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";
459
688
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
460
689
  "div",
461
690
  {
@@ -463,227 +692,2885 @@ function IterationBlock({
463
692
  "data-turn-index": turnIndex,
464
693
  "data-iter-index": iter.index,
465
694
  style: {
695
+ position: "relative",
466
696
  display: "flex",
467
697
  flexDirection: "column",
468
698
  gap: 6,
469
- // When the parent adds data-iter-selected (via IterationStrip
470
- // click), pulse a soft ring using the accent color. Subtle so
471
- // the chat itself stays readable.
472
- padding: 8,
473
- margin: -8,
474
- borderRadius: t.radius,
475
- outline: "2px solid transparent",
476
- outlineOffset: 2,
477
- 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"
478
707
  },
479
708
  children: [
480
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IterationBadge, { iter }),
481
- iter.assistantContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
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
+ ),
725
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
482
726
  "div",
483
727
  {
484
728
  style: {
485
- background: t.bgElev,
486
- border: `1px solid ${t.border}`,
487
- borderRadius: `2px ${t.radius} ${t.radius} ${t.radius}`,
488
- padding: "10px 14px",
489
- maxWidth: 820,
490
- whiteSpace: "pre-wrap"
729
+ display: "flex",
730
+ alignItems: "baseline",
731
+ gap: 8,
732
+ fontSize: 13,
733
+ color: t.textMuted
734
+ },
735
+ children: [
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
+ ),
766
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
767
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
768
+ "span",
769
+ {
770
+ style: {
771
+ fontSize: 10,
772
+ color: t.textSubtle,
773
+ fontFamily: t.fontMono
774
+ },
775
+ children: [
776
+ iter.inputTokens !== void 0 && `${iter.inputTokens}\u2192${iter.outputTokens ?? "?"} tok \xB7 `,
777
+ iter.durationMs !== void 0 && `${(iter.durationMs / 1e3).toFixed(2)}s`
778
+ ]
779
+ }
780
+ ),
781
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
782
+ "button",
783
+ {
784
+ onClick: () => setShowContext((v) => !v),
785
+ title: "See exactly what Neo saw when deciding this step",
786
+ style: {
787
+ fontSize: 11,
788
+ color: t.textMuted,
789
+ background: "transparent",
790
+ border: `1px solid ${t.border}`,
791
+ borderRadius: 4,
792
+ padding: "2px 8px",
793
+ cursor: "pointer",
794
+ fontWeight: 400,
795
+ width: "auto"
796
+ },
797
+ children: [
798
+ showContext ? "Hide" : "Show",
799
+ " what Neo saw"
800
+ ]
801
+ }
802
+ )
803
+ ]
804
+ }
805
+ ),
806
+ roundHasStarted && (iter.model || iter.stopReason || (iter.matchedInstructions?.length ?? 0) > 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
807
+ "div",
808
+ {
809
+ style: {
810
+ display: "flex",
811
+ flexWrap: "wrap",
812
+ gap: 6,
813
+ fontSize: 10,
814
+ color: t.textSubtle,
815
+ fontFamily: t.fontMono,
816
+ paddingLeft: 12
491
817
  },
492
- 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
+ ]
493
838
  }
494
839
  ),
495
- 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 }),
857
+ showContext && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
858
+ ContextDrawer,
859
+ {
860
+ messagesSentCount: iter.messagesSentCount,
861
+ contextMessages,
862
+ iter
863
+ }
864
+ )
496
865
  ]
497
866
  }
498
867
  );
499
868
  }
500
- function IterationBadge({ iter }) {
869
+ function ReasoningBubble({ text }) {
501
870
  const t = useLensTheme();
502
- const bits = [`iter ${iter.index}`];
503
- if (iter.model) bits.push(iter.model);
504
- if (iter.inputTokens !== void 0)
505
- bits.push(`${iter.inputTokens}\u2192${iter.outputTokens ?? "?"} tok`);
506
- if (iter.durationMs !== void 0) bits.push(`${(iter.durationMs / 1e3).toFixed(2)}s`);
507
- if (iter.stopReason) bits.push(iter.stopReason);
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;
508
876
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
509
877
  "div",
510
878
  {
511
879
  style: {
512
- alignSelf: "flex-start",
513
- fontSize: 10,
514
- color: t.textSubtle,
515
- textTransform: "uppercase",
516
- letterSpacing: "0.08em",
517
- fontWeight: 600,
518
- fontFamily: t.fontMono
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
519
888
  },
520
- children: bits.join(" \xB7 ")
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
+ ] })
521
929
  }
522
930
  );
523
931
  }
524
- function ToolCallCard({
525
- invocation,
526
- onClick
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
527
958
  }) {
528
959
  const t = useLensTheme();
529
960
  const [open, setOpen] = (0, import_react.useState)(false);
530
- const preview = shortArgs(invocation.arguments);
531
- const errored = invocation.error === true;
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 || "";
532
964
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
533
965
  "div",
534
966
  {
535
967
  style: {
536
- border: `1px solid ${errored ? t.error : t.border}`,
537
- borderLeft: `3px solid ${errored ? t.error : t.accent}`,
538
- borderRadius: 6,
539
- background: t.bg,
540
- overflow: "hidden"
968
+ display: "flex",
969
+ flexDirection: "column",
970
+ gap: 6,
971
+ paddingLeft: 12,
972
+ fontSize: 11,
973
+ color: t.textMuted,
974
+ fontFamily: t.fontSans
541
975
  },
542
976
  children: [
543
977
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
544
- "div",
978
+ "button",
545
979
  {
546
- onClick: () => {
547
- setOpen((v) => !v);
548
- onClick?.(invocation);
549
- },
980
+ onClick: () => setOpen((v) => !v),
550
981
  style: {
551
- padding: "8px 12px",
552
- cursor: "pointer",
553
982
  display: "flex",
983
+ flexWrap: "wrap",
554
984
  alignItems: "center",
555
- gap: 10,
556
- fontSize: 12,
557
- fontFamily: t.fontMono
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"
558
995
  },
996
+ title: "Click to see the actual diff",
559
997
  children: [
560
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: invocation.name }),
561
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: t.textMuted }, children: [
562
- "(",
563
- preview,
564
- ")"
565
- ] }),
566
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
567
- invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
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)(
568
1000
  "span",
569
1001
  {
570
1002
  style: {
571
- fontSize: 10,
572
- padding: "1px 6px",
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",
573
1018
  borderRadius: 3,
574
- background: `color-mix(in srgb, ${t.warning} 20%, transparent)`,
575
- color: t.warning,
576
- fontFamily: t.fontSans,
1019
+ background: `color-mix(in srgb, ${t.accent} 18%, transparent)`,
1020
+ color: t.accent,
577
1021
  fontWeight: 600,
578
- textTransform: "uppercase"
1022
+ letterSpacing: "0.02em"
579
1023
  },
580
- children: "decisionUpdate"
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
+ ]
581
1032
  }
582
1033
  ),
583
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.textSubtle }, children: open ? "\u25BE" : "\u25B8" })
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
+ )
584
1054
  ]
585
1055
  }
586
1056
  ),
587
- open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "8px 12px", borderTop: `1px solid ${t.border}` }, children: [
588
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, children: "args" }),
589
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.arguments }),
590
- invocation.result && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
591
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "result" }),
592
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
593
- "pre",
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",
594
1124
  {
595
1125
  style: {
596
- margin: 0,
597
- padding: "8px 10px",
598
- background: t.bgElev,
599
- borderRadius: 4,
600
- fontSize: 11,
601
- fontFamily: t.fontMono,
602
- color: errored ? t.error : t.text,
603
- maxHeight: 280,
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,
604
1191
  overflow: "auto",
605
- whiteSpace: "pre-wrap"
1192
+ display: "flex",
1193
+ flexDirection: "column",
1194
+ gap: 16
606
1195
  },
607
- children: invocation.result
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
+ ]
608
1303
  }
609
1304
  )
610
- ] }),
611
- invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
612
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "decisionUpdate" }),
613
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.decisionUpdate })
614
- ] })
615
- ] })
616
- ]
1305
+ ]
1306
+ }
1307
+ )
617
1308
  }
618
1309
  );
619
1310
  }
620
- function Label({
621
- t,
622
- children,
623
- style
1311
+ function ContextDrawer({
1312
+ messagesSentCount,
1313
+ contextMessages,
1314
+ iter
624
1315
  }) {
625
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1316
+ const t = useLensTheme();
1317
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
626
1318
  "div",
627
1319
  {
628
1320
  style: {
629
- fontSize: 10,
630
- color: t.textSubtle,
1321
+ border: `1px dashed ${t.border}`,
1322
+ borderRadius: t.radius,
1323
+ padding: "10px 12px",
1324
+ background: t.bg,
1325
+ fontSize: 12,
1326
+ color: t.textMuted
1327
+ },
1328
+ children: [
1329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1330
+ "div",
1331
+ {
1332
+ style: {
1333
+ fontSize: 10,
1334
+ color: t.textSubtle,
1335
+ textTransform: "uppercase",
1336
+ letterSpacing: "0.08em",
1337
+ fontWeight: 600,
1338
+ marginBottom: 8
1339
+ },
1340
+ children: "What Neo saw before this step"
1341
+ }
1342
+ ),
1343
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { color: t.text, marginBottom: 8 }, children: [
1344
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: messagesSentCount }),
1345
+ " message",
1346
+ messagesSentCount === 1 ? "" : "s",
1347
+ " in context",
1348
+ iter.model ? ` \xB7 sent to ${iter.model}` : "",
1349
+ iter.inputTokens !== void 0 && ` \xB7 ${iter.inputTokens} input tokens`
1350
+ ] }),
1351
+ contextMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: t.textSubtle, fontStyle: "italic" }, children: "Just the system configuration \u2014 this is the first call of the conversation." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { style: { margin: 0, paddingLeft: 18, display: "flex", flexDirection: "column", gap: 6 }, children: contextMessages.map((m, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { fontSize: 12 }, children: [
1352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1353
+ "span",
1354
+ {
1355
+ style: {
1356
+ display: "inline-block",
1357
+ padding: "1px 6px",
1358
+ borderRadius: 3,
1359
+ background: m.role === "user" ? `color-mix(in srgb, ${t.accent} 20%, transparent)` : m.role === "assistant" ? t.bgElev : m.role === "tool" ? `color-mix(in srgb, ${t.success} 18%, transparent)` : t.bgElev,
1360
+ color: m.role === "user" ? t.accent : m.role === "tool" ? t.success : t.text,
1361
+ fontFamily: t.fontMono,
1362
+ fontSize: 10,
1363
+ fontWeight: 600,
1364
+ textTransform: "uppercase",
1365
+ marginRight: 6
1366
+ },
1367
+ children: m.role
1368
+ }
1369
+ ),
1370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.textMuted }, children: summarizeMessage(m) })
1371
+ ] }, i)) }),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1373
+ "div",
1374
+ {
1375
+ style: {
1376
+ marginTop: 10,
1377
+ fontSize: 11,
1378
+ color: t.textSubtle,
1379
+ fontStyle: "italic"
1380
+ },
1381
+ children: "Plus the system configuration (see top of conversation) and the tools Neo had access to."
1382
+ }
1383
+ )
1384
+ ]
1385
+ }
1386
+ );
1387
+ }
1388
+ function summarizeMessage(m) {
1389
+ if (m.role === "tool") {
1390
+ return `tool result (${m.content.length.toLocaleString()} chars)`;
1391
+ }
1392
+ const t = m.content.replace(/\s+/g, " ").trim();
1393
+ return t.length > 120 ? t.slice(0, 120) + "\u2026" : t;
1394
+ }
1395
+ function ToolCallCard({
1396
+ invocation,
1397
+ onClick,
1398
+ resultRevealed = true
1399
+ }) {
1400
+ const t = useLensTheme();
1401
+ const [open, setOpen] = (0, import_react.useState)(false);
1402
+ const preview = shortArgs(invocation.arguments);
1403
+ const errored = invocation.error === true;
1404
+ const friendlyVerb = toolVerb(invocation);
1405
+ const borderStyle = resultRevealed ? "solid" : "dashed";
1406
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1407
+ "div",
1408
+ {
1409
+ style: {
1410
+ border: `1px ${borderStyle} ${errored ? t.error : t.border}`,
1411
+ borderLeft: `3px ${borderStyle} ${errored ? t.error : t.accent}`,
1412
+ borderRadius: 6,
1413
+ background: t.bg,
1414
+ overflow: "hidden"
1415
+ },
1416
+ children: [
1417
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1418
+ "div",
1419
+ {
1420
+ onClick: () => {
1421
+ setOpen((v) => !v);
1422
+ onClick?.(invocation);
1423
+ },
1424
+ style: {
1425
+ padding: "8px 12px",
1426
+ cursor: "pointer",
1427
+ display: "flex",
1428
+ alignItems: "center",
1429
+ gap: 10,
1430
+ fontSize: 12
1431
+ },
1432
+ children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.textMuted, fontFamily: t.fontSans }, children: friendlyVerb }),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600, fontFamily: t.fontMono }, children: invocation.name }),
1435
+ preview && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: t.textMuted, fontFamily: t.fontMono }, children: [
1436
+ "(",
1437
+ preview,
1438
+ ")"
1439
+ ] }),
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
+ ),
1458
+ invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1459
+ "span",
1460
+ {
1461
+ style: {
1462
+ fontSize: 10,
1463
+ padding: "1px 6px",
1464
+ borderRadius: 3,
1465
+ background: `color-mix(in srgb, ${t.warning} 20%, transparent)`,
1466
+ color: t.warning,
1467
+ fontWeight: 600,
1468
+ textTransform: "uppercase"
1469
+ },
1470
+ title: "This tool changed what skill is active",
1471
+ children: "skill change"
1472
+ }
1473
+ ),
1474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.textSubtle }, children: open ? "\u25BE" : "\u25B8" })
1475
+ ]
1476
+ }
1477
+ ),
1478
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "8px 12px", borderTop: `1px solid ${t.border}` }, children: [
1479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, children: "What Neo asked for" }),
1480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.arguments }),
1481
+ resultRevealed && invocation.result && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1482
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "What the tool returned" }),
1483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1484
+ "pre",
1485
+ {
1486
+ style: {
1487
+ margin: 0,
1488
+ padding: "8px 10px",
1489
+ background: t.bgElev,
1490
+ borderRadius: 4,
1491
+ fontSize: 11,
1492
+ fontFamily: t.fontMono,
1493
+ color: errored ? t.error : t.text,
1494
+ maxHeight: 280,
1495
+ overflow: "auto",
1496
+ whiteSpace: "pre-wrap"
1497
+ },
1498
+ children: invocation.result
1499
+ }
1500
+ )
1501
+ ] }),
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: [
1518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "What changed in Neo's state" }),
1519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.decisionUpdate })
1520
+ ] })
1521
+ ] })
1522
+ ]
1523
+ }
1524
+ );
1525
+ }
1526
+ function toolVerb(inv) {
1527
+ if (inv.name === "list_skills") return "Asked for";
1528
+ if (inv.name === "read_skill") return "Activated";
1529
+ if (inv.name === "ask_human" || inv.name === "ask_user") return "Asked user for";
1530
+ return "Called tool";
1531
+ }
1532
+ function Label({
1533
+ t,
1534
+ children,
1535
+ style
1536
+ }) {
1537
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1538
+ "div",
1539
+ {
1540
+ style: {
1541
+ fontSize: 10,
1542
+ color: t.textSubtle,
1543
+ textTransform: "uppercase",
1544
+ letterSpacing: "0.08em",
1545
+ fontWeight: 600,
1546
+ marginBottom: 4,
1547
+ ...style
1548
+ },
1549
+ children
1550
+ }
1551
+ );
1552
+ }
1553
+ function JsonBlock({ value }) {
1554
+ const t = useLensTheme();
1555
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1556
+ "pre",
1557
+ {
1558
+ style: {
1559
+ margin: 0,
1560
+ padding: "8px 10px",
1561
+ background: t.bgElev,
1562
+ borderRadius: 4,
1563
+ fontSize: 11,
1564
+ fontFamily: t.fontMono,
1565
+ color: t.text,
1566
+ maxHeight: 200,
1567
+ overflow: "auto"
1568
+ },
1569
+ children: JSON.stringify(value, null, 2)
1570
+ }
1571
+ );
1572
+ }
1573
+ function shortArgs(args) {
1574
+ const keys = Object.keys(args);
1575
+ if (keys.length === 0) return "";
1576
+ if (keys.length === 1) {
1577
+ const v = args[keys[0]];
1578
+ if (typeof v === "string" && v.length < 40) return `${keys[0]}: "${v}"`;
1579
+ }
1580
+ return keys.join(", ");
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
+ }
1620
+
1621
+ // src/panels/SkillsPanel.tsx
1622
+ var import_react2 = require("react");
1623
+ var import_jsx_runtime2 = require("react/jsx-runtime");
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,
631
3107
  textTransform: "uppercase",
632
3108
  letterSpacing: "0.08em",
633
3109
  fontWeight: 600,
634
- marginBottom: 4,
635
- ...style
3110
+ marginBottom: 4
636
3111
  },
637
3112
  children
638
3113
  }
639
3114
  );
640
3115
  }
641
- function JsonBlock({ value }) {
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
+ }) {
642
3280
  const t = useLensTheme();
643
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
644
- "pre",
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",
645
3364
  {
3365
+ "data-fp-lens": "shell",
646
3366
  style: {
647
- margin: 0,
648
- padding: "8px 10px",
649
- background: t.bgElev,
650
- borderRadius: 4,
651
- fontSize: 11,
652
- fontFamily: t.fontMono,
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,
653
3396
  color: t.text,
654
- maxHeight: 200,
655
- overflow: "auto"
3397
+ fontFamily: t.fontSans
656
3398
  },
657
- children: JSON.stringify(value, null, 2)
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
+ ]
658
3554
  }
659
3555
  );
660
3556
  }
661
- function shortArgs(args) {
662
- const keys = Object.keys(args);
663
- if (keys.length === 0) return "";
664
- if (keys.length === 1) {
665
- const v = args[keys[0]];
666
- if (typeof v === "string" && v.length < 40) return `${keys[0]}: "${v}"`;
667
- }
668
- return keys.join(", ");
669
- }
670
3557
 
671
3558
  // src/panels/IterationStrip.tsx
672
- var import_jsx_runtime2 = require("react/jsx-runtime");
3559
+ var import_jsx_runtime8 = require("react/jsx-runtime");
673
3560
  function IterationStrip({ timeline, selectedKey, onSelect }) {
674
3561
  const t = useLensTheme();
675
3562
  const chips = timeline.turns.flatMap(
676
- (turn) => turn.iterations.map((iter) => ({
3563
+ (turn) => turn.iterations.map((iter, stepIdx) => ({
677
3564
  key: `${turn.index}.${iter.index}`,
678
3565
  turn: turn.index + 1,
679
- iter: iter.index,
680
- label: chipLabel(iter),
681
- tools: iter.toolCalls.length,
3566
+ step: stepIdx + 1,
3567
+ label: stepHeadline(iter),
682
3568
  durationMs: iter.durationMs ?? 0,
683
- stopReason: iter.stopReason
3569
+ stopReason: iter.stopReason,
3570
+ toolCount: iter.toolCalls.length
684
3571
  }))
685
3572
  );
686
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3573
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
687
3574
  "div",
688
3575
  {
689
3576
  "data-fp-lens": "iteration-strip",
@@ -696,11 +3583,12 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
696
3583
  background: t.bgElev
697
3584
  },
698
3585
  children: [
699
- 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." }),
700
3587
  chips.map((c) => {
701
3588
  const active = c.key === selectedKey;
702
- const isFinal = c.stopReason === "stop" || c.stopReason === "end_turn";
703
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3589
+ const isFinal = c.toolCount === 0;
3590
+ const secs = c.durationMs >= 1e3 ? `${(c.durationMs / 1e3).toFixed(1)}s` : `${Math.round(c.durationMs)}ms`;
3591
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
704
3592
  "button",
705
3593
  {
706
3594
  onClick: () => onSelect?.(c.key),
@@ -709,23 +3597,27 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
709
3597
  color: active ? "#fff" : t.textMuted,
710
3598
  border: `1px solid ${active ? t.accent : t.border}`,
711
3599
  borderRadius: 4,
712
- padding: "4px 8px",
3600
+ padding: "4px 10px",
713
3601
  fontSize: 11,
714
- fontFamily: "ui-monospace, monospace",
3602
+ fontFamily: t.fontSans,
715
3603
  cursor: "pointer",
716
3604
  whiteSpace: "nowrap",
717
- flexShrink: 0
3605
+ flexShrink: 0,
3606
+ maxWidth: 280,
3607
+ overflow: "hidden",
3608
+ textOverflow: "ellipsis"
718
3609
  },
719
- title: `turn ${c.turn} \xB7 iter ${c.iter} \xB7 ${c.tools} tool call${c.tools === 1 ? "" : "s"}${c.stopReason ? ` \xB7 ${c.stopReason}` : ""}`,
3610
+ title: `Question ${c.turn} \xB7 Step ${c.step} \xB7 ${secs}${c.stopReason ? ` \xB7 stop: ${c.stopReason}` : ""}`,
720
3611
  children: [
721
- "t",
722
- c.turn,
723
- ".i",
724
- c.iter,
725
- " \xB7 ",
726
- c.label,
727
- " ",
728
- isFinal ? "\u2713" : ""
3612
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { style: { fontWeight: 600 }, children: [
3613
+ "Step ",
3614
+ c.step
3615
+ ] }),
3616
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { style: { opacity: 0.85 }, children: [
3617
+ " \xB7 ",
3618
+ c.label
3619
+ ] }),
3620
+ isFinal && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { marginLeft: 6 }, children: "\u2713" })
729
3621
  ]
730
3622
  },
731
3623
  c.key
@@ -735,22 +3627,35 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
735
3627
  }
736
3628
  );
737
3629
  }
738
- function chipLabel(iter) {
739
- const d = iter.durationMs ?? 0;
740
- const secs = d >= 1e3 ? `${(d / 1e3).toFixed(1)}s` : `${Math.round(d)}ms`;
741
- const toolBit = iter.toolCalls.length > 0 ? `${iter.toolCalls.length}t` : "final";
742
- return `${secs} \xB7 ${toolBit}`;
3630
+ function stepHeadline(iter) {
3631
+ if (iter.toolCalls.length === 0) return "Ready to answer";
3632
+ if (iter.toolCalls.length === 1) {
3633
+ const tc = iter.toolCalls[0];
3634
+ if (tc.name === "list_skills") return "Looking up skills";
3635
+ if (tc.name === "read_skill") {
3636
+ const id = tc.arguments?.id;
3637
+ return id ? `Activated ${id}` : "Activating skill";
3638
+ }
3639
+ if (tc.name === "ask_human" || tc.name === "ask_user") {
3640
+ return "Asked user";
3641
+ }
3642
+ return `Called tool (${tc.name})`;
3643
+ }
3644
+ if (iter.toolCalls.length <= 3) {
3645
+ return `Called ${iter.toolCalls.length} tools (${iter.toolCalls.map((tc) => tc.name).join(", ")})`;
3646
+ }
3647
+ return `Called ${iter.toolCalls.length} tools in parallel`;
743
3648
  }
744
3649
 
745
3650
  // src/panels/ToolCallInspector.tsx
746
- var import_jsx_runtime3 = require("react/jsx-runtime");
3651
+ var import_jsx_runtime9 = require("react/jsx-runtime");
747
3652
  function ToolCallInspector({
748
3653
  timeline,
749
3654
  selectedId,
750
3655
  onSelect
751
3656
  }) {
752
3657
  const t = useLensTheme();
753
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3658
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
754
3659
  "div",
755
3660
  {
756
3661
  "data-fp-lens": "tool-call-inspector",
@@ -764,7 +3669,7 @@ function ToolCallInspector({
764
3669
  overflow: "hidden"
765
3670
  },
766
3671
  children: [
767
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3672
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
768
3673
  "div",
769
3674
  {
770
3675
  style: {
@@ -778,17 +3683,17 @@ function ToolCallInspector({
778
3683
  background: t.bgElev
779
3684
  },
780
3685
  children: [
781
- "Tool calls \xB7 ",
3686
+ "Every tool Neo called \xB7 ",
782
3687
  timeline.tools.length
783
3688
  ]
784
3689
  }
785
3690
  ),
786
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { overflow: "auto", flex: 1 }, children: [
787
- timeline.tools.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "No tool calls 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." }),
788
3693
  timeline.tools.map((tc) => {
789
3694
  const active = tc.id === selectedId;
790
3695
  const errored = tc.error === true;
791
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3696
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
792
3697
  "button",
793
3698
  {
794
3699
  onClick: () => onSelect?.(tc),
@@ -808,7 +3713,7 @@ function ToolCallInspector({
808
3713
  fontFamily: "inherit"
809
3714
  },
810
3715
  children: [
811
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3716
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
812
3717
  "div",
813
3718
  {
814
3719
  style: {
@@ -819,8 +3724,8 @@ function ToolCallInspector({
819
3724
  fontFamily: t.fontMono
820
3725
  },
821
3726
  children: [
822
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: tc.name }),
823
- /* @__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)(
824
3729
  "span",
825
3730
  {
826
3731
  style: { color: t.textSubtle, fontSize: 10, marginLeft: "auto" },
@@ -836,7 +3741,7 @@ function ToolCallInspector({
836
3741
  ]
837
3742
  }
838
3743
  ),
839
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3744
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
840
3745
  "div",
841
3746
  {
842
3747
  style: {
@@ -872,111 +3777,6 @@ function shortArgs2(args) {
872
3777
  }).join(", ");
873
3778
  }
874
3779
 
875
- // src/AgentLens.tsx
876
- var import_jsx_runtime4 = require("react/jsx-runtime");
877
- function AgentLens({
878
- runtimeSnapshot,
879
- timeline: providedTimeline,
880
- systemPrompt,
881
- onToolCallClick
882
- }) {
883
- const t = useLensTheme();
884
- const timeline = (0, import_react2.useMemo)(() => {
885
- if (providedTimeline) return providedTimeline;
886
- if (!runtimeSnapshot) return null;
887
- return fromAgentSnapshot(runtimeSnapshot);
888
- }, [providedTimeline, runtimeSnapshot]);
889
- const [selectedToolId, setSelectedToolId] = (0, import_react2.useState)(null);
890
- const [selectedIterKey, setSelectedIterKey] = (0, import_react2.useState)(null);
891
- function handleToolClick(inv) {
892
- setSelectedToolId(inv.id);
893
- setSelectedIterKey(`${inv.turnIndex}.${inv.iterationIndex}`);
894
- onToolCallClick?.(inv);
895
- }
896
- if (!timeline) {
897
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
898
- "div",
899
- {
900
- "data-fp-lens": "empty",
901
- style: {
902
- padding: 32,
903
- color: t.textMuted,
904
- fontFamily: t.fontSans,
905
- textAlign: "center",
906
- background: t.bg
907
- },
908
- children: [
909
- "No agent run to show yet. Pass ",
910
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: "runtimeSnapshot" }),
911
- " after the agent runs."
912
- ]
913
- }
914
- );
915
- }
916
- const derivedSystemPrompt = systemPrompt ?? (typeof timeline.rawSnapshot?.sharedState?.systemPrompt === "string" ? timeline.rawSnapshot.sharedState.systemPrompt : void 0);
917
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
918
- "div",
919
- {
920
- "data-fp-lens": "shell",
921
- style: {
922
- display: "grid",
923
- gridTemplateColumns: "1fr 320px",
924
- gridTemplateRows: "auto 1fr",
925
- gridTemplateAreas: '"strip strip" "messages inspector"',
926
- height: "100%",
927
- minHeight: 0,
928
- background: t.bg,
929
- color: t.text,
930
- fontFamily: t.fontSans
931
- },
932
- children: [
933
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
934
- [data-iter-selected="true"] {
935
- outline-color: currentColor !important;
936
- background: color-mix(in srgb, currentColor 8%, transparent);
937
- }
938
- ` }),
939
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "strip" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
940
- IterationStrip,
941
- {
942
- timeline,
943
- selectedKey: selectedIterKey,
944
- onSelect: setSelectedIterKey
945
- }
946
- ) }),
947
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "messages", minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
948
- MessagesPanel,
949
- {
950
- timeline,
951
- onToolCallClick: handleToolClick,
952
- selectedIterKey,
953
- ...derivedSystemPrompt && { systemPrompt: derivedSystemPrompt }
954
- }
955
- ) }),
956
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
957
- "div",
958
- {
959
- style: {
960
- gridArea: "inspector",
961
- minHeight: 0,
962
- overflow: "hidden",
963
- borderLeft: `1px solid ${t.border}`
964
- },
965
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
966
- ToolCallInspector,
967
- {
968
- timeline,
969
- selectedId: selectedToolId,
970
- onSelect: handleToolClick
971
- }
972
- )
973
- }
974
- )
975
- ]
976
- }
977
- );
978
- }
979
-
980
3780
  // src/adapters/LiveTimelineBuilder.ts
981
3781
  var LiveTimelineBuilder = class {
982
3782
  constructor() {
@@ -1054,7 +3854,10 @@ var LiveTimelineBuilder = class {
1054
3854
  toolCalls: [],
1055
3855
  decisionAtStart: {},
1056
3856
  visibleTools: [],
1057
- startMs: Date.now()
3857
+ startMs: Date.now(),
3858
+ // Freeze the message count here so "What Neo saw" can reproduce
3859
+ // the context window at this exact iteration later.
3860
+ messagesSentCount: this.messages.length
1058
3861
  };
1059
3862
  this.currentTurn.iterations.push(this.currentIter);
1060
3863
  }
@@ -1158,48 +3961,48 @@ var LiveTimelineBuilder = class {
1158
3961
  };
1159
3962
 
1160
3963
  // src/adapters/useLiveTimeline.ts
1161
- var import_react3 = require("react");
3964
+ var import_react7 = require("react");
1162
3965
  function useLiveTimeline() {
1163
- const builderRef = (0, import_react3.useRef)(null);
3966
+ const builderRef = (0, import_react7.useRef)(null);
1164
3967
  if (!builderRef.current) builderRef.current = new LiveTimelineBuilder();
1165
3968
  const builder = builderRef.current;
1166
- const [timeline, setTimeline] = (0, import_react3.useState)(() => builder.getTimeline());
1167
- const sync = (0, import_react3.useCallback)(() => {
3969
+ const [timeline, setTimeline] = (0, import_react7.useState)(() => builder.getTimeline());
3970
+ const sync = (0, import_react7.useCallback)(() => {
1168
3971
  setTimeline(builder.getTimeline());
1169
3972
  }, [builder]);
1170
- const ingest = (0, import_react3.useCallback)(
3973
+ const ingest = (0, import_react7.useCallback)(
1171
3974
  (event) => {
1172
3975
  builder.ingest(event);
1173
3976
  sync();
1174
3977
  },
1175
3978
  [builder, sync]
1176
3979
  );
1177
- const startTurn = (0, import_react3.useCallback)(
3980
+ const startTurn = (0, import_react7.useCallback)(
1178
3981
  (userPrompt) => {
1179
3982
  builder.startTurn(userPrompt);
1180
3983
  sync();
1181
3984
  },
1182
3985
  [builder, sync]
1183
3986
  );
1184
- const setSystemPrompt = (0, import_react3.useCallback)(
3987
+ const setSystemPrompt = (0, import_react7.useCallback)(
1185
3988
  (prompt) => {
1186
3989
  builder.setSystemPrompt(prompt);
1187
3990
  sync();
1188
3991
  },
1189
3992
  [builder, sync]
1190
3993
  );
1191
- const setFinalDecision = (0, import_react3.useCallback)(
3994
+ const setFinalDecision = (0, import_react7.useCallback)(
1192
3995
  (decision) => {
1193
3996
  builder.setFinalDecision(decision);
1194
3997
  sync();
1195
3998
  },
1196
3999
  [builder, sync]
1197
4000
  );
1198
- const reset = (0, import_react3.useCallback)(() => {
4001
+ const reset = (0, import_react7.useCallback)(() => {
1199
4002
  builder.reset();
1200
4003
  sync();
1201
4004
  }, [builder, sync]);
1202
- return (0, import_react3.useMemo)(
4005
+ return (0, import_react7.useMemo)(
1203
4006
  () => ({ timeline, ingest, startTurn, setSystemPrompt, setFinalDecision, reset, builder }),
1204
4007
  [timeline, ingest, startTurn, setSystemPrompt, setFinalDecision, reset, builder]
1205
4008
  );
@@ -1207,10 +4010,16 @@ function useLiveTimeline() {
1207
4010
  // Annotate the CommonJS export names for ESM import in node:
1208
4011
  0 && (module.exports = {
1209
4012
  AgentLens,
4013
+ AskCard,
1210
4014
  IterationStrip,
1211
4015
  LiveTimelineBuilder,
1212
4016
  MessagesPanel,
4017
+ RunSummary,
4018
+ SkillsPanel,
4019
+ StageFlow,
4020
+ TimeTravel,
1213
4021
  ToolCallInspector,
4022
+ deriveStages,
1214
4023
  fromAgentSnapshot,
1215
4024
  resolveLensTheme,
1216
4025
  useLensTheme,