agentfootprint-lens 0.2.0 → 0.3.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
@@ -193,8 +193,10 @@ function assembleTurns(messages, llmCalls, toolExecs, instructionEvals, toolReso
193
193
  ...tc,
194
194
  turnIndex: currentTurn.index
195
195
  }));
196
+ const msgsBefore = messages.indexOf(msg);
196
197
  const iteration = {
197
198
  index: iterIndex,
199
+ messagesSentCount: msgsBefore >= 0 ? msgsBefore : 0,
198
200
  ...call?.model && { model: call.model },
199
201
  ...call?.inputTokens !== void 0 && { inputTokens: call.inputTokens },
200
202
  ...call?.outputTokens !== void 0 && { outputTokens: call.outputTokens },
@@ -275,12 +277,26 @@ var import_jsx_runtime = require("react/jsx-runtime");
275
277
  function MessagesPanel({
276
278
  timeline,
277
279
  onToolCallClick,
278
- systemPrompt
280
+ systemPrompt,
281
+ selectedIterKey
279
282
  }) {
280
283
  const t = useLensTheme();
284
+ const scrollRef = (0, import_react.useRef)(null);
285
+ (0, import_react.useEffect)(() => {
286
+ if (!selectedIterKey || !scrollRef.current) return;
287
+ const target = scrollRef.current.querySelector(
288
+ `[data-iter-key="${CSS.escape(selectedIterKey)}"]`
289
+ );
290
+ if (!target) return;
291
+ target.scrollIntoView({ block: "start", behavior: "smooth" });
292
+ target.setAttribute("data-iter-selected", "true");
293
+ const h = window.setTimeout(() => target.removeAttribute("data-iter-selected"), 1200);
294
+ return () => window.clearTimeout(h);
295
+ }, [selectedIterKey]);
281
296
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
282
297
  "div",
283
298
  {
299
+ ref: scrollRef,
284
300
  "data-fp-lens": "messages-panel",
285
301
  style: {
286
302
  display: "flex",
@@ -297,7 +313,15 @@ function MessagesPanel({
297
313
  },
298
314
  children: [
299
315
  systemPrompt && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SystemBubble, { text: systemPrompt }),
300
- timeline.turns.map((turn) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TurnBlock, { turn, onToolCallClick }, turn.index))
316
+ timeline.turns.map((turn) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
317
+ TurnBlock,
318
+ {
319
+ turn,
320
+ allMessages: timeline.messages,
321
+ onToolCallClick
322
+ },
323
+ turn.index
324
+ ))
301
325
  ]
302
326
  }
303
327
  );
@@ -334,7 +358,7 @@ function SystemBubble({ text }) {
334
358
  font: "inherit"
335
359
  },
336
360
  children: [
337
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "SYSTEM" }),
361
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "How Neo is configured" }),
338
362
  " ",
339
363
  open ? "\u25BE" : "\u25B8",
340
364
  " ",
@@ -361,23 +385,34 @@ function SystemBubble({ text }) {
361
385
  }
362
386
  function TurnBlock({
363
387
  turn,
388
+ allMessages,
364
389
  onToolCallClick
365
390
  }) {
366
391
  const t = useLensTheme();
367
392
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
368
393
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TurnHeader, { turn }),
369
394
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UserBubble, { text: turn.userPrompt }),
370
- turn.iterations.map((iter) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IterationBlock, { iter, onToolCallClick }, iter.index)),
395
+ turn.iterations.map((iter, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
396
+ IterationBlock,
397
+ {
398
+ iter,
399
+ iterPositionInTurn: i + 1,
400
+ turnIndex: turn.index,
401
+ allMessages,
402
+ onToolCallClick
403
+ },
404
+ iter.index
405
+ )),
371
406
  turn.finalContent && turn.iterations.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11, color: t.textSubtle, textAlign: "center" }, children: [
372
- "turn ",
373
- turn.index + 1,
374
- " final \xB7 ",
407
+ "Answer compiled \xB7 ",
375
408
  turn.iterations.length,
376
- " iter \xB7 ",
409
+ " step",
410
+ turn.iterations.length === 1 ? "" : "s",
411
+ " \xB7 ",
377
412
  turn.totalInputTokens,
378
413
  "\u2192",
379
414
  turn.totalOutputTokens,
380
- " tok \xB7 ",
415
+ " tokens \xB7 ",
381
416
  (turn.totalDurationMs / 1e3).toFixed(1),
382
417
  "s"
383
418
  ] })
@@ -401,7 +436,7 @@ function TurnHeader({ turn }) {
401
436
  children: [
402
437
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, height: 1, background: t.border } }),
403
438
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
404
- "Turn ",
439
+ "Your question ",
405
440
  turn.index + 1
