@tangle-network/charts 0.21.15 → 0.21.16

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.
@@ -2546,6 +2546,7 @@ function flowWaterfallRows(trace, resolutionMs) {
2546
2546
  row.hasChildren = kids.length > 0;
2547
2547
  ordered_.push(row);
2548
2548
  const marks = [];
2549
+ const own = row.span.promptTokens !== void 0 || row.span.completionTokens !== void 0 || row.span.cachedTokens !== void 0;
2549
2550
  let cost = row.span.costUsd ?? 0;
2550
2551
  let costKnown = row.span.kind !== "model" || row.span.costUsd !== void 0;
2551
2552
  for (const child of kids) {
@@ -2556,18 +2557,15 @@ function flowWaterfallRows(trace, resolutionMs) {
2556
2557
  if (childCost === void 0) {
2557
2558
  if (child.kind === "model" || child.hasChildren) costKnown = false;
2558
2559
  } else cost += childCost;
2559
- if (child.kind !== "pipeline" && child.kind !== "waiting" && ordered(child.span)) {
2560
- row.own.push(segmentOf(child));
2561
- const counts = row.counts ??= {
2562
- model: 0,
2563
- tool: 0,
2564
- failed: 0
2565
- };
2566
- if (!child.ok) counts.failed++;
2567
- else if (child.kind === "model") counts.model++;
2568
- else counts.tool++;
2569
- }
2560
+ if (child.kind !== "pipeline" && child.kind !== "waiting" && ordered(child.span)) row.own.push(segmentOf(child));
2570
2561
  }
2562
+ Object.assign(row, stepStats(kids));
2563
+ const tokens = own ? {
2564
+ prompt: row.span.promptTokens,
2565
+ completion: row.span.completionTokens,
2566
+ cached: row.span.cachedTokens
2567
+ } : sumTokens(kids);
2568
+ if (tokens) row.tokens = tokens;
2571
2569
  if (row.hasChildren && costKnown) row.rollupCostUsd = cost;
2572
2570
  if (row.hasChildren) {
2573
2571
  row.segments = mergeSegments(marks.map((mark) => ({ ...mark })), gap);
@@ -2580,6 +2578,74 @@ function flowWaterfallRows(trace, resolutionMs) {
2580
2578
  for (const row of rows) if (!row.parentId) visit(row, 0, -1);
2581
2579
  return ordered_;
2582
2580
  }
2581
+ /** What a row's direct steps add up to; nothing when it has none. */
2582
+ function stepStats(steps) {
2583
+ let counts;
2584
+ const modelMs = [];
2585
+ for (const step of steps) {
2586
+ if (step.kind === "pipeline" || step.kind === "waiting" || !ordered(step.span)) continue;
2587
+ counts ??= {
2588
+ model: 0,
2589
+ tool: 0,
2590
+ failed: 0,
2591
+ retried: 0,
2592
+ tools: {}
2593
+ };
2594
+ if (!step.ok) counts.failed++;
2595
+ if (step.retry) counts.retried++;
2596
+ if (step.kind === "model") {
2597
+ counts.model++;
2598
+ if (!spanRunning(step.span)) modelMs.push(step.span.endMs - step.span.startMs);
2599
+ } else {
2600
+ counts.tool++;
2601
+ const tool = toolOf(step.name) || step.kind;
2602
+ counts.tools[tool] = (counts.tools[tool] ?? 0) + 1;
2603
+ }
2604
+ }
2605
+ return counts ? {
2606
+ counts,
2607
+ modelMs: modelMs.sort((a, b) => a - b)
2608
+ } : {};
2609
+ }
2610
+ /** Several rows' steps as one: a family's members or a folded run. */
2611
+ function mergeStats(rows) {
2612
+ let counts;
2613
+ for (const row of rows) {
2614
+ if (!row.counts) continue;
2615
+ counts ??= {
2616
+ model: 0,
2617
+ tool: 0,
2618
+ failed: 0,
2619
+ retried: 0,
2620
+ tools: {}
2621
+ };
2622
+ counts.model += row.counts.model;
2623
+ counts.tool += row.counts.tool;
2624
+ counts.failed += row.counts.failed;
2625
+ counts.retried += row.counts.retried;
2626
+ for (const [tool, n] of Object.entries(row.counts.tools)) counts.tools[tool] = (counts.tools[tool] ?? 0) + n;
2627
+ }
2628
+ const tokens = sumTokens(rows);
2629
+ return {
2630
+ ...counts ? {
2631
+ counts,
2632
+ modelMs: rows.flatMap((row) => row.modelMs ?? []).sort((a, b) => a - b)
2633
+ } : {},
2634
+ ...tokens ? { tokens } : {}
2635
+ };
2636
+ }
2637
+ function sumTokens(rows) {
2638
+ let out;
2639
+ for (const row of rows) for (const key of [
2640
+ "prompt",
2641
+ "completion",
2642
+ "cached"
2643
+ ]) {
2644
+ const n = row.tokens?.[key];
2645
+ if (n !== void 0) (out ??= {})[key] = (out[key] ?? 0) + n;
2646
+ }
2647
+ return out;
2648
+ }
2583
2649
  function segmentOf(row) {
2584
2650
  return {
2585
2651
  startMs: row.span.startMs,
@@ -2813,6 +2879,7 @@ function agentFamilies(agents, open) {
2813
2879
  alt: i % 2 === 1
2814
2880
  }))).sort((a, b) => a.startMs - b.startMs),
2815
2881
  ...known.length ? { rollupCostUsd: known.reduce((sum, cost) => sum + cost, 0) } : {},
2882
+ ...mergeStats(members),
2816
2883
  span: {
2817
2884
  id,
2818
2885
  kind: "pipeline",
@@ -2843,35 +2910,24 @@ function agentFamilies(agents, open) {
2843
2910
  }
2844
2911
  return out;
2845
2912
  }
2846
- /** The second line of an agent row: how it ended and how much it did ("failed · 3 turns · 412 tool calls", "6 agents · 4 failed"). */
2913
+ /** The second line of an agent row in parts: how it ended and how much it did (failed, 3 LLM calls, 412 tool calls; 6 agents, 4 failed).
2914
+ * The row shows the parts in order and drops the last ones whole when the line is full, never half a word. */
2847
2915
  function agentSummary(row) {
2848
2916
  if (row.id.startsWith("family:")) {
2849
2917
  const failed = Number(row.span.meta?.failedAgents ?? 0);
2850
- return `${row.descendants} agents${failed ? ` · ${failed} failed` : ""}`;
2918
+ return [`${row.descendants} agents`, ...failed ? [`${failed} failed`] : []];
2851
2919
  }
2852
2920
  if (row.id.startsWith("run:")) {
2853
2921
  const failed = row.own.filter((mark) => mark.kind === "failed").length;
2854
- return failed ? `${failed} failed` : "all done";
2922
+ return [failed ? `${failed} failed` : "all done"];
2855
2923
  }
2856
- const counts = row.counts ?? {
2857
- model: 0,
2858
- tool: 0,
2859
- failed: 0
2860
- };
2861
- const turns = counts.model + counts.failed;
2862
- const tools = counts.tool;
2863
- const state = row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : !row.ok ? "failed" : row.kind === "pipeline" ? "" : "done");
2864
- const unit = row.kind === "pipeline" ? ["turn", "turns"] : ["model call", "model calls"];
2924
+ const calls = row.counts?.model ?? 0;
2925
+ const tools = row.counts?.tool ?? 0;
2865
2926
  return [
2866
- state,
2867
- turns ? `${turns} ${turns === 1 ? unit[0] : unit[1]}` : "",
2927
+ row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : !row.ok ? "failed" : row.kind === "pipeline" ? "" : "done"),
2928
+ calls ? `${calls} LLM ${calls === 1 ? "call" : "calls"}` : "",
2868
2929
  tools ? `${tools} tool ${tools === 1 ? "call" : "calls"}` : ""
2869
- ].filter(Boolean).join(" · ");
2870
- }
2871
- /** The summary split where it may be cut: the tool count goes first when the line does not fit, never half a word. */
2872
- function agentSummaryParts(row) {
2873
- const tools = / · (\d+ tool calls?)$/.exec(agentSummary(row));
2874
- return tools ? [agentSummary(row).slice(0, tools.index), tools[1]] : [agentSummary(row)];
2930
+ ].filter(Boolean);
2875
2931
  }
