@tangle-network/charts 0.19.0 → 0.20.1

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.
@@ -1,4 +1,4 @@
1
- import { G as hatchPath, J as markerPath, b as isRefusal, h as eventsGap, t as timeline, u as MARK_HEADS, y as markName } from "../timeline-CVXBpZzt.js";
1
+ import { G as hatchPath, J as markerPath, b as isRefusal, h as eventsGap, t as timeline, u as MARK_HEADS, y as markName } from "../timeline-B7c-cede.js";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Fragment as Fragment$1, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  //#region src/react/sparkline.tsx
@@ -1001,10 +1001,10 @@ function HBars({ rows, format = formatCompact, color = LIVE_SERIES[0], max, limi
1001
1001
  return /* @__PURE__ */ jsxs("ol", {
1002
1002
  className: "ov-hbars",
1003
1003
  children: [rows.slice(0, limit).map((row) => {
1004
+ const title = row.title ?? `${row.label}: ${format(row.value)}`;
1004
1005
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
1005
1006
  /* @__PURE__ */ jsx("span", {
1006
1007
  className: "ov-hbar-label",
1007
- title: row.label,
1008
1008
  children: row.label
1009
1009
  }),
1010
1010
  /* @__PURE__ */ jsx("span", {
@@ -1025,8 +1025,12 @@ function HBars({ rows, format = formatCompact, color = LIVE_SERIES[0], max, limi
1025
1025
  ] });
1026
1026
  return /* @__PURE__ */ jsx("li", { children: row.href ? /* @__PURE__ */ jsx("a", {
1027
1027
  href: row.href,
1028
+ title,
1028
1029
  children: content
1029
- }) : /* @__PURE__ */ jsx("div", { children: content }) }, row.label);
1030
+ }) : /* @__PURE__ */ jsx("div", {
1031
+ title,
1032
+ children: content
1033
+ }) }, row.label);
1030
1034
  }), rest.length > 0 && /* @__PURE__ */ jsxs("li", {
1031
1035
  className: "ov-hbars-more",
1032
1036
  children: [
@@ -2500,6 +2504,29 @@ function slowestRow(rows, clockMs) {
2500
2504
  }
2501
2505
  return best;
2502
2506
  }
2507
+ /** The agents a reader should look at first: the longest one by the clock, and the costliest when two or more are priced. */
2508
+ function agentCallouts(agents, clockMs) {
2509
+ let slowest, slowestMs = -1, costliest, top = -1, priced = 0;
2510
+ for (const row of agents) {
2511
+ if (row.durationLabel === "—" || row.span.startMs > clockMs) continue;
2512
+ const ms = (spanRunning(row.span) ? Math.max(row.span.endMs, clockMs) : Math.min(row.span.endMs, clockMs)) - row.span.startMs;
2513
+ if (ms > slowestMs) {
2514
+ slowest = row;
2515
+ slowestMs = ms;
2516
+ }
2517
+ const cost = row.span.costUsd ?? row.rollupCostUsd;
2518
+ if (cost === void 0) continue;
2519
+ priced += 1;
2520
+ if (cost > top) {
2521
+ costliest = row;
2522
+ top = cost;
2523
+ }
2524
+ }
2525
+ return {
2526
+ slowestId: agents.length > 1 ? slowest?.id : void 0,
2527
+ costliestId: priced > 1 && top > 0 ? costliest?.id : void 0
2528
+ };
2529
+ }
2503
2530
  /** Busy time per agent row: the union of its own work. Their sum over wall time is the parallel speedup. */
2504
2531
  function busyMs(row, clockMs) {
2505
2532
  let total = 0, end = -Infinity;
@@ -2537,6 +2564,33 @@ function totalsAt(trace, clockMs) {
2537
2564
  errors
2538
2565
  };
2539
2566
  }
2567
+ /** The cost a reader can rely on: a lower bound when some calls are unpriced, a dash only when nothing is priced. */
2568
+ function costSummary(costUsd, trace) {
2569
+ const list = trace.costBasis === "list";
2570
+ const label = list ? "Cost · list price" : "Cost";
2571
+ if (costUsd === void 0) return trace.billedUsd !== void 0 ? {
2572
+ value: `${formatCost(trace.billedUsd)} billed`,
2573
+ title: "No list price for these calls; billed is the seat charge",
2574
+ label
2575
+ } : {
2576
+ value: "—",
2577
+ title: "No cost source: the harness recorded no cost and no price catalog was given",
2578
+ label
2579
+ };
2580
+ const coverage = trace.costCoverage;
2581
+ const partial = coverage !== void 0 && coverage.priced < coverage.total;
2582
+ const upper = !partial && trace.costUpperBound === true;
2583
+ return {
2584
+ value: `${partial ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
2585
+ title: [
2586
+ partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
2587
+ list ? "at the model catalog list price; not a bill" : "",
2588
+ upper ? "cached input is not split out, so it is priced as fresh input: at most this" : "",
2589
+ trace.billedUsd !== void 0 ? `billed ${formatCost(trace.billedUsd)} (the seat charge)` : ""
2590
+ ].filter(Boolean).join("; "),
2591
+ label
2592
+ };
2593
+ }
2540
2594
  //#endregion
2541
2595
  //#region src/react/flow-waterfall-icons.tsx
2542
2596
  const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
@@ -2636,6 +2690,8 @@ const ZoomOutGlyph = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @_
2636
2690
  const FitGlyph = () => svg(/* @__PURE__ */ jsx("path", { d: "M2.5 6V3.5a1 1 0 0 1 1-1H6M10 2.5h2.5a1 1 0 0 1 1 1V6M13.5 10v2.5a1 1 0 0 1-1 1H10M6 13.5H3.5a1 1 0 0 1-1-1V10" }));
2637
2691
  //#endregion
2638
2692
  //#region src/react/flow-waterfall-card.tsx
2693
+ /** Names wrap between words, never at their hyphens ("memscale-engineer-" / "2" read as two names). */
2694
+ const keepWords = (name) => name.replace(/-/g, "‑");
2639
2695
  const KIND_LABEL = {
2640
2696
  pipeline: "Agent",
2641
2697
  model: "LLM call",
@@ -2646,7 +2702,7 @@ const KIND_LABEL = {
2646
2702
  const unknown = /* @__PURE__ */ jsx("span", {
2647
2703
  className: "fw-unknown",
2648
2704
  title: "Not recorded by the harness",
2649
- children: "Unknown"
2705
+ children: "—"
2650
2706
  });
2651
2707
  const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
2652
2708
  title: n.toLocaleString(),
@@ -2655,23 +2711,29 @@ const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
2655
2711
  /** The span card: what ran, for how long, at what recorded cost, on what input. */
2656
2712
  function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderIcon, compact }) {
2657
2713
  const span = row.span;
2658
- const running = spanRunning(span);
2659
- const end = running ? Math.max(span.endMs, clockMs) : span.endMs;
2660
- const status = running ? "Running" : row.ok ? "Done" : "Failed";
2714
+ const status = spanRunning(span) ? "Running" : row.ok ? "Done" : "Failed";
2661
2715
  const tokens = span.kind === "model" || span.kind === "pipeline" || span.promptTokens !== void 0;
2662
2716
  const owner = agent && agent.id !== row.id ? agent : void 0;
2663
- const duration = Number.isFinite(end - span.startMs) && end >= span.startMs ? formatSpanDuration(end - span.startMs) : "—";
2717
+ const lasted = (s) => {
2718
+ const stop = spanRunning(s) ? Math.max(s.endMs, clockMs) : s.endMs;
2719
+ return Number.isFinite(stop - s.startMs) && stop >= s.startMs ? formatSpanDuration(stop - s.startMs) : "—";
2720
+ };
2721
+ const duration = lasted(span);
2664
2722
  if (compact) {
2665
2723
  const who = owner ?? row;
2666
- const outcome = who.span.outcome;
2667
- const tokenCount = span.promptTokens !== void 0 || span.completionTokens !== void 0 ? (span.promptTokens ?? 0) + (span.completionTokens ?? 0) : void 0;
2668
- const cells = [[span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind], duration]];
2669
- for (const fact of (span.facts ?? who.span.facts ?? []).slice(0, 3)) cells.push([fact.label, fact.value]);
2670
- cells.push(["Started", `+${formatSpanDuration(Math.max(0, span.startMs))}`]);
2724
+ const subject = who.span;
2725
+ const outcome = subject.outcome;
2726
+ const tokenCount = subject.promptTokens !== void 0 || subject.completionTokens !== void 0 ? (subject.promptTokens ?? 0) + (subject.completionTokens ?? 0) : void 0;
2727
+ const cost = subject.costUsd ?? who.rollupCostUsd;
2728
+ const cells = [[subject.kind === "model" ? "Thinking" : KIND_LABEL[subject.kind], lasted(subject)]];
2729
+ for (const fact of (subject.facts ?? []).slice(0, 3)) cells.push([fact.label, fact.value]);
2730
+ cells.push(["Started", `+${formatSpanDuration(Math.max(0, subject.startMs))}`]);
2671
2731
  if (tokenCount !== void 0) cells.push(["Tokens", count(tokenCount)]);
2672
- if (span.costUsd !== void 0) cells.push(["Cost", formatCost(span.costUsd)]);
2732
+ if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
2673
2733
  cells.splice(6);
2674
- const sentence = [span.output, span.id === who.id ? void 0 : span.name].find((text) => text && text !== who.name);
2734
+ const kind = span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind];
2735
+ const step = owner ? `${kind}${span.name && span.name !== kind && span.name.toLowerCase() !== span.kind ? ` ${span.name}` : ""} · ${duration}${row.ok ? "" : " · failed"}` : void 0;
2736
+ const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
2675
2737
  return /* @__PURE__ */ jsxs("div", {
2676
2738
  id,
2677
2739
  role: pinned ? "dialog" : "tooltip",
@@ -2699,7 +2761,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2699
2761
  }),
2700
2762
  /* @__PURE__ */ jsx("span", {
2701
2763
  className: "fw-card-who",
2702
- children: who.name
2764
+ children: keepWords(who.name)
2703
2765
  }),
2704
2766
  outcome ? /* @__PURE__ */ jsx("span", {
2705
2767
  className: "fw-pill fw-pill-outcome",
@@ -2732,6 +2794,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2732
2794
  className: "fw-card-cells",
2733
2795
  children: cells.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
2734
2796
  }),
2797
+ step && /* @__PURE__ */ jsx("p", {
2798
+ className: "fw-card-step",
2799
+ children: step
2800
+ }),
2735
2801
  sentence && /* @__PURE__ */ jsx("p", {
2736
2802
  className: "fw-card-result",
2737
2803
  children: sentence
@@ -2782,7 +2848,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2782
2848
  className: "fw-card-title",
2783
2849
  children: [/* @__PURE__ */ jsx("div", {
2784
2850
  className: "fw-card-name",
2785
- children: span.name
2851
+ children: keepWords(span.name)
2786
2852
  }), span.detail && /* @__PURE__ */ jsx("div", {
2787
2853
  className: "fw-muted",
2788
2854
  children: span.detail
@@ -2807,9 +2873,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2807
2873
  children: [
2808
2874
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Duration" }), /* @__PURE__ */ jsx("dd", { children: duration })] }),
2809
2875
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Started" }), /* @__PURE__ */ jsxs("dd", { children: ["+", formatSpanDuration(Math.max(0, span.startMs))] })] }),
2810
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: span.costUsd === void 0 ? unknown : formatCost(span.costUsd) })] }),
2811
- tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
2812
- tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
2876
+ span.costUsd !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: formatCost(span.costUsd) })] }),
2877
+ tokens && span.promptTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
2878
+ tokens && span.completionTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
2879
+ (span.facts ?? []).slice(0, 3).map((fact) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: fact.label }), /* @__PURE__ */ jsx("dd", { children: fact.value })] }, fact.label)),
2813
2880
  row.hasChildren && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Steps" }), /* @__PURE__ */ jsx("dd", { children: row.descendants })] })
2814
2881
  ]
2815
2882
  }),
@@ -2839,7 +2906,7 @@ const at = (startMs, endMs) => ({
2839
2906
  "--e": Number.isFinite(endMs) ? endMs : OPEN_END
2840
2907
  });
2841
2908
  function Segment({ mark, events, agentView }) {
2842
- const glyph = mark.kind === "failed" ? /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
2909
+ const glyph = mark.kind === "failed" ? agentView ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
2843
2910
  return /* @__PURE__ */ jsx("span", {
2844
2911
  className: `fw-seg fw-seg-${mark.kind}`,
2845
2912
  "data-tone": toneKey(mark.tone),
@@ -2868,7 +2935,7 @@ function endLabel(row, clockMs) {
2868
2935
  }
2869
2936
  /** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
2870
2937
  const FlowRow = memo(function FlowRow(props) {
2871
- const { row, view, top, collapsed, searching, slowest, active, focusable, selected, sticky, clockMs, renderIcon } = props;
2938
+ const { row, view, top, collapsed, searching, slowest, costliest, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
2872
2939
  const span = row.span;
2873
2940
  const running = spanRunning(span);
2874
2941
  const agentView = view === "agents";
@@ -2884,18 +2951,26 @@ const FlowRow = memo(function FlowRow(props) {
2884
2951
  "data-span-id": row.id,
2885
2952
  "data-span-kind": kind,
2886
2953
  "data-slowest": slowest || void 0,
2954
+ "data-running": running || void 0,
2887
2955
  "aria-level": agentView ? void 0 : row.depth + 1,
2888
2956
  "aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
2889
2957
  "aria-selected": selected,
2890
2958
  tabIndex: focusable ? 0 : -1,
2891
2959
  "data-tone": toneKey(row.tone),
2892
- "data-outcome": span.outcome?.tone,
2893
- className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}`,
2894
- style: { "--row-y": `${top}px` },
2960
+ "data-outcome": fillByAgent ? void 0 : span.outcome?.tone,
2961
+ className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}${motion?.enter !== void 0 ? " fw-enter" : ""}${motion?.settle ? " fw-settle" : ""}`,
2962
+ style: {
2963
+ "--row-y": `${top}px`,
2964
+ "--fw-enter-delay": `${motion?.enter ?? 0}ms`
2965
+ },
2895
2966
  onFocus: (event) => {
2896
2967
  if (event.target === event.currentTarget) props.onActivate(row);
2897
2968
  },
2898
- onClick: () => props.onActivate(row),
2969
+ onClick: (event) => {
2970
+ props.onActivate(row);
2971
+ const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
2972
+ if (bar) props.onPin(row.id, bar);
2973
+ },
2899
2974
  children: [/* @__PURE__ */ jsxs("div", {
2900
2975
  role: "gridcell",
2901
2976
  className: "fw-label",
@@ -2921,10 +2996,6 @@ const FlowRow = memo(function FlowRow(props) {
2921
2996
  className: "fw-toggle-space",
2922
2997
  "aria-hidden": true
2923
2998
  })),
2924
- agentView && /* @__PURE__ */ jsx("span", {
2925
- className: "fw-agent-dot",
2926
- "aria-hidden": true
2927
- }),
2928
2999
  !agentView && /* @__PURE__ */ jsx("span", {
2929
3000
  className: "fw-icon-tile",
2930
3001
  "aria-hidden": true,
@@ -2936,14 +3007,27 @@ const FlowRow = memo(function FlowRow(props) {
2936
3007
  children: [
2937
3008
  /* @__PURE__ */ jsxs("span", {
2938
3009
  className: `fw-span-name${isAgent || row.hasChildren ? " fw-strong" : ""}`,
2939
- children: [running && /* @__PURE__ */ jsx("span", {
2940
- className: "fw-spinner",
2941
- role: "status",
2942
- "aria-label": "running"
2943
- }), /* @__PURE__ */ jsx("span", {
2944
- className: "fw-name-text",
2945
- children: row.name
2946
- })]
3010
+ children: [
3011
+ fillByAgent && isAgent && span.outcome ? /* @__PURE__ */ jsx("span", {
3012
+ className: "fw-agent-dot",
3013
+ "data-tone": "0",
3014
+ "data-outcome": span.outcome.tone,
3015
+ title: span.outcome.label,
3016
+ "aria-hidden": true
3017
+ }) : agentView && /* @__PURE__ */ jsx("span", {
3018
+ className: "fw-agent-dot",
3019
+ "aria-hidden": true
3020
+ }),
3021
+ running && /* @__PURE__ */ jsx("span", {
3022
+ className: "fw-spinner",
3023
+ role: "status",
3024
+ "aria-label": "running"
3025
+ }),
3026
+ /* @__PURE__ */ jsx("span", {
3027
+ className: "fw-name-text",
3028
+ children: row.name
3029
+ })
3030
+ ]
2947
3031
  }),
2948
3032
  agentView && span.detail && /* @__PURE__ */ jsx("span", {
2949
3033
  className: "fw-subtitle",
@@ -2970,11 +3054,19 @@ const FlowRow = memo(function FlowRow(props) {
2970
3054
  role: "gridcell",
2971
3055
  className: "fw-lane",
2972
3056
  "data-waterfall-track": true,
2973
- "aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? ", slowest step" : ""}`,
3057
+ "aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? `, slowest ${agentView ? "agent" : "step"}` : ""}${costliest ? ", costliest agent" : ""}`,
2974
3058
  children: invalid ? /* @__PURE__ */ jsx("span", {
2975
3059
  className: "fw-invalid",
2976
3060
  children: "—"
2977
3061
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3062
+ span.marks?.map((mark) => /* @__PURE__ */ jsx("span", {
3063
+ className: "fw-mark",
3064
+ "data-mark": mark.kind,
3065
+ style: at(mark.at, mark.at),
3066
+ title: mark.label,
3067
+ "aria-label": mark.label,
3068
+ role: "img"
3069
+ }, `m-${mark.at}-${mark.label}`)),
2978
3070
  /* @__PURE__ */ jsxs("span", {
2979
3071
  className: `fw-bar fw-k-${isAgent ? "pipeline" : kind}${row.approx ? " fw-approx" : ""}${marks.length ? " fw-has-marks" : ""}${row.hasAgents ? " fw-has-agents" : ""}`,
2980
3072
  "data-tone": toneKey(row.tone),
@@ -2998,21 +3090,28 @@ const FlowRow = memo(function FlowRow(props) {
2998
3090
  events: props,
2999
3091
  agentView
3000
3092
  }, `${mark.id}-${i}`)),
3001
- !agentView && /* @__PURE__ */ jsxs("span", {
3093
+ /* @__PURE__ */ jsxs("span", {
3002
3094
  className: "fw-end",
3003
- style: at(span.startMs, running ? 0x9184e72a000 : span.endMs),
3004
- children: [/* @__PURE__ */ jsx("span", {
3005
- className: "fw-duration",
3006
- children: label
3007
- }), slowest && /* @__PURE__ */ jsx("span", {
3008
- className: "fw-badge",
3009
- children: "Slowest"
3010
- })]
3095
+ style: at(span.startMs, running ? OPEN_END : span.endMs),
3096
+ children: [
3097
+ /* @__PURE__ */ jsx("span", {
3098
+ className: "fw-duration",
3099
+ children: label
3100
+ }),
3101
+ slowest && /* @__PURE__ */ jsx("span", {
3102
+ className: "fw-badge",
3103
+ children: "Slowest"
3104
+ }),
3105
+ costliest && /* @__PURE__ */ jsx("span", {
3106
+ className: "fw-badge",
3107
+ children: "Costliest"
3108
+ })
3109
+ ]
3011
3110
  })
3012
3111
  ] })
3013
3112
  })]
3014
3113
  });
3015
- }, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.renderIcon === b.renderIcon && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
3114
+ }, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
3016
3115
  /** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
3017
3116
  function LinkMark({ link, rowHeight, hot }) {
3018
3117
  const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
@@ -3029,6 +3128,48 @@ function LinkMark({ link, rowHeight, hot }) {
3029
3128
  });
3030
3129
  }
3031
3130
  //#endregion
3131
+ //#region src/react/flow-waterfall-motion.ts
3132
+ const HOLD_MS = 700;
3133
+ const STAGGER_MS = 20;
3134
+ const STAGGER_CAP_MS = 200;
3135
+ /**
3136
+ * Grow, unfold and settle. Rows that arrived since the last change (a live span, an unfolded branch, the first paint) grow
3137
+ * from their start in a short stagger; a row that stops running settles. Motion is keyed to the row set, not to what is
3138
+ * mounted, so scrolling a virtualized tree never replays it; an entry expires after its animation so a remount stays still.
3139
+ */
3140
+ function useRowMotion(rows) {
3141
+ const known = useRef(null);
3142
+ const running = useRef(/* @__PURE__ */ new Set());
3143
+ const active = useRef(/* @__PURE__ */ new Map());
3144
+ const [expiry, setExpiry] = useState(0);
3145
+ const motion = useMemo(() => {
3146
+ const now = Date.now();
3147
+ for (const [id, entry] of active.current) if (entry.until <= now) active.current.delete(id);
3148
+ const previous = known.current;
3149
+ let order = 0;
3150
+ for (const row of rows) {
3151
+ const isRunning = spanRunning(row.span);
3152
+ if (!previous || !previous.has(row.id)) active.current.set(row.id, {
3153
+ enter: Math.min(order++ * STAGGER_MS, STAGGER_CAP_MS),
3154
+ until: now + HOLD_MS + STAGGER_CAP_MS
3155
+ });
3156
+ else if (running.current.has(row.id) && !isRunning) active.current.set(row.id, {
3157
+ settle: true,
3158
+ until: now + HOLD_MS
3159
+ });
3160
+ }
3161
+ known.current = new Set(rows.map((row) => row.id));
3162
+ running.current = new Set(rows.filter((row) => spanRunning(row.span)).map((row) => row.id));
3163
+ return new Map(active.current);
3164
+ }, [rows, expiry]);
3165
+ useEffect(() => {
3166
+ if (motion.size === 0) return;
3167
+ const timer = setTimeout(() => setExpiry((value) => value + 1), 920);
3168
+ return () => clearTimeout(timer);
3169
+ }, [motion]);
3170
+ return motion;
3171
+ }
3172
+ //#endregion
3032
3173
  //#region src/react/flow-waterfall-time.ts