406
441
  ] }),
407
442
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, height: 1, background: t.border } })
@@ -427,54 +462,221 @@ function UserBubble({ text }) {
427
462
  }
428
463
  ) });
429
464
  }
465
+ function iterationHeadline(iter) {
466
+ if (iter.toolCalls.length === 0) {
467
+ return "Neo is ready to answer";
468
+ }
469
+ if (iter.toolCalls.length === 1) {
470
+ const tc = iter.toolCalls[0];
471
+ if (tc.name === "list_skills") return "Neo is looking up available skills";
472
+ if (tc.name === "read_skill") {
473
+ const id = tc.arguments?.id ?? "?";
474
+ return `Neo activated the \u201C${id}\u201D skill`;
475
+ }
476
+ return `Neo called ${tc.name} to get data`;
477
+ }
478
+ const names = iter.toolCalls.map((tc) => tc.name);
479
+ if (names.length <= 3) return `Neo called ${names.join(", ")} in parallel`;
480
+ return `Neo gathered data from ${names.length} sources in parallel`;
481
+ }
430
482
  function IterationBlock({
431
483
  iter,
484
+ iterPositionInTurn,
485
+ turnIndex,
486
+ allMessages,
432
487
  onToolCallClick
433
488
  }) {
434
489
  const t = useLensTheme();
435
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
436
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IterationBadge, { iter }),
437
- iter.assistantContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
438
- "div",
439
- {
440
- style: {
441
- background: t.bgElev,
442
- border: `1px solid ${t.border}`,
443
- borderRadius: `2px ${t.radius} ${t.radius} ${t.radius}`,
444
- padding: "10px 14px",
445
- maxWidth: 820,
446
- whiteSpace: "pre-wrap"
447
- },
448
- children: iter.assistantContent
449
- }
450
- ),
451
- 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)) })
452
- ] });
490
+ const [showContext, setShowContext] = (0, import_react.useState)(false);
491
+ const key = `${turnIndex}.${iter.index}`;
492
+ const headline = iterationHeadline(iter);
493
+ const contextMessages = allMessages.slice(0, iter.messagesSentCount);
494
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
495
+ "div",
496
+ {
497
+ "data-iter-key": key,
498
+ "data-turn-index": turnIndex,
499
+ "data-iter-index": iter.index,
500
+ style: {
501
+ display: "flex",
502
+ flexDirection: "column",
503
+ gap: 6,
504
+ padding: 8,
505
+ margin: -8,
506
+ borderRadius: t.radius,
507
+ outline: "2px solid transparent",
508
+ outlineOffset: 2,
509
+ transition: "outline-color 180ms ease, background 180ms ease"
510
+ },
511
+ children: [
512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
513
+ "div",
514
+ {
515
+ style: {
516
+ display: "flex",
517
+ alignItems: "baseline",
518
+ gap: 8,
519
+ fontSize: 13,
520
+ color: t.textMuted
521
+ },
522
+ children: [
523
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: t.accent, fontWeight: 600 }, children: [
524
+ "Step ",
525
+ iterPositionInTurn,
526
+ ":"
527
+ ] }),
528
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.text }, children: headline }),
529
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
530
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
531
+ "span",
532
+ {
533
+ style: {
534
+ fontSize: 10,
535
+ color: t.textSubtle,
536
+ fontFamily: t.fontMono
537
+ },
538
+ children: [
539
+ iter.inputTokens !== void 0 && `${iter.inputTokens}\u2192${iter.outputTokens ?? "?"} tok \xB7 `,
540
+ iter.durationMs !== void 0 && `${(iter.durationMs / 1e3).toFixed(2)}s`
541
+ ]
542
+ }
543
+ ),
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
545
+ "button",
546
+ {
547
+ onClick: () => setShowContext((v) => !v),
548
+ title: "See exactly what Neo saw when deciding this step",
549
+ style: {
550
+ fontSize: 11,
551
+ color: t.textMuted,
552
+ background: "transparent",
553
+ border: `1px solid ${t.border}`,
554
+ borderRadius: 4,
555
+ padding: "2px 8px",
556
+ cursor: "pointer",
557
+ fontWeight: 400,
558
+ width: "auto"
559
+ },
560
+ children: [
561
+ showContext ? "Hide" : "Show",
562
+ " what Neo saw"
563
+ ]
564
+ }
565
+ )
566
+ ]
567
+ }
568
+ ),
569
+ iter.assistantContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
570
+ "div",
571
+ {
572
+ style: {
573
+ background: t.bgElev,
574
+ border: `1px solid ${t.border}`,
575
+ borderRadius: `2px ${t.radius} ${t.radius} ${t.radius}`,
576
+ padding: "10px 14px",
577
+ maxWidth: 820,
578
+ whiteSpace: "pre-wrap"
579
+ },
580
+ children: iter.assistantContent
581
+ }
582
+ ),
583
+ iter.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6, paddingLeft: 12 }, children: iter.toolCalls.map((tc) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolCallCard, { invocation: tc, onClick: onToolCallClick }, tc.id)) }),
584
+ showContext && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
585
+ ContextDrawer,
586
+ {
587
+ messagesSentCount: iter.messagesSentCount,
588
+ contextMessages,
589
+ iter
590
+ }
591
+ )
592
+ ]
593
+ }
594
+ );
453
595
  }