2876
2932
  /**
2877
2933
  * The agent view's rows: families folded until opened, and an opened agent's own steps (its turns and tool calls, not its
@@ -2923,6 +2979,8 @@ function stepRuns(steps) {
2923
2979
  hasChildren: false,
2924
2980
  descendants: 0,
2925
2981
  segments: [],
2982
+ ...stepStats(run),
2983
+ tokens: sumTokens(run),
2926
2984
  own: run.map((step) => ({
2927
2985
  startMs: step.span.startMs,
2928
2986
  endMs: step.span.endMs,
@@ -3114,58 +3172,110 @@ const KIND_LABEL = {
3114
3172
  waiting: "Waiting",
3115
3173
  handoff: "Handoff"
3116
3174
  };
3117
- const unknown = /* @__PURE__ */ jsx("span", {
3118
- className: "fw-unknown",
3119
- title: "Not recorded by the harness",
3120
- children: "—"
3121
- });
3122
- const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
3123
- title: n.toLocaleString(),
3124
- children: formatCount(n)
3175
+ const count = (n, unit) => /* @__PURE__ */ jsxs("span", {
3176
+ title: `${n.toLocaleString()} ${unit}`,
3177
+ children: [
3178
+ formatCount(n),
3179
+ " ",
3180
+ unit
3181
+ ]
3125
3182
  });