3033
3174
  /** Time-axis geometry for the trace inspector: clock-aligned steps, labels and the zoom window. */
3034
3175
  const STEPS = [
@@ -3108,7 +3249,7 @@ function clampWindow(view, limitMs) {
3108
3249
  //#region src/react/flow-waterfall-body.tsx
3109
3250
  const ROW_HEIGHT = {
3110
3251
  spans: 36,
3111
- agents: 52
3252
+ agents: 40
3112
3253
  };
3113
3254
  const OVERSCAN = 8;
3114
3255
  /** Rows re-render once per CHUNK rows scrolled; overscan covers the rows in between. */
@@ -3181,6 +3322,7 @@ function FlowBody(props) {
3181
3322
  view
3182
3323
  ]);
3183
3324
  const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
3325
+ const motion = useRowMotion(rows);
3184
3326
  const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
3185
3327
  const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
3186
3328
  const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
@@ -3310,7 +3452,7 @@ function FlowBody(props) {
3310
3452
  handle.addEventListener("pointerup", up);
3311
3453
  };
3312
3454
  const axis = /* @__PURE__ */ jsxs("div", {
3313
- className: `fw-axis fw-axis-${view === "agents" ? "bottom" : "top"}`,
3455
+ className: "fw-axis fw-axis-top",
3314
3456
  children: [/* @__PURE__ */ jsxs("div", {
3315
3457
  className: "fw-axis-label",
3316
3458
  children: [view === "spans" && /* @__PURE__ */ jsx(Fragment, { children: props.onExpandAll && /* @__PURE__ */ jsxs("span", {
@@ -3349,14 +3491,27 @@ function FlowBody(props) {
3349
3491
  ref: props.onTrack,
3350
3492
  onPointerDown: scrub,
3351
3493
  title: "Drag to scrub",
3352
- children: [ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3353
- className: "fw-tick",
3354
- style: { "--s": tick },
3355
- children: formatClock(tick)
3356
- }, tick)), showCursor && /* @__PURE__ */ jsx("span", {
3357
- className: "fw-cursor-handle",
3358
- "aria-hidden": true
3359
- })]
3494
+ children: [
3495
+ ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3496
+ className: "fw-tick",
3497
+ style: { "--s": tick },
3498
+ children: formatClock(tick)
3499
+ }, tick)),
3500
+ props.markers?.map((marker) => /* @__PURE__ */ jsx("button", {
3501
+ type: "button",
3502
+ className: "fw-event",
3503
+ "data-tone": marker.tone ?? "neutral",
3504
+ style: { "--s": marker.at },
3505
+ title: marker.kind ? `${marker.kind}: ${marker.label}` : marker.label,
3506
+ "aria-label": `Jump to ${marker.kind ? `${marker.kind} ` : ""}${marker.label}`,
3507
+ onPointerDown: (event) => event.stopPropagation(),
3508
+ onClick: () => props.onScrub(marker.at)
3509
+ }, `e-${marker.at}-${marker.label}`)),
3510
+ showCursor && /* @__PURE__ */ jsx("span", {
3511
+ className: "fw-cursor-handle",
3512
+ "aria-hidden": true
3513
+ })
3514
+ ]
3360
3515
  })]
3361
3516
  });
3362
3517
  const row = (item, i, isSticky = false) => /* @__PURE__ */ jsx(FlowRow, {
@@ -3366,12 +3521,15 @@ function FlowBody(props) {
3366
3521
  collapsed: collapsed.has(item.id),
3367
3522
  searching,
3368
3523
  slowest: item.id === slowestId,
3524
+ costliest: item.id === props.costliestId,
3369
3525
  active: item.id === activeId,
3370
3526
  focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
3371
3527
  selected: item.id === selectedId,
3372
3528
  sticky: isSticky,
3529
+ motion: isSticky ? void 0 : motion.get(item.id),
3373
3530
  clockMs,
3374
3531
  renderIcon: props.renderIcon,
3532
+ fillByAgent: props.fillByAgent,
3375
3533
  onToggle: props.onToggle,
3376
3534
  onInspect: props.onInspect,
3377
3535
  onLeave: props.onLeave,
@@ -3381,83 +3539,91 @@ function FlowBody(props) {
3381
3539
  return /* @__PURE__ */ jsxs("div", {
3382
3540
  className: `fw-body fw-view-${view}${zoomed ? " fw-zoomed" : ""}`,
3383
3541
  ref: surface,
3384
- children: [
3385
- view === "spans" && axis,
3386
- /* @__PURE__ */ jsxs("div", {
3387
- className: "fw-viewport",
3388
- children: [
3389
- /* @__PURE__ */ jsx("div", {
3390
- className: "fw-gridlayer",
3391
- "aria-hidden": true,
3392
- children: ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3393
- className: "fw-gridline",
3394
- style: { "--s": tick }
3395
- }, tick))
3396
- }),
3397
- /* @__PURE__ */ jsxs("div", {
3398
- ref: scroller,
3399
- role: "treegrid",
3400
- "aria-label": "Trace timeline",
3401
- "aria-rowcount": rows.length,
3402
- "aria-multiselectable": false,
3403
- tabIndex: -1,
3404
- className: "fw-scroller",
3542
+ children: [axis, /* @__PURE__ */ jsxs("div", {
3543
+ className: "fw-viewport",
3544
+ children: [
3545
+ /* @__PURE__ */ jsx("div", {
3546
+ className: "fw-gridlayer",
3547
+ "aria-hidden": true,
3548
+ children: ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3549
+ className: "fw-gridline",
3550
+ style: { "--s": tick }
3551
+ }, tick))
3552
+ }),
3553
+ /* @__PURE__ */ jsxs("div", {
3554
+ ref: scroller,
3555
+ role: "treegrid",
3556
+ "aria-label": "Trace timeline",
3557
+ "aria-rowcount": rows.length,
3558
+ "aria-multiselectable": false,
3559
+ tabIndex: -1,
3560
+ className: "fw-scroller",
3561
+ "data-primary-content": "",
3562
+ style: {
3563
+ height,
3564
+ maxHeight
3565
+ },
3566
+ onKeyDown: keydown,
3567
+ onScroll: (event) => {
3568
+ const next = Math.floor(event.currentTarget.scrollTop / rowHeight / CHUNK);
3569
+ if (next !== chunk) setChunk(next);
3570
+ },
3571
+ children: [/* @__PURE__ */ jsxs("div", {
3572
+ className: "fw-canvas",
3573
+ style: { height: rows.length * rowHeight },
3574
+ children: [/* @__PURE__ */ jsx("div", {
3575
+ className: "fw-links",
3576
+ "aria-hidden": true,
3577
+ children: links.filter((link) => Math.max(link.from, link.to) >= first && Math.min(link.from, link.to) <= last).map((link) => /* @__PURE__ */ jsx(LinkMark, {
3578
+ link,
3579
+ rowHeight,
3580
+ hot: props.focusId !== void 0 && (rows[link.from]?.id === props.focusId || rows[link.to]?.id === props.focusId)
3581
+ }, link.id))
3582
+ }), rows.slice(first, last).map((item, offset) => row(item, first + offset))]
3583
+ }), rows.length === 0 && /* @__PURE__ */ jsx("p", {
3584
+ role: "status",
3585
+ className: "fw-empty",
3586
+ children: props.emptyText
3587
+ })]
3588
+ }),
3589
+ view === "spans" && /* @__PURE__ */ jsx(StickyRows, {
3590
+ scroller,
3591
+ rows,
3592
+ index,
3593
+ rowHeight,
3594
+ render: row
3595
+ }),
3596
+ props.markers?.length || props.pauses?.length ? /* @__PURE__ */ jsxs("div", {
3597
+ className: "fw-marker-layer",
3598
+ children: [props.pauses?.map((pause) => /* @__PURE__ */ jsx("span", {
3599
+ className: "fw-pause",
3405
3600
  style: {
3406
- height,
3407
- maxHeight
3601
+ "--s": pause.startMs,
3602
+ "--e": pause.endMs
3408
3603
  },
3409
- onKeyDown: keydown,
3410
- onScroll: (event) => {
3411
- const next = Math.floor(event.currentTarget.scrollTop / rowHeight / CHUNK);
3412
- if (next !== chunk) setChunk(next);
3413
- },
3414
- children: [/* @__PURE__ */ jsxs("div", {
3415
- className: "fw-canvas",
3416
- style: { height: rows.length * rowHeight },
3417
- children: [/* @__PURE__ */ jsx("div", {
3418
- className: "fw-links",
3419
- "aria-hidden": true,
3420
- children: links.filter((link) => Math.max(link.from, link.to) >= first && Math.min(link.from, link.to) <= last).map((link) => /* @__PURE__ */ jsx(LinkMark, {
3421
- link,
3422
- rowHeight,
3423
- hot: props.focusId !== void 0 && (rows[link.from]?.id === props.focusId || rows[link.to]?.id === props.focusId)
3424
- }, link.id))
3425
- }), rows.slice(first, last).map((item, offset) => row(item, first + offset))]
3426
- }), rows.length === 0 && /* @__PURE__ */ jsx("p", {
3427
- role: "status",
3428
- className: "fw-empty",
3429
- children: props.emptyText
3430
- })]
3431
- }),
3432
- view === "spans" && /* @__PURE__ */ jsx(StickyRows, {
3433
- scroller,
3434
- rows,
3435
- index,
3436
- rowHeight,
3437
- render: row
3438
- }),
3439
- props.markers?.length ? /* @__PURE__ */ jsx("div", {
3440
- className: "fw-marker-layer",
3441
- children: props.markers.map((marker) => /* @__PURE__ */ jsx("span", {
3442
- className: "fw-marker",
3443
- "data-tone": marker.tone ?? "neutral",
3444
- style: { "--s": marker.at },
3445
- title: marker.label,
3446
- children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
3447
- className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
3448
- children: marker.label
3449
- })
3450
- }, `${marker.at}-${marker.label}`))
3451
- }) : null,
3452
- showCursor && /* @__PURE__ */ jsx("div", {
3453
- className: "fw-cursor-layer",
3454
- "aria-hidden": true,
3455
- children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
3456
- })
3457
- ]
3458
- }),
3459
- view === "agents" && axis
3460
- ]
3604
+ title: pause.label,
3605
+ children: /* @__PURE__ */ jsx("span", {
3606
+ className: "fw-pause-label",
3607
+ children: pause.label
3608
+ })
3609
+ }, `p-${pause.startMs}`)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
3610
+ className: "fw-marker",
3611
+ "data-tone": marker.tone ?? "neutral",
3612
+ style: { "--s": marker.at },
3613
+ title: marker.label,
3614
+ children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
3615
+ className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
3616
+ children: marker.label
3617
+ })
3618
+ }, `${marker.at}-${marker.label}`))]
3619
+ }) : null,
3620
+ showCursor && /* @__PURE__ */ jsx("div", {
3621
+ className: "fw-cursor-layer",
3622
+ "aria-hidden": true,
3623
+ children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
3624
+ })
3625
+ ]
3626
+ })]
3461
3627
  });