454
- function IterationBadge({ iter }) {
596
+ function ContextDrawer({
597
+ messagesSentCount,
598
+ contextMessages,
599
+ iter
600
+ }) {
455
601
  const t = useLensTheme();
456
- const bits = [`iter ${iter.index}`];
457
- if (iter.model) bits.push(iter.model);
458
- if (iter.inputTokens !== void 0)
459
- bits.push(`${iter.inputTokens}\u2192${iter.outputTokens ?? "?"} tok`);
460
- if (iter.durationMs !== void 0) bits.push(`${(iter.durationMs / 1e3).toFixed(2)}s`);
461
- if (iter.stopReason) bits.push(iter.stopReason);
462
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
602
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
463
603
  "div",
464
604
  {
465
605
  style: {
466
- alignSelf: "flex-start",
467
- fontSize: 10,
468
- color: t.textSubtle,
469
- textTransform: "uppercase",
470
- letterSpacing: "0.08em",
471
- fontWeight: 600,
472
- fontFamily: t.fontMono
606
+ border: `1px dashed ${t.border}`,
607
+ borderRadius: t.radius,
608
+ padding: "10px 12px",
609
+ background: t.bg,
610
+ fontSize: 12,
611
+ color: t.textMuted
473
612
  },
474
- children: bits.join(" \xB7 ")
613
+ children: [
614
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
615
+ "div",
616
+ {
617
+ style: {
618
+ fontSize: 10,
619
+ color: t.textSubtle,
620
+ textTransform: "uppercase",
621
+ letterSpacing: "0.08em",
622
+ fontWeight: 600,
623
+ marginBottom: 8
624
+ },
625
+ children: "What Neo saw before this step"
626
+ }
627
+ ),
628
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { color: t.text, marginBottom: 8 }, children: [
629
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: messagesSentCount }),
630
+ " message",
631
+ messagesSentCount === 1 ? "" : "s",
632
+ " in context",
633
+ iter.model ? ` \xB7 sent to ${iter.model}` : "",
634
+ iter.inputTokens !== void 0 && ` \xB7 ${iter.inputTokens} input tokens`
635
+ ] }),
636
+ 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: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
638
+ "span",
639
+ {
640
+ style: {
641
+ display: "inline-block",
642
+ padding: "1px 6px",
643
+ borderRadius: 3,
644
+ 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,
645
+ color: m.role === "user" ? t.accent : m.role === "tool" ? t.success : t.text,
646
+ fontFamily: t.fontMono,
647
+ fontSize: 10,
648
+ fontWeight: 600,
649
+ textTransform: "uppercase",
650
+ marginRight: 6
651
+ },
652
+ children: m.role
653
+ }
654
+ ),
655
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.textMuted }, children: summarizeMessage(m) })
656
+ ] }, i)) }),
657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
658
+ "div",
659
+ {
660
+ style: {
661
+ marginTop: 10,
662
+ fontSize: 11,
663
+ color: t.textSubtle,
664
+ fontStyle: "italic"
665
+ },
666
+ children: "Plus the system configuration (see top of conversation) and the tools Neo had access to."
667
+ }
668
+ )
669
+ ]
475
670
  }
476
671
  );
477
672
  }