3126
- /** The span card: what ran, for how long, at what recorded cost, on what input. */
3183
+ const plural = (n, one, many = `${one}s`) => `${n.toLocaleString()} ${n === 1 ? one : many}`;
3184
+ /** Nearest-rank percentile of ascending values. */
3185
+ const percentile = (sorted, p) => sorted[Math.min(sorted.length - 1, Math.ceil(p * sorted.length) - 1)];
3186
+ const TOOLS_SHOWN = 4;
3187
+ /** The card's table: what the row recorded, every value with its unit. A value nobody recorded is left out, never printed as zero. */
3188
+ function metrics(row, clockMs, full) {
3189
+ const span = row.span;
3190
+ const out = [];
3191
+ if (span.detail) out.push([span.kind === "tool" ? "Provider" : "Model", span.detail]);
3192
+ const stop = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
3193
+ out.push(["Duration", Number.isFinite(stop - span.startMs) && stop >= span.startMs ? formatSpanDuration(stop - span.startMs) : "—"]);
3194
+ if (full) out.push(["Started", `+${formatSpanDuration(Math.max(0, span.startMs))}`]);
3195
+ const tokens = row.tokens;
3196
+ if (tokens) {
3197
+ const parts = [
3198
+ ["prompt", "in"],
3199
+ ["completion", "out"],
3200
+ ["cached", "cached"]
3201
+ ].flatMap(([key, unit]) => tokens[key] === void 0 ? [] : [count(tokens[key], unit)]);
3202
+ out.push(["Tokens", /* @__PURE__ */ jsx("span", {
3203
+ className: "fw-card-parts",
3204
+ children: parts.map((part, i) => /* @__PURE__ */ jsx("span", { children: part }, i))
3205
+ })]);
3206
+ }
3207
+ const latency = row.modelMs;
3208
+ if (latency?.length) out.push(["LLM latency", /* @__PURE__ */ jsxs("span", {
3209
+ className: "fw-card-parts",
3210
+ children: [/* @__PURE__ */ jsxs("span", { children: [formatSpanDuration(percentile(latency, .5)), " median"] }), latency.length >= 10 && /* @__PURE__ */ jsxs("span", { children: [formatSpanDuration(percentile(latency, .95)), " p95"] })]
3211
+ })]);
3212
+ const cost = span.costUsd ?? row.rollupCostUsd;
3213
+ if (cost !== void 0) out.push(["Cost", rowCostText(row, cost)]);
3214
+ const counts = row.counts;
3215
+ if (counts?.model) out.push(["LLM calls", counts.model.toLocaleString()]);
3216
+ if (counts?.tool) {
3217
+ const tools = Object.entries(counts.tools).sort(([a, x], [b, y]) => y - x || a.localeCompare(b));
3218
+ const rest = tools.length - TOOLS_SHOWN;
3219
+ out.push(["Tool calls", /* @__PURE__ */ jsxs(Fragment, { children: [counts.tool.toLocaleString(), /* @__PURE__ */ jsxs("span", {
3220
+ className: "fw-card-tools",
3221
+ title: tools.map(([tool, n]) => `${tool} ${n}`).join(", "),
3222
+ children: [tools.slice(0, rest > 1 ? TOOLS_SHOWN : tools.length).map(([tool, n]) => /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", { children: tool }), /* @__PURE__ */ jsx("span", { children: n.toLocaleString() })] }, tool)), rest > 1 && /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsxs("span", { children: [rest, " more tools"] }), /* @__PURE__ */ jsx("span", { children: tools.slice(TOOLS_SHOWN).reduce((sum, [, n]) => sum + n, 0).toLocaleString() })] })]
3223
+ })] })]);
3224
+ }
3225
+ if (counts?.failed) out.push(["Failures", plural(counts.failed, "failed step")]);
3226
+ const restarts = span.marks?.filter((mark) => mark.kind === "restart").length ?? 0;
3227
+ if (counts?.retried || restarts) out.push(["Retries", [counts?.retried ? plural(counts.retried, "step retried", "steps retried") : "", restarts ? plural(restarts, "restart") : ""].filter(Boolean).join(" · ")]);
3228
+ if (full && row.hasChildren) out.push(["Steps", row.descendants.toLocaleString()]);
3229
+ for (const fact of span.facts ?? []) if (!out.some(([label]) => label.toLowerCase() === fact.label.toLowerCase())) out.push([fact.label, fact.value]);
3230
+ return out;
3231
+ }
3232
+ function Table({ rows }) {
3233
+ return /* @__PURE__ */ jsx("dl", {
3234
+ className: "fw-card-table",
3235
+ children: rows.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
3236
+ });
3237
+ }
3238
+ /** The span card: what ran, for how long, on what tokens and at what recorded cost, then its input and output. */
3127
3239
  function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderIcon, compact }) {
3128
3240
  const span = row.span;
3129
3241
  const status = spanRunning(span) ? "Running" : row.ok ? "Done" : "Failed";
3130
- const tokens = span.kind === "model" || span.kind === "pipeline" || span.promptTokens !== void 0;
3131
3242
  const owner = agent && agent.id !== row.id ? agent : void 0;
3132
- const lasted = (s) => {
3133
- const stop = spanRunning(s) ? Math.max(s.endMs, clockMs) : s.endMs;
3134
- return Number.isFinite(stop - s.startMs) && stop >= s.startMs ? formatSpanDuration(stop - s.startMs) : "—";
3243
+ const frame = {
3244
+ id,
3245
+ role: pinned ? "dialog" : "tooltip",
3246
+ "aria-label": pinned ? `${span.name} details` : void 0,
3247
+ style: {
3248
+ left,
3249
+ top
3250
+ },
3251
+ onKeyDown: (event) => {
3252
+ if (event.key === "Escape") {
3253
+ event.stopPropagation();
3254
+ onClose();
3255
+ }
3256
+ }
3135
3257
  };
3136
- const duration = lasted(span);
3258
+ const close = pinned && /* @__PURE__ */ jsx("button", {
3259
+ type: "button",
3260
+ className: "fw-icon-button fw-card-close",
3261
+ "aria-label": "Close details",
3262
+ onClick: onClose,
3263
+ autoFocus: true,
3264
+ children: /* @__PURE__ */ jsx(CloseGlyph, {})
3265
+ });
3137
3266
  if (compact) {
3138
3267
  const who = owner ?? row;
3139
- const subject = who.span;
3140
- const outcome = subject.outcome;
3141
- const tokenCount = subject.promptTokens !== void 0 || subject.completionTokens !== void 0 ? (subject.promptTokens ?? 0) + (subject.completionTokens ?? 0) : void 0;
3142
- const cost = subject.costUsd ?? who.rollupCostUsd;
3143
- const cells = [[subject.kind === "model" ? "Thinking" : KIND_LABEL[subject.kind], lasted(subject)]];
3144
- for (const fact of (subject.facts ?? []).slice(0, 3)) cells.push([fact.label, fact.value]);
3145
- cells.push(["Started", `+${formatSpanDuration(Math.max(0, subject.startMs))}`]);
3146
- if (tokenCount !== void 0) cells.push(["Tokens", count(tokenCount)]);
3147
- if (cost !== void 0) cells.push(["Cost", rowCostText(who, cost)]);
3148
- cells.splice(6);
3268
+ const outcome = who.span.outcome;
3149
3269
  const kind = span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind];
3150
- const step = owner && span.endMs > span.startMs ? `${kind}${span.name && span.name !== kind && span.name.toLowerCase() !== span.kind ? ` ${span.name}` : ""} · ${duration}${row.ok ? "" : " · failed"}` : void 0;
3270
+ const named = span.name && span.name.toLowerCase() !== kind.toLowerCase() && span.name.toLowerCase() !== span.kind ? ` ${span.name}` : "";
3271
+ const stop = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
3272
+ const step = owner && span.endMs > span.startMs ? `${kind}${named} · ${formatSpanDuration(stop - span.startMs)}${row.ok ? "" : " · failed"}` : void 0;
3151
3273
  const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
3152
3274
  return /* @__PURE__ */ jsxs("div", {
3153
- id,
3154
- role: pinned ? "dialog" : "tooltip",
3155
- "aria-label": pinned ? `${span.name} details` : void 0,
3275
+ ...frame,
3156
3276
  className: "fw-card fw-card-compact",
3157
3277
  "data-tone": toneKey(who.tone),
3158
3278
  "data-outcome": outcome?.tone,
3159
- style: {
3160
- left,
3161
- top
3162
- },
3163
- onKeyDown: (event) => {
3164
- if (event.key === "Escape") {
3165
- event.stopPropagation();
3166
- onClose();
3167
- }
3168
- },
3169
3279
  children: [
3170
3280
  /* @__PURE__ */ jsxs("div", {
3171
3281
  className: "fw-card-agent",
@@ -3187,28 +3297,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
3187
3297
  className: `fw-pill fw-pill-${status.toLowerCase()}`,
3188
3298
  children: status
3189
3299
  }),
3190
- pinned && /* @__PURE__ */ jsx("button", {
3191
- type: "button",
3192
- className: "fw-icon-button",
3193
- style: {
3194
- width: 20,
3195
- height: 20
3196
- },
3197
- "aria-label": "Close details",
3198
- onClick: onClose,
3199
- autoFocus: true,
3200
- children: /* @__PURE__ */ jsx(CloseGlyph, {})
3201
- })
3300
+ close
3202
3301
  ]
3203
3302
  }),
3204
- who.span.detail && /* @__PURE__ */ jsx("p", {
3205
- className: "fw-card-detail",
3206
- children: who.span.detail
3207
- }),
3208
- /* @__PURE__ */ jsx("dl", {
3209
- className: "fw-card-cells",
3210
- children: cells.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
3211
- }),
3303
+ /* @__PURE__ */ jsx(Table, { rows: metrics(who, clockMs, false) }),
3212
3304
  step && /* @__PURE__ */ jsx("p", {
3213
3305
  className: "fw-card-step",
3214
3306
  children: step
@@ -3221,35 +3313,16 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
3221
3313
  });
3222
3314
  }
3223
3315
  return /* @__PURE__ */ jsxs("div", {
3224
- id,
3225
- role: pinned ? "dialog" : "tooltip",
3226
- "aria-label": pinned ? `${span.name} details` : void 0,
3316
+ ...frame,
3227
3317
  className: "fw-card",
3228
3318
  "data-tone": toneKey(row.tone),
3229
- style: {
3230
- left,
3231
- top
3232
- },
3233
- onKeyDown: (event) => {
3234
- if (event.key === "Escape") {
3235
- event.stopPropagation();
3236
- onClose();
3237
- }
3238
- },
3239
3319
  children: [
3240
3320
  owner && /* @__PURE__ */ jsxs("div", {
3241
3321
  className: "fw-card-agent",
3242
- children: [
3243
- /* @__PURE__ */ jsx("span", {
3244
- className: "fw-agent-dot",
3245
- "aria-hidden": true
3246
- }),
3247
- owner.name,
3248
- owner.span.detail && /* @__PURE__ */ jsxs("span", {
3249
- className: "fw-muted",
3250
- children: [" ", owner.span.detail]
3251
- })
3252
- ]
3322
+ children: [/* @__PURE__ */ jsx("span", {
3323
+ className: "fw-agent-dot",
3324
+ "aria-hidden": true
3325
+ }), owner.name]
3253
3326
  }),
3254
3327
  /* @__PURE__ */ jsxs("div", {
3255
3328
  className: "fw-card-head",
@@ -3262,42 +3335,18 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
3262
3335
  name: span.name
3263
3336
  })
3264
3337
  }),
3265
- /* @__PURE__ */ jsxs("div", {
3266
- className: "fw-card-title",
3267
- children: [/* @__PURE__ */ jsx("div", {
3268
- className: "fw-card-name",
3269
- children: keepWords(span.name)
3270
- }), span.detail && /* @__PURE__ */ jsx("div", {
3271
- className: "fw-muted",
3272
- children: span.detail
3273
- })]
3338
+ /* @__PURE__ */ jsx("div", {
3339
+ className: "fw-card-name",
3340
+ children: keepWords(span.name)
3274
3341
  }),
3275
3342
  /* @__PURE__ */ jsx("span", {
3276
3343
  className: `fw-pill fw-pill-${status.toLowerCase()}`,
3277
3344
  children: status
3278
3345
  }),
3279
- pinned && /* @__PURE__ */ jsx("button", {
3280
- type: "button",
3281
- className: "fw-icon-button",
3282
- "aria-label": "Close details",
3283
- onClick: onClose,
3284
- autoFocus: true,
3285
- children: /* @__PURE__ */ jsx(CloseGlyph, {})
3286
- })
3287
- ]
3288
- }),
3289
- /* @__PURE__ */ jsxs("dl", {
3290
- className: "fw-card-stats",
3291
- children: [
3292
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Duration" }), /* @__PURE__ */ jsx("dd", { children: duration })] }),
3293
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Started" }), /* @__PURE__ */ jsxs("dd", { children: ["+", formatSpanDuration(Math.max(0, span.startMs))] })] }),
3294
- span.costUsd !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: rowCostText(row, span.costUsd) })] }),
3295
- tokens && span.promptTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
3296
- tokens && span.completionTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
3297
- (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)),
3298
- row.hasChildren && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Steps" }), /* @__PURE__ */ jsx("dd", { children: row.descendants })] })
3346
+ close
3299
3347
  ]