3462
3628
  }
3463
3629
  /** Ancestors of the first visible row stay pinned while their children scroll beneath them. This layer follows
@@ -3721,13 +3887,71 @@ function PlaybackBar({ view, playing, limit, clockMs, speed, zoom, marks, slider
3721
3887
  const LANE_PAD = 12;
3722
3888
  const END_LABEL_GUTTER = {
3723
3889
  spans: 120,
3724
- agents: 16
3890
+ agents: 176
3725
3891
  };
3726
3892
  const NAME_WIDTH = {
3727
3893
  spans: 320,
3728
3894
  agents: 240
3729
3895
  };
3896
+ /** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
3897
+ function summaryCells(trace, at, durationMs) {
3898
+ const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
3899
+ const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
3900
+ const modelCalls = at ? at.modelCalls : trace.modelCalls;
3901
+ const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
3902
+ return {
3903
+ cost,
3904
+ tokens,
3905
+ errors,
3906
+ summary: [
3907
+ ["Duration", formatSpanDuration(durationMs)],
3908
+ [
3909
+ cost.label,
3910
+ cost.value,
3911
+ cost.title
3912
+ ],
3913
+ [
3914
+ "Tokens",
3915
+ formatCount(tokens),
3916
+ `${tokens.toLocaleString()} tokens`
3917
+ ],
3918
+ [
3919
+ "LLM calls",
3920
+ modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
3921
+ modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
3922
+ ],
3923
+ ["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
3924
+ ["Errors", errors.toLocaleString()]
3925
+ ]
3926
+ };
3927
+ }
3730
3928
  /** One legend item per outcome label the agents carry, with its count; nothing when no agent has an outcome. */