673
+ function summarizeMessage(m) {
674
+ if (m.role === "tool") {
675
+ return `tool result (${m.content.length.toLocaleString()} chars)`;
676
+ }
677
+ const t = m.content.replace(/\s+/g, " ").trim();
678
+ return t.length > 120 ? t.slice(0, 120) + "\u2026" : t;
679
+ }
478
680
  function ToolCallCard({
479
681
  invocation,
480
682
  onClick
@@ -483,6 +685,7 @@ function ToolCallCard({
483
685
  const [open, setOpen] = (0, import_react.useState)(false);
484
686
  const preview = shortArgs(invocation.arguments);
485
687
  const errored = invocation.error === true;
688
+ const friendlyVerb = toolVerb(invocation);
486
689
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
487
690
  "div",
488
691
  {
@@ -507,12 +710,12 @@ function ToolCallCard({
507
710
  display: "flex",
508
711
  alignItems: "center",
509
712
  gap: 10,
510
- fontSize: 12,
511
- fontFamily: t.fontMono
713
+ fontSize: 12
512
714
  },
513
715
  children: [
514
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: invocation.name }),
515
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: t.textMuted }, children: [
716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.textMuted, fontFamily: t.fontSans }, children: friendlyVerb }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600, fontFamily: t.fontMono }, children: invocation.name }),
718
+ preview && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: t.textMuted, fontFamily: t.fontMono }, children: [
516
719
  "(",
517
720
  preview,
518
721
  ")"
@@ -527,11 +730,11 @@ function ToolCallCard({
527
730
  borderRadius: 3,
528
731
  background: `color-mix(in srgb, ${t.warning} 20%, transparent)`,
529
732
  color: t.warning,
530
- fontFamily: t.fontSans,
531
733
  fontWeight: 600,
532
734
  textTransform: "uppercase"
533
735
  },
534
- children: "decisionUpdate"
736
+ title: "This tool changed what skill is active",
737
+ children: "skill change"
535
738
  }
536
739
  ),
537
740
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.textSubtle }, children: open ? "\u25BE" : "\u25B8" })
@@ -539,10 +742,10 @@ function ToolCallCard({
539
742
  }
540
743
  ),
541
744
  open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "8px 12px", borderTop: `1px solid ${t.border}` }, children: [
542
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, children: "args" }),
745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, children: "What Neo asked for" }),
543
746
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.arguments }),
544
747
  invocation.result && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
545
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "result" }),
748
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "What the tool returned" }),
546
749
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
547
750
  "pre",
548
751
  {
@@ -563,7 +766,7 @@ function ToolCallCard({
563
766
  )
564
767
  ] }),
565
768
  invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
566
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "decisionUpdate" }),
769
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "What changed in Neo's state" }),
567
770
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.decisionUpdate })
568
771
  ] })
569
772
  ] })
@@ -571,6 +774,11 @@ function ToolCallCard({
571
774
  }
572
775
  );
573
776
  }
777
+ function toolVerb(inv) {
778
+ if (inv.name === "list_skills") return "Asked for";
779
+ if (inv.name === "read_skill") return "Activated";
780
+ return "Called";
781
+ }
574
782
  function Label({
575
783
  t,
576
784
  children,
@@ -627,14 +835,14 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
627
835
  function IterationStrip({ timeline, selectedKey, onSelect }) {
628
836
  const t = useLensTheme();
629
837
  const chips = timeline.turns.flatMap(
630
- (turn) => turn.iterations.map((iter) => ({
838
+ (turn) => turn.iterations.map((iter, stepIdx) => ({
631
839
  key: `${turn.index}.${iter.index}`,
632
840
  turn: turn.index + 1,
633
- iter: iter.index,
634
- label: chipLabel(iter),
635
- tools: iter.toolCalls.length,
841
+ step: stepIdx + 1,
842
+ label: stepHeadline(iter),
636
843
  durationMs: iter.durationMs ?? 0,
637
- stopReason: iter.stopReason
844
+ stopReason: iter.stopReason,
845
+ toolCount: iter.toolCalls.length
638
846
  }))
639
847
  );
640
848
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -653,7 +861,8 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
653
861
  chips.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: t.textSubtle, fontSize: 11 }, children: "No iterations yet." }),
654
862
  chips.map((c) => {
655
863
  const active = c.key === selectedKey;
656
- const isFinal = c.stopReason === "stop" || c.stopReason === "end_turn";
864
+ const isFinal = c.toolCount === 0;
865
+ const secs = c.durationMs >= 1e3 ? `${(c.durationMs / 1e3).toFixed(1)}s` : `${Math.round(c.durationMs)}ms`;
657
866
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
658
867
  "button",
659
868
  {
@@ -663,23 +872,27 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
663
872
  color: active ? "#fff" : t.textMuted,
664
873
  border: `1px solid ${active ? t.accent : t.border}`,
665
874
  borderRadius: 4,
666
- padding: "4px 8px",
875
+ padding: "4px 10px",
667
876
  fontSize: 11,
668
- fontFamily: "ui-monospace, monospace",
877
+ fontFamily: t.fontSans,
669
878
  cursor: "pointer",
670
879
  whiteSpace: "nowrap",
671
- flexShrink: 0
880
+ flexShrink: 0,
881
+ maxWidth: 280,
882
+ overflow: "hidden",
883
+ textOverflow: "ellipsis"
672
884
  },
673
- title: `turn ${c.turn} \xB7 iter ${c.iter} \xB7 ${c.tools} tool call${c.tools === 1 ? "" : "s"}${c.stopReason ? ` \xB7 ${c.stopReason}` : ""}`,
885
+ title: `Question ${c.turn} \xB7 Step ${c.step} \xB7 ${secs}${c.stopReason ? ` \xB7 stop: ${c.stopReason}` : ""}`,
674
886
  children: [
675
- "t",
676
- c.turn,
677
- ".i",
678
- c.iter,
679
- " \xB7 ",
680
- c.label,
681
- " ",
682
- isFinal ? "\u2713" : ""
887
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { fontWeight: 600 }, children: [
888
+ "Step ",
889
+ c.step
890
+ ] }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { opacity: 0.85 }, children: [
892
+ " \xB7 ",
893
+ c.label
894
+ ] }),
895
+ isFinal && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { marginLeft: 6 }, children: "\u2713" })
683
896
  ]
