agentfootprint-lens 0.2.1 → 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 },
@@ -311,7 +313,15 @@ function MessagesPanel({
311
313
  },
312
314
  children: [
313
315
  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))
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
+ ))
315
325
  ]
316
326
  }
317
327
  );
@@ -348,7 +358,7 @@ function SystemBubble({ text }) {
348
358
  font: "inherit"
349
359
  },
350
360
  children: [
351
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "SYSTEM" }),
361
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "How Neo is configured" }),
352
362
  " ",
353
363
  open ? "\u25BE" : "\u25B8",
354
364
  " ",
@@ -375,31 +385,34 @@ function SystemBubble({ text }) {
375
385
  }
376
386
  function TurnBlock({
377
387
  turn,
388
+ allMessages,
378
389
  onToolCallClick
379
390
  }) {
380
391
  const t = useLensTheme();
381
392
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
382
393
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TurnHeader, { turn }),
383
394
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UserBubble, { text: turn.userPrompt }),
384
- turn.iterations.map((iter) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
395
+ turn.iterations.map((iter, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
385
396
  IterationBlock,
386
397
  {
387
398
  iter,
399
+ iterPositionInTurn: i + 1,
388
400
  turnIndex: turn.index,
401
+ allMessages,
389
402
  onToolCallClick
390
403
  },
391
404
  iter.index
392
405
  )),
393
406
  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 ",
407
+ "Answer compiled \xB7 ",
397
408
  turn.iterations.length,
398
- " iter \xB7 ",
409
+ " step",
410
+ turn.iterations.length === 1 ? "" : "s",
411
+ " \xB7 ",
399
412
  turn.totalInputTokens,
400
413
  "\u2192",
401
414
  turn.totalOutputTokens,
402
- " tok \xB7 ",
415
+ " tokens \xB7 ",
403
416
  (turn.totalDurationMs / 1e3).toFixed(1),
404
417
  "s"
405
418
  ] })
@@ -423,7 +436,7 @@ function TurnHeader({ turn }) {
423
436
  children: [
424
437
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, height: 1, background: t.border } }),
425
438
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
426
- "Turn ",
439
+ "Your question ",
427
440
  turn.index + 1
428
441
  ] }),
429
442
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, height: 1, background: t.border } })
@@ -449,13 +462,35 @@ function UserBubble({ text }) {
449
462
  }
450
463
  ) });
451
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
+ }
452
482
  function IterationBlock({
453
483
  iter,
484
+ iterPositionInTurn,
454
485
  turnIndex,
486
+ allMessages,
455
487
  onToolCallClick
456
488
  }) {
457
489
  const t = useLensTheme();
490
+ const [showContext, setShowContext] = (0, import_react.useState)(false);
458
491
  const key = `${turnIndex}.${iter.index}`;
492
+ const headline = iterationHeadline(iter);
493
+ const contextMessages = allMessages.slice(0, iter.messagesSentCount);
459
494
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
460
495
  "div",
461
496
  {
@@ -466,9 +501,6 @@ function IterationBlock({
466
501
  display: "flex",
467
502
  flexDirection: "column",
468
503
  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
504
  padding: 8,
473
505
  margin: -8,
474
506
  borderRadius: t.radius,
@@ -477,7 +509,63 @@ function IterationBlock({
477
509
  transition: "outline-color 180ms ease, background 180ms ease"
478
510
  },
479
511
  children: [
480
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IterationBadge, { iter }),
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
+ ),
481
569
  iter.assistantContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
482
570
  "div",
483
571
  {
@@ -492,35 +580,103 @@ function IterationBlock({
492
580
  children: iter.assistantContent
493
581
  }
494
582
  ),
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)) })
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
+ )
496
592
  ]
497
593
  }
498
594
  );
499
595
  }
500
- function IterationBadge({ iter }) {
596
+ function ContextDrawer({
597
+ messagesSentCount,
598
+ contextMessages,
599
+ iter
600
+ }) {
501
601
  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);
508
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
602
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
509
603
  "div",
510
604
  {
511
605
  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
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
519
612
  },
520
- 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
+ ]
521
670
  }
522
671
  );