3929
+ const TONE_ORDER = [
3930
+ "ok",
3931
+ "warn",
3932
+ "failed",
3933
+ "neutral"
3934
+ ];
3935
+ /** What each mark means, as the agent view draws it; a key only for kinds the run has. */
3936
+ function KindLegend({ rows }) {
3937
+ const has = (kind) => rows.some((row) => row.own.some((mark) => mark.kind === kind) || row.span.kind === kind);
3938
+ const shown = [
3939
+ ["model", "Model call"],
3940
+ ["tool", "Tool call"],
3941
+ ["waiting", "Waiting"],
3942
+ ["handoff", "Handoff"],
3943
+ ["failed", "Failed turn"]
3944
+ ].filter(([kind]) => kind === "waiting" ? rows.some((row) => row.kind === "pipeline") : kind === "handoff" ? rows.some((row) => row.span.handoffTo || row.hasAgents) : has(kind));
3945
+ if (shown.length === 0) return null;
3946
+ return /* @__PURE__ */ jsx("ul", {
3947
+ className: "fw-legend fw-kinds",
3948
+ "aria-label": "Key",
3949
+ children: shown.map(([kind, name]) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
3950
+ className: `fw-swatch-${kind === "waiting" ? "wait" : kind}`,
3951
+ "aria-hidden": true
3952
+ }), name] }, kind))
3953
+ });
3954
+ }
3731
3955
  function OutcomeLegend({ agents }) {
3732
3956
  const items = /* @__PURE__ */ new Map();
3733
3957
  for (const row of agents) {
@@ -3745,7 +3969,7 @@ function OutcomeLegend({ agents }) {
3745
3969
  return /* @__PURE__ */ jsxs("ul", {
3746
3970
  className: "fw-legend",
3747
3971
  "aria-label": "Outcomes",
3748
- children: [[...items].map(([label, item]) => /* @__PURE__ */ jsxs("li", { children: [
3972
+ children: [[...items].sort(([a, x], [b, y]) => TONE_ORDER.indexOf(x.tone) - TONE_ORDER.indexOf(y.tone) || a.localeCompare(b)).map(([label, item]) => /* @__PURE__ */ jsxs("li", { children: [
3749
3973
  /* @__PURE__ */ jsx("span", {
3750
3974
  className: "fw-swatch",
3751
3975
  "data-tone": "0",
@@ -3766,10 +3990,9 @@ function OutcomeLegend({ agents }) {
3766
3990
  ] })]
3767
3991
  });
3768
3992
  }
3769
- const headlineSeconds = (ms) => ms < 6e4 ? `${(ms / 1e3).toFixed(1)}s` : formatSpanDuration(ms);
3770
3993
  /** Shared trace inspector: the agent timeline and the span waterfall over one clock.
3771
3994
  * Data remains producer-owned; playback, zoom and folding change presentation only. */
3772
- function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers }) {
3995
+ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect, description }) {
3773
3996
  const root = useRef(null);
3774
3997
  const search = useRef(null);
3775
3998
  const slider = useRef(null);
@@ -3850,6 +4073,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3850
4073
  collapsed
3851
4074
  ]);
3852
4075
  const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
4076
+ const callouts = useMemo(() => view === "agents" ? agentCallouts(agents, clockMs) : {}, [
4077
+ view,
4078
+ agents,
4079
+ clockMs
4080
+ ]);
3853
4081
  const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
3854
4082
  const resized = useRef(false);
3855
4083
  useLayoutEffect(() => {
@@ -3880,7 +4108,17 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3880
4108
  if (event.key === "+" || event.key === "=") zoomBy(.5);
3881
4109
  else if (event.key === "-" || event.key === "_") zoomBy(2);
3882
4110
  else if (event.key === "0") setZoom(null);
3883
- else if (event.key === "Escape" && card) closeCard();
4111
+ else if (event.key === "Escape" && (card || selectedId)) {
4112
+ if (card) closeCard();
4113
+ onDeselect?.();
4114
+ } else if ((event.key === "n" || event.key === "p") && markers?.length) {
4115
+ const ats = markers.map((marker) => marker.at).sort((a, b) => a - b);
4116
+ const next = event.key === "n" ? ats.find((at) => at > clockMs + 1) : [...ats].reverse().find((at) => at < clockMs - 1);
4117
+ if (next !== void 0) {
4118
+ event.preventDefault();
4119
+ scrubTo(next);
4120
+ }
4121
+ }
3884
4122
  };
3885
4123
  document.addEventListener("keydown", keydown);
3886
4124
  return () => document.removeEventListener("keydown", keydown);
@@ -3907,7 +4145,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3907
4145
  const width = current.endMs - current.startMs;
3908
4146
  const end = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
3909
4147
  if (view !== "spans" || !Number.isFinite(end) || (end - span.startMs) / Math.max(1, width) > .25) return;
3910
- const pad = Math.max((end - span.startMs) * .15, 500);
4148
+ const pad = Math.max((end - span.startMs) * .15, 500, (width / 8 - (end - span.startMs)) / 2);
3911
4149
  const target = clampWindow({
3912
4150
  startMs: span.startMs - pad,
3913
4151
  endMs: end + pad
@@ -3998,6 +4236,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3998
4236
  }
3999
4237
  };
4000
4238
  const pin = useCallback((target, element) => latest.current.pin(target, element), []);
4239
+ const selection = selectedId ?? (card?.pinned ? card.target : void 0);
4001
4240
  const closeCard = () => {
4002
4241
  const target = card?.target;
4003
4242
  setCard(null);
@@ -4007,6 +4246,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4007
4246
  setPlaying(false);
4008
4247
  setPosition(Math.min(ms, clock.limitMs()));
4009
4248
  }, [clock]);
4249
+ const timeListener = useRef(onTimeChange);
4250
+ timeListener.current = onTimeChange;
4251
+ useEffect(() => {
4252
+ timeListener.current?.(position);
4253
+ }, [position]);
4010
4254
  const togglePlay = () => {
4011
4255
  if (playing) {
4012
4256
  setPosition(clock.positionMs());
@@ -4017,31 +4261,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4017
4261
  setPlaying(true);
4018
4262
  };
4019
4263
  const scrubbing = position !== null || playing;
4020
- const at = scrubbing ? totalsAt(trace, clockMs) : void 0;
4021
- const costUsd = at ? at.costUsd : trace.costUsd;
4022
- const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
4023
- const modelCalls = at ? at.modelCalls : trace.modelCalls;
4024
- const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
4025
- const summary = [
4026
- ["Duration", formatSpanDuration(scrubbing ? clockMs : limit)],
4027
- [
4028
- trace.costBasis === "list" ? "Cost · list price" : "Cost",
4029
- costUsd === void 0 ? "Unknown" : formatCost(costUsd),
4030
- costUsd === void 0 ? "The harness recorded no cost for this trace" : trace.costBasis === "list" ? "Input and output tokens at the model catalog list price; not a bill" : void 0
4031
- ],
4032
- [
4033
- "Tokens",
4034
- formatCount(tokens),
4035
- `${tokens.toLocaleString()} tokens`
4036
- ],
4037
- [
4038
- "LLM calls",
4039
- modelCalls === void 0 ? "Unknown" : modelCalls.toLocaleString(),
4040
- modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
4041
- ],
4042
- ["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
4043
- ["Errors", errors.toLocaleString()]
4044
- ];
4264
+ const { errors, summary } = summaryCells(trace, scrubbing ? totalsAt(trace, clockMs) : void 0, scrubbing ? clockMs : limit);
4045
4265
  const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
4046
4266
  const speedup = limit > 0 ? busy / limit : 0;
4047
4267
  const done = !isLive && !trace.spans.some(spanRunning);
@@ -4059,11 +4279,37 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4059
4279
  count: 1,
4060
4280
  retry: false
4061
4281
  }]), [rows]);
4282
+ const playback = /* @__PURE__ */ jsx(PlaybackBar, {
4283
+ view,
4284
+ playing,
4285
+ limit,
4286
+ clockMs,
4287
+ speed,
4288
+ zoom,
4289
+ marks: overviewMarks,
4290
+ slider,
4291
+ timeOutput,
4292
+ followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
4293
+ onTogglePlay: togglePlay,
4294
+ onRestart: () => {
4295
+ setPosition(0);
4296
+ setPlaying(false);
4297
+ },
4298
+ onSpeed: setSpeed,
4299
+ onScrub: scrubTo,
4300
+ onFollow: () => {
4301
+ setPosition(null);
4302
+ setPlaying(false);
4303
+ },
4304
+ onZoom: zoomBy,
4305
+ onFit: () => setZoom(null)
4306
+ });
4062
4307
  return /* @__PURE__ */ jsxs("section", {
4063
4308
  ref: root,
4064
4309
  "data-flow-waterfall": true,
4065
4310
  "aria-label": label,
4066
4311
  className: `fw-root fw-root-${view}`,
4312
+ "data-selection": selectedId ? "" : void 0,
4067
4313
  style: {
4068
4314
  "--fw-name-w": `${nameWidth}px`,
4069
4315
  "--fw-pad": `${LANE_PAD}px`,
@@ -4087,17 +4333,22 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4087
4333
  className: "fw-title",
4088
4334
  children: label
4089
4335
  }),
4336
+ description && /* @__PURE__ */ jsx("p", {
4337
+ className: "fw-description",
4338
+ title: description,
4339
+ children: description
4340
+ }),
4090
4341
  isLive && /* @__PURE__ */ jsx("span", {
4091
4342
  className: "fw-state fw-state-live",
4092
4343
  children: "Live"
4093
4344
  })
4094
4345
  ]
4095
4346
  }),
4096
- view === "agents" && /* @__PURE__ */ jsx("p", {
4347
+ view === "agents" && /* @__PURE__ */ jsxs("p", {
4097
4348
  className: "fw-headline",
4098
4349
  "aria-live": "polite",
4099
- children: done ? /* @__PURE__ */ jsxs(Fragment, { children: [
4100
- /* @__PURE__ */ jsx("span", {
4350
+ children: [
4351
+ done && /* @__PURE__ */ jsx("span", {
4101
4352
  className: "fw-done-mark",
4102
4353
  "aria-label": "done",
4103
4354
  children: "✓"
@@ -4105,17 +4356,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4105
4356
  agents.length,
4106
4357
  " ",
4107
4358
  agents.length === 1 ? "agent" : "agents",
4108
- " · ",
4109
- headlineSeconds(limit),
4110
- speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : "",
4111
- trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4112
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4113
- headlineSeconds(clockMs),
4114
- " · ",
4115
- tokens.toLocaleString(),
4116
- " tokens",
4117
- trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4118
- ] })
4359
+ done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
4360
+ ]
4119
4361
  }),
4120
4362
  /* @__PURE__ */ jsx(OutcomeLegend, { agents })
4121
4363
  ]
@@ -4129,6 +4371,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4129
4371
  type: "button",
4130
4372
  role: "radio",
4131
4373
  "aria-checked": view === option,
4374
+ disabled: view === option,
4132
4375
  className: "fw-view-button",
4133
4376
  onClick: () => {
4134
4377
  setView(option);
@@ -4170,7 +4413,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4170
4413
  })]
4171
4414
  })]
4172
4415
  }),
4173
- view === "spans" && /* @__PURE__ */ jsx("dl", {
4416
+ /* @__PURE__ */ jsx("dl", {
4174
4417
  className: "fw-summary",
4175
4418
  children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
4176
4419
  className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
@@ -4181,6 +4424,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4181
4424
  })]
4182
4425
  }, name))