3300
3348
  }),
3349
+ /* @__PURE__ */ jsx(Table, { rows: metrics(row, clockMs, true) }),
3301
3350
  span.input && /* @__PURE__ */ jsxs("div", {
3302
3351
  className: "fw-card-text",
3303
3352
  children: [/* @__PURE__ */ jsx("div", {
@@ -3323,7 +3372,7 @@ const at = (startMs, endMs) => ({
3323
3372
  "--s": startMs,
3324
3373
  "--e": Number.isFinite(endMs) ? endMs : OPEN_END
3325
3374
  });
3326
- const endLabelWidth = (label, slowest, costliest) => label.length * 7 + 8 + (slowest ? 74 : 0) + (costliest ? 84 : 0);
3375
+ const endLabelWidth = (label, slowest, costliest) => label.length * 7 + 8 + (slowest ? 80 : 0) + (costliest ? 90 : 0);
3327
3376
  function Segment({ mark, events, agentView, ending }) {
3328
3377
  const glyph = mark.kind === "failed" ? agentView && !ending ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
3329
3378
  const named = agentView && mark.kind === "tool" && (mark.count === 1 ? mark.name : mark.tool && `${mark.tool} ×${mark.count}`);
@@ -3332,10 +3381,10 @@ function Segment({ mark, events, agentView, ending }) {
3332
3381
  "data-tone": toneKey(mark.tone),
3333
3382
  style: at(mark.startMs, mark.endMs),
3334
3383
  title: mark.name ? `${mark.name}${mark.count > 1 ? ` +${mark.count - 1} more` : ""}` : void 0,
3335
- onPointerEnter: agentView ? (event) => events.onInspect(mark.id, event.currentTarget) : void 0,
3384
+ onPointerEnter: agentView ? (event) => events.onInspect(mark.id, event.currentTarget, event.clientX) : void 0,
3336
3385
  onClick: agentView ? (event) => {
3337
3386
  event.stopPropagation();
3338
- events.onPin(mark.id, event.currentTarget);
3387
+ events.onPin(mark.id, event.currentTarget, event.clientX);
3339
3388
  } : void 0,
3340
3389
  children: glyph ? /* @__PURE__ */ jsx("span", {
3341
3390
  className: "fw-glyph",
@@ -3369,8 +3418,8 @@ const FlowRow = memo(function FlowRow(props) {
3369
3418
  const kind = !row.ok ? "failed" : row.kind;
3370
3419
  const marks = isAgent || row.hasChildren || row.id.startsWith("run:") ? folded && !agentView ? row.segments : row.own : [];
3371
3420
  const label = endLabel(row, clockMs);
3372
- const summary = agentSummaryParts(row);
3373
- const inspect = (event) => props.onInspect(row.id, event.currentTarget);
3421
+ const summary = [...span.detail && span.detail !== props.sharedDetail ? [span.detail] : [], ...agentSummary(row)];
3422
+ const inspect = (event) => props.onInspect(row.id, event.currentTarget, event.clientX);
3374
3423
  const family = agentView && row.id.startsWith("family:");
3375
3424
  const step = agentView && row.stepOf !== void 0;
3376
3425
  const opens = family || agentView && isAgent && row.own.length > 0;
@@ -3392,7 +3441,8 @@ const FlowRow = memo(function FlowRow(props) {
3392
3441
  className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}${step ? " fw-step" : ""}${motion?.enter !== void 0 ? " fw-enter" : ""}${motion?.settle ? " fw-settle" : ""}`,
3393
3442
  style: {
3394
3443
  "--row-y": `${top}px`,
3395
- "--fw-enter-delay": `${motion?.enter ?? 0}ms`
3444
+ "--fw-enter-delay": `${motion?.enter ?? 0}ms`,
3445
+ "--fw-end-w": `${endLabelWidth(label, slowest, costliest)}px`
3396
3446
  },
3397
3447
  onFocus: (event) => {
3398
3448
  if (event.target === event.currentTarget) props.onActivate(row);
@@ -3404,7 +3454,7 @@ const FlowRow = memo(function FlowRow(props) {
3404
3454
  return;
3405
3455
  }
3406
3456
  const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
3407
- if (bar) props.onPin(row.id, bar);
3457
+ if (bar) props.onPin(row.id, bar, event.target instanceof Element && event.target.closest(".fw-lane") ? event.clientX : void 0);
3408
3458
  },
3409
3459
  children: [/* @__PURE__ */ jsxs("div", {
3410
3460
  role: "gridcell",
@@ -3491,11 +3541,11 @@ const FlowRow = memo(function FlowRow(props) {
3491
3541
  className: "fw-subtitle-tail",
3492
3542
  children: [/* @__PURE__ */ jsx("span", {
3493
3543
  className: "fw-subtitle-text",
3494
- children: span.detail && span.detail !== props.sharedDetail ? `${span.detail} · ${summary[0]}` : summary[0]
3495
- }), summary[1] && /* @__PURE__ */ jsxs("span", {
3544
+ children: summary[0]
3545
+ }), summary.slice(1).map((part) => /* @__PURE__ */ jsxs("span", {
3496
3546
  className: "fw-subtitle-extra",
3497
- children: ["· ", summary[1]]
3498
- })]
3547
+ children: ["· ", part]
3548
+ }, part))]
3499
3549
  })]
3500
3550
  })]
3501
3551
  }),
@@ -3535,7 +3585,7 @@ const FlowRow = memo(function FlowRow(props) {
3535
3585
  onClick: (event) => {
3536
3586
  event.stopPropagation();
3537
3587
  if (family) props.onToggle(row);
3538
- else props.onPin(row.id, event.currentTarget);
3588
+ else props.onPin(row.id, event.currentTarget, event.clientX);
3539
3589
  },
3540
3590
  children: [!isAgent && !row.ok && /* @__PURE__ */ jsx("span", {
3541
3591
  className: "fw-glyph",
@@ -3553,10 +3603,7 @@ const FlowRow = memo(function FlowRow(props) {
3553
3603
  }, `${mark.id}-${i}`)),
3554
3604
  /* @__PURE__ */ jsxs("span", {
3555
3605
  className: "fw-end",
3556
- style: {
3557
- ...at(span.startMs, running ? OPEN_END : span.endMs),
3558
- "--fw-end-w": `${endLabelWidth(label, slowest, costliest)}px`
3559
- },
3606
+ style: at(span.startMs, running ? OPEN_END : span.endMs),
3560
3607
  children: [
3561
3608
  /* @__PURE__ */ jsx("span", {
3562
3609
  className: "fw-duration",
@@ -3820,22 +3867,6 @@ function FlowBody(props) {
3820
3867
  trackPx,
3821
3868
  view
3822
3869
  ]);
3823
- const pauseLabels = useMemo(() => {
3824
- const shown = /* @__PURE__ */ new Set();
3825
- let freeFrom = -Infinity;
3826
- for (const pause of [...props.pauses ?? []].sort((a, b) => a.startMs - b.startMs)) {
3827
- const width = duration(pause.endMs - pause.startMs).length * 7 + 24;
3828
- const centre = ((pause.startMs + pause.endMs) / 2 - win.startMs) / msPerPx;
3829
- if (centre - width / 2 < freeFrom) continue;
3830
- shown.add(pause);
3831
- freeFrom = centre + width / 2 + 8;
3832
- }
3833
- return shown;
3834
- }, [
3835
- props.pauses,
3836
- win.startMs,
3837
- msPerPx
3838
- ]);
3839
3870
  useEffect(() => {
3840
3871
  const element = scroller.current;
3841
3872
  if (!element) return;
@@ -4017,18 +4048,21 @@ function FlowBody(props) {
4017
4048
  props.onScrub(marker.at);
4018
4049
  }
4019
4050
  }, `e-${marker.at}-${marker.label}`)),
4020
- props.pauses?.filter((pause) => pauseLabels.has(pause)).map((pause) => /* @__PURE__ */ jsx("span", {
4021
- className: "fw-pause-label",
4022
- style: {
4023
- "--s": pause.startMs,
4024
- "--e": pause.endMs
4025
- },
4026
- title: pause.detail ? `${pause.label}: ${pause.detail}` : pause.label,
4027
- tabIndex: 0,
4028
- "aria-label": `${pause.label}, ${duration(pause.endMs - pause.startMs)}${pause.detail ? `: ${pause.detail}` : ""}`,
4029
- onPointerDown: (event) => event.stopPropagation(),
4030
- children: /* @__PURE__ */ jsx("b", { children: duration(pause.endMs - pause.startMs) })
4031
- }, `pl-${pause.startMs}`)),
4051
+ props.pauses?.map((pause) => {
4052
+ const said = `${pause.label} ${duration(pause.endMs - pause.startMs)}${pause.detail ? `: ${pause.detail}` : ""}`;
4053
+ return /* @__PURE__ */ jsx("span", {
4054
+ className: "fw-pause-mark",
4055
+ style: {
4056
+ "--s": pause.startMs,
4057
+ "--e": pause.endMs
4058
+ },
4059
+ title: said,
4060
+ "aria-label": said,
4061
+ role: "img",
4062
+ tabIndex: 0,
4063
+ onPointerDown: (event) => event.stopPropagation()
4064
+ }, `pm-${pause.startMs}`);
4065
+ }),
4032
4066
  jumped && /* @__PURE__ */ jsx("span", {
4033
4067
  className: "fw-event-label",
4034
4068
  "data-tone": jumped.tone ?? "neutral",
@@ -4077,14 +4111,24 @@ function FlowBody(props) {
4077
4111
  /* @__PURE__ */ jsxs("div", {
4078
4112
  className: "fw-gridlayer",
4079
4113
  "aria-hidden": true,
4080
- children: [ticks.map((tick) => /* @__PURE__ */ jsx("span", {
4081
- className: "fw-gridline",
4082
- style: { "--s": tick }
4083
- }, tick)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
4084
- className: "fw-marker-line",
4085
- "data-tone": marker.tone ?? "neutral",
4086
- style: { "--s": marker.at }
4087
- }, `l-${marker.at}-${marker.label}`))]
4114
+ children: [
4115
+ props.pauses?.map((pause) => /* @__PURE__ */ jsx("span", {
4116
+ className: "fw-pause",
4117
+ style: {
4118
+ "--s": pause.startMs,
4119
+ "--e": pause.endMs
4120
+ }
4121
+ }, `p-${pause.startMs}`)),
4122
+ ticks.map((tick) => /* @__PURE__ */ jsx("span", {
4123
+ className: "fw-gridline",
4124
+ style: { "--s": tick }
4125
+ }, tick)),
4126
+ props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
4127
+ className: "fw-marker-line",
4128
+ "data-tone": marker.tone ?? "neutral",
4129
+ style: { "--s": marker.at }
4130
+ }, `l-${marker.at}-${marker.label}`))
4131
+ ]
4088
4132
  }),
4089
4133
  /* @__PURE__ */ jsxs("div", {
4090
4134
  ref: scroller,
@@ -4129,16 +4173,9 @@ function FlowBody(props) {
4129
4173
  rowHeight,
4130
4174
  render: row
4131
4175
  }),
4132
- props.markers?.length || props.pauses?.length ? /* @__PURE__ */ jsxs("div", {
4176
+ props.markers?.length ? /* @__PURE__ */ jsx("div", {
4133
4177
  className: "fw-marker-layer",
4134
- children: [props.pauses?.map((pause) => /* @__PURE__ */ jsx("span", {
4135
- className: "fw-pause",
4136
- style: {
4137
- "--s": pause.startMs,
4138
- "--e": pause.endMs
4139
- },
4140
- title: `${pause.label}, ${duration(pause.endMs - pause.startMs)}`
4141
- }, `p-${pause.startMs}`)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
4178
+ children: props.markers.map((marker) => /* @__PURE__ */ jsx("span", {
4142
4179
  className: "fw-marker",
4143
4180
  "data-tone": marker.tone ?? "neutral",
4144
4181
  style: { "--s": marker.at },
@@ -4147,7 +4184,7 @@ function FlowBody(props) {
4147
4184
  className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
4148
4185
  children: marker.label
4149
4186
  })
4150
- }, `${marker.at}-${marker.label}`))]
4187
+ }, `${marker.at}-${marker.label}`))
4151
4188
  }) : null,
4152
4189
  showCursor && props.cursorLine !== false && /* @__PURE__ */ jsx("div", {
4153
4190
  className: "fw-cursor-layer",
@@ -4443,6 +4480,7 @@ function useHostable(value, onChange, initial) {
4443
4480
  report.current?.(resolved);
4444
4481
  }, [controlled])];
4445
4482
  }
4483
+ const CARD_WIDTH = 320;
4446
4484
  /** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
4447
4485
  function summaryCells(trace, at, durationMs) {
4448
4486
  const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
@@ -4620,7 +4658,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4620
4658
  const pipelines = rows.filter((row) => row.kind === "pipeline");
4621
4659
  return pipelines.length ? pipelines : rows.filter((row) => !row.parentId);
4622
4660
  }, [rows]);
4623
- const sharedDetail = agents.length > 1 && agents.every((row) => row.span.detail && row.span.detail === agents[0]?.span.detail) ? agents[0]?.span.detail : void 0;
4661
+ const details = new Set(agents.flatMap((row) => row.span.detail ? [row.span.detail] : []));
4662
+ const sharedDetail = agents.length > 1 && details.size === 1 ? [...details][0] : void 0;
4624
4663
  const [flippedFamilies, setFlippedFamilies] = useState(/* @__PURE__ */ new Set());
4625
4664
  const openFamilies = useMemo(() => defaultOpenFamilies(agents, UNFOLD_ROWS, flippedFamilies), [agents, flippedFamilies]);
4626
4665
  const [openSteps, setOpenSteps] = useState(/* @__PURE__ */ new Set());
@@ -4735,8 +4774,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4735
4774
  }, limit));
4736
4775
  const latest = useRef({
4737
4776
  toggle: (_row) => {},
4738
- pin: (_target, _element) => {},
4739
- inspect: (_target, _element) => {}
4777
+ pin: (_target, _element, _x) => {},
4778
+ inspect: (_target, _element, _x) => {}
4740
4779
  });
4741
4780
  const toggleNow = (row) => {
4742
4781
  const flip = (open) => new Set(open.has(row.id) ? [...open].filter((id) => id !== row.id) : [...open, row.id]);
@@ -4760,25 +4799,43 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4760
4799
  };
4761
4800
  latest.current.toggle = toggleNow;
4762
4801
  const toggle = useCallback((row) => latest.current.toggle(row), []);
4763
- const place = (target, element, pinned) => {
4802
+ const place = (target, element, pinned, x) => {
4764
4803
  const host = root.current?.getBoundingClientRect();
4765
4804
  if (!host) return;
4766
4805
  const rect = element.getBoundingClientRect();
4767
- const above = view === "agents" ? rect.top - host.top > 150 : globalThis.innerHeight - rect.bottom < 260 && rect.top - host.top > 260;
4768
- const left = Math.max(8, Math.min(rect.left - host.left, host.width - 328));
4806
+ const width = Math.min(CARD_WIDTH, host.width - 16);
4807
+ const left = Math.max(8, Math.min((x ?? rect.left + 24) - host.left - 24, host.width - width - 8));
4769
4808
  clearTimeout(hideTimer.current);
4770
4809
  setCard({
4771
4810
  target,
4772
4811
  pinned,
4773
4812
  left,
4774
- top: above ? rect.top - host.top - 8 : rect.bottom - host.top + 8,
4775
- above
4813
+ anchorTop: rect.top - host.top,
4814
+ anchorBottom: rect.bottom - host.top,
4815
+ above: view === "agents"
4776
4816
  });
4777
4817
  };
4778
- latest.current.inspect = (target, element) => {
4779
- if (!card?.pinned) place(target, element, false);
4818
+ useLayoutEffect(() => {
4819
+ if (!card || card.fitted || !root.current) return;
4820
+ const box = document.getElementById(cardId)?.getBoundingClientRect();
4821
+ if (!box) return;
4822
+ const hostTop = root.current.getBoundingClientRect().top;
4823
+ const room = {
4824
+ above: hostTop + card.anchorTop - 16,
4825
+ below: globalThis.innerHeight - (hostTop + card.anchorBottom) - 16
4826
+ };
4827
+ const preferred = card.above ? room.above : room.below;
4828
+ const other = card.above ? room.below : room.above;
4829
+ setCard({
4830
+ ...card,
4831
+ fitted: true,
4832
+ above: box.height <= preferred || box.height > other && preferred >= other ? card.above : !card.above
4833
+ });
4834
+ }, [card, cardId]);
4835
+ latest.current.inspect = (target, element, x) => {
4836
+ if (!card?.pinned) place(target, element, false, x);
4780
4837
  };
4781
- const inspect = useCallback((target, element) => latest.current.inspect(target, element), []);
4838
+ const inspect = useCallback((target, element, x) => latest.current.inspect(target, element, x), []);
4782
4839
  const leave = useCallback(() => {
4783
4840
  clearTimeout(hideTimer.current);
4784
4841
  hideTimer.current = setTimeout(() => setCard((previous) => previous?.pinned ? previous : null), 140);
@@ -4791,19 +4848,19 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4791
4848
  document.addEventListener("pointermove", move);
4792
4849
  return () => document.removeEventListener("pointermove", move);
4793
4850
  }, [card, leave]);
4794
- latest.current.pin = (target, element) => {
4851
+ latest.current.pin = (target, element, x) => {
4795
4852
  if (card?.pinned && card.target === target) {
4796
4853
  closeCard();
4797
4854
  return;
4798
4855
  }
4799
- place(target, element, true);
4856
+ place(target, element, true, x);
4800
4857
  const row = byId.get(target);
4801
4858
  if (row) {
4802
4859
  onSelect?.(row.span);
4803
4860
  zoomTo(row);
4804
4861
  }
4805
4862
  };
4806
- const pin = useCallback((target, element) => latest.current.pin(target, element), []);
4863
+ const pin = useCallback((target, element, x) => latest.current.pin(target, element, x), []);
4807
4864
  const selection = selectedId ?? (card?.pinned ? card.target : void 0);
4808
4865
  const closeCard = () => {
4809
4866
  const target = card?.target;
@@ -5056,7 +5113,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
5056
5113
  agent: cardAgent,
5057
5114
  clockMs,
5058
5115
  left: card.left,
5059
- top: card.top,
5116
+ top: card.above ? card.anchorTop - 8 : card.anchorBottom + 8,
5060
5117
  pinned: card.pinned,
5061
5118
  onClose: closeCard,
5062
5119
  renderIcon,