684
897
  },
685
898
  c.key
@@ -689,11 +902,21 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
689
902
  }
690
903
  );
691
904
  }
692
- function chipLabel(iter) {
693
- const d = iter.durationMs ?? 0;
694
- const secs = d >= 1e3 ? `${(d / 1e3).toFixed(1)}s` : `${Math.round(d)}ms`;
695
- const toolBit = iter.toolCalls.length > 0 ? `${iter.toolCalls.length}t` : "final";
696
- return `${secs} \xB7 ${toolBit}`;
905
+ function stepHeadline(iter) {
906
+ if (iter.toolCalls.length === 0) return "Ready to answer";
907
+ if (iter.toolCalls.length === 1) {
908
+ const tc = iter.toolCalls[0];
909
+ if (tc.name === "list_skills") return "Looking up skills";
910
+ if (tc.name === "read_skill") {
911
+ const id = tc.arguments?.id;
912
+ return id ? `Activated ${id}` : "Activating skill";
913
+ }
914
+ return `Called ${tc.name}`;
915
+ }
916
+ if (iter.toolCalls.length <= 3) {
917
+ return `Called ${iter.toolCalls.map((tc) => tc.name).join(", ")}`;
918
+ }
919
+ return `Called ${iter.toolCalls.length} tools in parallel`;
697
920
  }
698
921
 
699
922
  // src/panels/ToolCallInspector.tsx
@@ -732,13 +955,13 @@ function ToolCallInspector({
732
955
  background: t.bgElev
733
956
  },
734
957
  children: [
735
- "Tool calls \xB7 ",
958
+ "Every tool Neo called \xB7 ",
736
959
  timeline.tools.length
737
960
  ]
738
961
  }
739
962
  ),
740
963
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { overflow: "auto", flex: 1 }, children: [
741
- timeline.tools.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "No tool calls yet." }),
964
+ timeline.tools.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "Neo hasn't called any tools yet." }),
742
965
  timeline.tools.map((tc) => {
743
966
  const active = tc.id === selectedId;
744
967
  const errored = tc.error === true;
@@ -884,6 +1107,12 @@ function AgentLens({
884
1107
  fontFamily: t.fontSans
885
1108
  },
886
1109
  children: [
1110
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
1111
+ [data-iter-selected="true"] {
1112
+ outline-color: currentColor !important;
1113
+ background: color-mix(in srgb, currentColor 8%, transparent);
1114
+ }
1115
+ ` }),
887
1116
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { gridArea: "strip" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
888
1117
  IterationStrip,
889
1118
  {
@@ -897,6 +1126,7 @@ function AgentLens({
897
1126
  {
898
1127
  timeline,
899
1128
  onToolCallClick: handleToolClick,
1129
+ selectedIterKey,
900
1130
  ...derivedSystemPrompt && { systemPrompt: derivedSystemPrompt }
901
1131
  }
902
1132
  ) }),
@@ -1001,7 +1231,10 @@ var LiveTimelineBuilder = class {
1001
1231
  toolCalls: [],
1002
1232
  decisionAtStart: {},
1003
1233
  visibleTools: [],
1004
- startMs: Date.now()
1234
+ startMs: Date.now(),
1235
+ // Freeze the message count here so "What Neo saw" can reproduce
1236
+ // the context window at this exact iteration later.
1237
+ messagesSentCount: this.messages.length
1005
1238
  };
1006
1239
  this.currentTurn.iterations.push(this.currentIter);
1007
1240
  }