4183
4426
  }),
4427
+ view === "agents" && playback,
4184
4428
  /* @__PURE__ */ jsx(FlowBody, {
4185
4429
  rows: visible,
4186
4430
  byId,
@@ -4189,15 +4433,18 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4189
4433
  maxHeight: rowsHeight,
4190
4434
  collapsed,
4191
4435
  searching: Boolean(query.trim()),
4192
- slowestId: slowest?.id,
4436
+ pauses: trace.pauses,
4437
+ slowestId: slowest?.id ?? callouts.slowestId,
4438
+ costliestId: callouts.costliestId,
4193
4439
  activeId,
4194
- selectedId: card?.pinned ? card.target : void 0,
4440
+ selectedId: selection,
4195
4441
  focusId: cardAgent?.id ?? (view === "agents" ? activeId : void 0),
4196
4442
  clockMs,
4197
4443
  window: win,
4198
4444
  trackPx,
4199
4445
  showCursor,
4200
4446
  markers,
4447
+ fillByAgent: agentFill === "agent",
4201
4448
  zoomed: zoom !== null,
4202
4449
  renderIcon,
4203
4450
  emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
@@ -4236,31 +4483,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4236
4483
  compact: view === "agents"
4237
4484
  })
4238
4485
  }),
4239
- /* @__PURE__ */ jsx(PlaybackBar, {
4240
- view,
4241
- playing,
4242
- limit,
4243
- clockMs,
4244
- speed,
4245
- zoom,
4246
- marks: overviewMarks,
4247
- slider,
4248
- timeOutput,
4249
- followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
4250
- onTogglePlay: togglePlay,
4251
- onRestart: () => {
4252
- setPosition(0);
4253
- setPlaying(false);
4254
- },
4255
- onSpeed: setSpeed,
4256
- onScrub: scrubTo,
4257
- onFollow: () => {
4258
- setPosition(null);
4259
- setPlaying(false);
4260
- },
4261
- onZoom: zoomBy,
4262
- onFit: () => setZoom(null)
4263
- })
4486
+ view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
4487
+ view === "spans" && playback
4264
4488
  ]
4265
4489
  });
4266
4490
  }