523
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
+ }
524
680
  function ToolCallCard({
525
681
  invocation,
526
682
  onClick
@@ -529,6 +685,7 @@ function ToolCallCard({
529
685
  const [open, setOpen] = (0, import_react.useState)(false);
530
686
  const preview = shortArgs(invocation.arguments);
531
687
  const errored = invocation.error === true;
688
+ const friendlyVerb = toolVerb(invocation);
532
689
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
533
690
  "div",
534
691
  {
@@ -553,12 +710,12 @@ function ToolCallCard({
553
710
  display: "flex",
554
711
  alignItems: "center",
555
712
  gap: 10,
556
- fontSize: 12,
557
- fontFamily: t.fontMono
713
+ fontSize: 12
558
714
  },
559
715
  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: [
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: [
562
719
  "(",
563
720
  preview,
564
721
  ")"
@@ -573,11 +730,11 @@ function ToolCallCard({
573
730
  borderRadius: 3,
574
731
  background: `color-mix(in srgb, ${t.warning} 20%, transparent)`,
575
732
  color: t.warning,
576
- fontFamily: t.fontSans,
577
733
  fontWeight: 600,
578
734
  textTransform: "uppercase"
579
735
  },
580
- children: "decisionUpdate"
736
+ title: "This tool changed what skill is active",
737
+ children: "skill change"
581
738
  }
582
739
  ),
583
740
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: t.textSubtle }, children: open ? "\u25BE" : "\u25B8" })
@@ -585,10 +742,10 @@ function ToolCallCard({
585
742
  }
586
743
  ),
587
744
  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" }),
745
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, children: "What Neo asked for" }),
589
746
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.arguments }),
590
747
  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" }),
748
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "What the tool returned" }),
592
749
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
593
750
  "pre",
594
751
  {
@@ -609,7 +766,7 @@ function ToolCallCard({
609
766
  )
610
767
  ] }),
611
768
  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" }),
769
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { t, style: { marginTop: 10 }, children: "What changed in Neo's state" }),
613
770
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonBlock, { value: invocation.decisionUpdate })
614
771
  ] })
615
772
  ] })
@@ -617,6 +774,11 @@ function ToolCallCard({
617
774
  }
618
775
  );
619
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
+ }
620
782
  function Label({
621
783
  t,
622
784
  children,
@@ -673,14 +835,14 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
673
835
  function IterationStrip({ timeline, selectedKey, onSelect }) {
674
836
  const t = useLensTheme();
675
837
  const chips = timeline.turns.flatMap(
676
- (turn) => turn.iterations.map((iter) => ({
838
+ (turn) => turn.iterations.map((iter, stepIdx) => ({
677
839
  key: `${turn.index}.${iter.index}`,
678
840
  turn: turn.index + 1,
679
- iter: iter.index,
680
- label: chipLabel(iter),
681
- tools: iter.toolCalls.length,
841
+ step: stepIdx + 1,
842
+ label: stepHeadline(iter),
682
843
  durationMs: iter.durationMs ?? 0,
683
- stopReason: iter.stopReason
844
+ stopReason: iter.stopReason,
845
+ toolCount: iter.toolCalls.length
684
846
  }))
685
847
  );
686
848
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -699,7 +861,8 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
699
861
  chips.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: t.textSubtle, fontSize: 11 }, children: "No iterations yet." }),
700
862
  chips.map((c) => {
701
863
  const active = c.key === selectedKey;
702
- 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`;
703
866
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
704
867
  "button",
705
868
  {
@@ -709,23 +872,27 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
709
872
  color: active ? "#fff" : t.textMuted,
710
873
  border: `1px solid ${active ? t.accent : t.border}`,
711
874
  borderRadius: 4,
712
- padding: "4px 8px",
875
+ padding: "4px 10px",
713
876
  fontSize: 11,
714
- fontFamily: "ui-monospace, monospace",
877
+ fontFamily: t.fontSans,
715
878
  cursor: "pointer",
716
879
  whiteSpace: "nowrap",
717
- flexShrink: 0
880
+ flexShrink: 0,
881
+ maxWidth: 280,
882
+ overflow: "hidden",
883
+ textOverflow: "ellipsis"
718
884
  },
719
- 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}` : ""}`,
720
886
  children: [
721
- "t",
722
- c.turn,
723
- ".i",
724
- c.iter,
725
- " \xB7 ",
726
- c.label,
727
- " ",
728
- 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" })
729
896
  ]
730
897
  },
731
898
  c.key
@@ -735,11 +902,21 @@ function IterationStrip({ timeline, selectedKey, onSelect }) {
735
902
  }
736
903
  );
737
904
  }
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}`;
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`;
743
920
  }
744
921
 
745
922
  // src/panels/ToolCallInspector.tsx
@@ -778,13 +955,13 @@ function ToolCallInspector({
778
955
  background: t.bgElev
779
956
  },
780
957
  children: [
781
- "Tool calls \xB7 ",
958
+ "Every tool Neo called \xB7 ",
782
959
  timeline.tools.length
783
960
  ]
784
961
  }
785
962
  ),
786
963
  /* @__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." }),
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." }),
788
965
  timeline.tools.map((tc) => {
789
966
  const active = tc.id === selectedId;
790
967
  const errored = tc.error === true;
@@ -1054,7 +1231,10 @@ var LiveTimelineBuilder = class {
1054
1231
  toolCalls: [],
1055
1232
  decisionAtStart: {},
1056
1233
  visibleTools: [],
1057
- 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
1058
1238
  };
1059
1239
  this.currentTurn.iterations.push(this.currentIter);
1060
1240
  }