@tangle-network/charts 0.20.1 → 0.20.2
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/{flow-trace-DqCMrnTs.d.ts → flow-trace-DfSefpBM.d.ts} +5 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/flow-waterfall.css +21 -9
- package/dist/react/index.d.ts +4 -2
- package/dist/react/index.js +335 -43
- package/dist/react/live.css +1 -1
- package/package.json +1 -1
|
@@ -382,8 +382,9 @@ interface FlowTrace {
|
|
|
382
382
|
costUsd?: number;
|
|
383
383
|
/** `list`: priced from a model catalog's list prices, not a bill; the summary says so. */
|
|
384
384
|
costBasis?: 'billed' | 'list';
|
|
385
|
-
/**
|
|
386
|
-
|
|
385
|
+
/** `costUsd` is a bound, not a count: `lower` prints "≥" (a floor, such as the larger of usage and meter per agent),
|
|
386
|
+
* `upper` prints "≤" (list price on inputs that include cached tokens priced as fresh). */
|
|
387
|
+
costBound?: 'lower' | 'upper';
|
|
387
388
|
/** How many model calls `costUsd` covers; when `priced < total` the total is a lower bound and shown as one. */
|
|
388
389
|
costCoverage?: {
|
|
389
390
|
priced: number;
|
|
@@ -391,7 +392,8 @@ interface FlowTrace {
|
|
|
391
392
|
};
|
|
392
393
|
/** The real charge when the producer knows it (for example $0 on a subscription seat), shown beside a list price. */
|
|
393
394
|
billedUsd?: number;
|
|
394
|
-
|
|
395
|
+
/** Tool calls the producer counted; absent means unknown and is shown as a dash, never as zero. */
|
|
396
|
+
toolCalls?: number;
|
|
395
397
|
/** Authoritative LLM request count when supplied; absent means unknown. */
|
|
396
398
|
modelCalls?: number;
|
|
397
399
|
errors?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-
|
|
1
|
+
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-DfSefpBM.js";
|
|
2
2
|
//#region src/block.d.ts
|
|
3
3
|
interface RenderFigureOptions {
|
|
4
4
|
/** Chart uses a closed data disclosure; plot keeps only the SVG and accessible data. */
|
package/dist/index.js
CHANGED
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
/* Light fills carry enough of the hue to tell agents apart on white (24% and 42% both read as "pastel, washed out"); the outline is
|
|
66
66
|
the full hue, which passes 3:1 against the surface, so every mark keeps its edge. */
|
|
67
67
|
.fw-root [data-tone] {
|
|
68
|
-
--fill: light-dark(
|
|
68
|
+
--fill: light-dark(var(--tone), color-mix(in oklab, var(--tone) 94%, #000));
|
|
69
69
|
--line: light-dark(var(--tone), var(--tone));
|
|
70
|
-
--dim: light-dark(color-mix(in oklab, var(--tone)
|
|
70
|
+
--dim: light-dark(color-mix(in oklab, var(--tone) 62%, #fff), color-mix(in oklab, var(--tone) 40%, #17171d));
|
|
71
71
|
--dim-line: light-dark(color-mix(in oklab, var(--tone) 70%, #fff), color-mix(in oklab, var(--tone) 55%, #17171d));
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -138,6 +138,8 @@
|
|
|
138
138
|
/* The event track: one dot per marker on the axis; click or N/P moves the cursor there. */
|
|
139
139
|
.fw-root .fw-event { --fw-marker: var(--fw-muted); position: absolute; bottom: 2px; width: 8px; height: 8px; margin-left: -4px; padding: 0; border: 0; border-radius: 50%; background: var(--fw-marker); box-shadow: 0 0 0 2px var(--fw-surface); cursor: pointer; z-index: 4; left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); }
|
|
140
140
|
.fw-root .fw-event[data-tone="ok"] { --fw-marker: var(--fw-ok); }
|
|
141
|
+
.fw-root .fw-event[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--fw-surface), 0 0 0 4px var(--fw-marker); }
|
|
142
|
+
.fw-root .fw-event-label { position: absolute; bottom: 14px; z-index: 5; transform: translateX(-50%); left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); padding: 0 8px; border-radius: 4px; background: var(--fw-surface); box-shadow: inset 0 0 0 1px var(--fw-border); color: var(--fw-text); font-size: 12px; line-height: 20px; white-space: nowrap; pointer-events: none; }
|
|
141
143
|
.fw-root .fw-event[data-tone="failed"] { --fw-marker: var(--fw-failed); }
|
|
142
144
|
.fw-root .fw-event:hover, .fw-root .fw-event:focus-visible { transform: scale(1.4); outline: 0; }
|
|
143
145
|
.fw-root .fw-cursor { position: absolute; top: 0; bottom: 0; width: 1px; background: color-mix(in oklab, var(--fw-text) 70%, transparent); left: calc(var(--fw-pad) + (var(--fw-t) - var(--fw-v0)) * var(--fw-k) * 1px); }
|
|
@@ -231,7 +233,7 @@
|
|
|
231
233
|
.fw-root [data-outcome="neutral"], .fw-root [data-outcome="neutral"] [data-tone] { --tone: var(--fw-muted); }
|
|
232
234
|
.fw-root .fw-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 8px 0 0; padding: 0; list-style: none; color: var(--fw-muted); font-size: 12px; line-height: 16px; }
|
|
233
235
|
.fw-root .fw-legend li { display: inline-flex; align-items: center; gap: 8px; }
|
|
234
|
-
.fw-root .fw-kinds { margin: 0; padding:
|
|
236
|
+
.fw-root .fw-kinds { margin: 0; padding: 4px 24px 8px; justify-content: flex-start; color: var(--fw-text); }
|
|
235
237
|
.fw-root .fw-kinds .fw-swatch-model { width: 12px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--fw-text) 40%, var(--fw-surface)); }
|
|
236
238
|
.fw-root .fw-kinds .fw-swatch-tool { width: 12px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--fw-text) 16%, var(--fw-surface)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-text) 30%, transparent); }
|
|
237
239
|
.fw-root .fw-kinds .fw-swatch-wait { width: 12px; height: 8px; border-radius: 2px; background: repeating-linear-gradient(-45deg, var(--fw-hatch) 0 1px, transparent 1px 4px); box-shadow: inset 0 0 0 1px var(--fw-hatch); }
|
|
@@ -248,11 +250,13 @@
|
|
|
248
250
|
.fw-root .fw-seg-model { background: var(--fill); box-shadow: inset 0 0 0 1px var(--line); }
|
|
249
251
|
.fw-root .fw-seg-tool { background: var(--dim); box-shadow: inset 0 0 0 1px var(--dim-line); }
|
|
250
252
|
.fw-root .fw-seg-failed { background: color-mix(in oklab, var(--fw-failed) 18%, var(--fw-surface)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 70%, transparent); color: var(--fw-failed); }
|
|
251
|
-
|
|
253
|
+
/* In the agent view a failed turn draws like any turn; the agent's own failure shows once, by its name and its end label. */
|
|
254
|
+
.fw-root .fw-view-agents .fw-seg-failed { background: var(--dim); box-shadow: inset 0 0 0 1px var(--dim-line); }
|
|
255
|
+
.fw-root .fw-view-agents .fw-alert { color: var(--fw-muted); }
|
|
252
256
|
.fw-root .fw-glyph { display: inline-flex; color: currentColor; }
|
|
253
257
|
.fw-root .fw-glyph svg { width: 11px; height: 11px; stroke-width: 2; }
|
|
254
258
|
.fw-root .fw-seg-model .fw-glyph, .fw-root .fw-k-model .fw-glyph { color: light-dark(var(--tone), #fff); }
|
|
255
|
-
.fw-root .fw-end { display: inline-flex; align-items: center; gap: 8px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2); height: var(--fw-bar-h); left: calc(var(--x0) + max(var(--w), 3px) + 8px); white-space: nowrap; pointer-events: none; }
|
|
259
|
+
.fw-root .fw-end { z-index: 2; display: inline-flex; align-items: center; gap: 8px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2); height: var(--fw-bar-h); left: calc(var(--x0) + max(var(--w), 3px) + 8px); white-space: nowrap; pointer-events: none; }
|
|
256
260
|
.fw-root .fw-duration { font-size: 12px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
|
|
257
261
|
.fw-root .fw-k-failed ~ .fw-end .fw-duration { color: var(--fw-failed); }
|
|
258
262
|
.fw-root .fw-badge { height: 18px; padding: 0 8px; border-radius: 999px; font-size: 12px; line-height: 16px; font-weight: 600; color: var(--fw-amber); background: color-mix(in oklab, var(--fw-amber) 13%, transparent); border: 1px solid color-mix(in oklab, var(--fw-amber) 28%, transparent); }
|
|
@@ -265,17 +269,25 @@
|
|
|
265
269
|
opacity: clamp(0, (var(--fw-t) - var(--s)) * 1000, 1);
|
|
266
270
|
}
|
|
267
271
|
.fw-root .fw-view-spans .fw-link { opacity: calc(clamp(0, (var(--fw-t) - var(--s)) * 1000, 1) * .7); }
|
|
268
|
-
/* Agent-view connectors
|
|
269
|
-
|
|
272
|
+
/* Agent-view connectors rest faint, one curved elbow per spawn in the agent's hue (they show who started whom), and come to
|
|
273
|
+
full strength for the agent under the pointer, pinned or focused. */
|
|
274
|
+
.fw-root .fw-view-agents .fw-link:not(.fw-link-hot) { opacity: calc(clamp(0, (var(--fw-t) - var(--s)) * 1000, 1) * .6); }
|
|
270
275
|
.fw-root .fw-link { transition: opacity 160ms var(--fw-ease); }
|
|
271
|
-
.fw-root .fw-link-down { left: calc(var(--x) - 8px); border-left-width: 1.5px; border-bottom-left-radius:
|
|
272
|
-
.fw-root .fw-link-up { left: var(--x); border-right-width: 1.5px; border-bottom-right-radius:
|
|
276
|
+
.fw-root .fw-link-down { left: calc(var(--x) - 8px); border-left-width: 1.5px; border-bottom-left-radius: 12px; }
|
|
277
|
+
.fw-root .fw-link-up { left: var(--x); border-right-width: 1.5px; border-bottom-right-radius: 12px; }
|
|
273
278
|
.fw-root .fw-link::before { content: ""; position: absolute; top: -3.5px; width: 5px; height: 5px; border-radius: 50%; background: var(--line); box-shadow: 0 0 0 1.5px var(--fw-surface); }
|
|
274
279
|
.fw-root .fw-link-down::before { left: -3.25px; }
|
|
275
280
|
.fw-root .fw-link-up::before { right: -3.25px; }
|
|
276
281
|
|
|
277
282
|
/* Span card */
|
|
278
283
|
.fw-root .fw-card-anchor { display: contents; }
|
|
284
|
+
/* Agent view: the card docks under the lanes instead of floating over the plot (judges: "covers the data it describes"). */
|
|
285
|
+
/* Two classes on the root so these win over the base .fw-root .fw-card rule that follows. */
|
|
286
|
+
.fw-root.fw-root-agents .fw-card-anchor { display: block; padding: 0 24px 12px; }
|
|
287
|
+
.fw-root.fw-root-agents .fw-card, .fw-root.fw-root-agents .fw-card-above .fw-card { position: relative; left: auto !important; top: auto !important; transform: none; animation-name: fw-arrive; width: auto; max-width: 560px; }
|
|
288
|
+
.fw-root .fw-seg-alt { filter: brightness(.78); }
|
|
289
|
+
/* Agent view: the scrubber track shows how far the run has played, so at rest it reads full, not empty. */
|
|
290
|
+
.fw-root-agents .fw-slider { background: linear-gradient(90deg, color-mix(in oklab, var(--fw-accent) 45%, transparent) var(--fw-progress), var(--fw-well) var(--fw-progress)) 0 50% / 100% 4px no-repeat; }
|
|
279
291
|
.fw-root .fw-card { position: absolute; z-index: 20; width: 320px; padding: 12px; border-radius: 12px; background: var(--fw-raised); border: 1px solid var(--fw-border);
|
|
280
292
|
box-shadow: 0 12px 32px -8px rgb(0 0 0 / .28), 0 2px 6px rgb(0 0 0 / .08); animation: fw-arrive 140ms var(--fw-ease); }
|
|
281
293
|
/* A hover card never covers the next mark: only a pinned card (a dialog) takes the pointer. */
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-
|
|
1
|
+
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-DfSefpBM.js";
|
|
2
2
|
import { CSSProperties, ReactElement, ReactNode } from "react";
|
|
3
3
|
//#region src/react/sparkline.d.ts
|
|
4
4
|
type SparklineDirection = "rising" | "falling" | "flat";
|
|
@@ -384,9 +384,11 @@ interface FlowWaterfallProps {
|
|
|
384
384
|
selectedId?: string;
|
|
385
385
|
/** Escape clears the host's selection, after closing any open card. */
|
|
386
386
|
onDeselect?: () => void;
|
|
387
|
+
/** Show the stat strip (default); a page with its own stat row passes false. */
|
|
388
|
+
summary?: boolean;
|
|
387
389
|
}
|
|
388
390
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
389
391
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
390
|
-
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill, selectedId, onDeselect, description }: FlowWaterfallProps): import("react").JSX.Element;
|
|
392
|
+
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill, selectedId, onDeselect, description, summary: showSummary }: FlowWaterfallProps): import("react").JSX.Element;
|
|
391
393
|
//#endregion
|
|
392
394
|
export type { BandKind, ChartBand, ChartMark, DotMapLabel, DotMapProps, DotMapRect, Figure, FigureAtWidth, FigureViewProps, FlowWaterfallProps, LineSeries, MarkKind, MarkVerdict, MarkerShape, MeterSegment, PointFields, PointGlyph, Refusal, SparklineDirection, SparklineGeometry, SparklineGeometryOptions, SparklineLabelOptions, SparklinePoint, SparklineProps, SparklineValues, StackedBarBucket, StackedBarChartProps, StackedBarSegment, StackedBarSeries, TickOutcome, TimelineLane, TimelineOptions, TimelineSpan, TimelineTick, TimelineViewProps, WaterfallBarProps, WaterfallHeaderProps, WaterfallRowProps, WaterfallSpanGeometry, WaterfallTimeAxisProps, WaterfallWindow, XCategory, YScale, YTarget, YTick };
|
package/dist/react/index.js
CHANGED
|
@@ -2441,7 +2441,17 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
2441
2441
|
if (childCost === void 0) {
|
|
2442
2442
|
if (child.kind === "model" || child.hasChildren) costKnown = false;
|
|
2443
2443
|
} else cost += childCost;
|
|
2444
|
-
if (child.kind !== "pipeline" && child.kind !== "waiting" && ordered(child.span))
|
|
2444
|
+
if (child.kind !== "pipeline" && child.kind !== "waiting" && ordered(child.span)) {
|
|
2445
|
+
row.own.push(segmentOf(child));
|
|
2446
|
+
const counts = row.counts ??= {
|
|
2447
|
+
model: 0,
|
|
2448
|
+
tool: 0,
|
|
2449
|
+
failed: 0
|
|
2450
|
+
};
|
|
2451
|
+
if (!child.ok) counts.failed++;
|
|
2452
|
+
else if (child.kind === "model") counts.model++;
|
|
2453
|
+
else counts.tool++;
|
|
2454
|
+
}
|
|
2445
2455
|
}
|
|
2446
2456
|
if (row.hasChildren && costKnown) row.rollupCostUsd = cost;
|
|
2447
2457
|
if (row.hasChildren) {
|
|
@@ -2560,7 +2570,7 @@ function totalsAt(trace, clockMs) {
|
|
|
2560
2570
|
costUsd: trace.costUsd === void 0 ? void 0 : costs ? cost : trace.costUsd,
|
|
2561
2571
|
tokens: counted ? tokens : trace.promptTokens + trace.completionTokens,
|
|
2562
2572
|
modelCalls: trace.modelCalls === void 0 ? void 0 : models,
|
|
2563
|
-
toolCalls: tools,
|
|
2573
|
+
toolCalls: trace.toolCalls === void 0 ? void 0 : tools,
|
|
2564
2574
|
errors
|
|
2565
2575
|
};
|
|
2566
2576
|
}
|
|
@@ -2579,18 +2589,197 @@ function costSummary(costUsd, trace) {
|
|
|
2579
2589
|
};
|
|
2580
2590
|
const coverage = trace.costCoverage;
|
|
2581
2591
|
const partial = coverage !== void 0 && coverage.priced < coverage.total;
|
|
2582
|
-
const upper = !partial && trace.
|
|
2592
|
+
const upper = !partial && trace.costBound === "upper";
|
|
2583
2593
|
return {
|
|
2584
|
-
value: `${partial ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
|
|
2594
|
+
value: `${partial || trace.costBound === "lower" ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
|
|
2585
2595
|
title: [
|
|
2586
2596
|
partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
|
|
2587
2597
|
list ? "at the model catalog list price; not a bill" : "",
|
|
2588
2598
|
upper ? "cached input is not split out, so it is priced as fresh input: at most this" : "",
|
|
2599
|
+
trace.costBound === "lower" ? "a floor: the producer reports at least this" : "",
|
|
2589
2600
|
trace.billedUsd !== void 0 ? `billed ${formatCost(trace.billedUsd)} (the seat charge)` : ""
|
|
2590
2601
|
].filter(Boolean).join("; "),
|
|
2591
2602
|
label
|
|
2592
2603
|
};
|
|
2593
2604
|
}
|
|
2605
|
+
/** The role an agent plays: its name without a trailing counter ("memscale-engineer-3", "Subagent 2"). */
|
|
2606
|
+
const roleOf = (name) => name.replace(/[-\s]\d+$/, "");
|
|
2607
|
+
/**
|
|
2608
|
+
* Agents sharing a role fold into one family row until opened: a 17-agent run reads as its few roles, and the bar of a
|
|
2609
|
+
* family carries every member's work in each member's hue. Members follow their opened family, marked with `family`.
|
|
2610
|
+
*/
|
|
2611
|
+
function agentFamilies(agents, open) {
|
|
2612
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2613
|
+
for (const row of agents) {
|
|
2614
|
+
const key = row.parentId === void 0 ? `root:${row.id}` : roleOf(row.name);
|
|
2615
|
+
groups.set(key, [...groups.get(key) ?? [], row]);
|
|
2616
|
+
}
|
|
2617
|
+
const out = [];
|
|
2618
|
+
let next = 0;
|
|
2619
|
+
const hue = (row) => row.tone <= 0 ? row.tone : ++next;
|
|
2620
|
+
for (const [role, members] of groups) {
|
|
2621
|
+
if (members.length < 2) {
|
|
2622
|
+
out.push(...members.map((row) => ({
|
|
2623
|
+
...row,
|
|
2624
|
+
tone: hue(row)
|
|
2625
|
+
})));
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
const tone = hue(members[0]);
|
|
2629
|
+
const id = `family:${role}`;
|
|
2630
|
+
const startMs = Math.min(...members.map((row) => row.span.startMs));
|
|
2631
|
+
const endMs = Math.max(...members.map((row) => row.span.endMs));
|
|
2632
|
+
const running = members.some((row) => spanRunning(row.span));
|
|
2633
|
+
const known = members.map((row) => row.span.costUsd ?? row.rollupCostUsd).filter((cost) => cost !== void 0);
|
|
2634
|
+
const failed = members.filter((row) => !row.ok).length;
|
|
2635
|
+
out.push({
|
|
2636
|
+
id,
|
|
2637
|
+
parentId: members[0].parentId,
|
|
2638
|
+
name: `${role} ×${members.length}`,
|
|
2639
|
+
kind: "pipeline",
|
|
2640
|
+
durationLabel: formatSpanDuration(endMs - startMs),
|
|
2641
|
+
approx: members.some((row) => row.approx),
|
|
2642
|
+
ok: failed === 0,
|
|
2643
|
+
depth: 0,
|
|
2644
|
+
hasChildren: true,
|
|
2645
|
+
tone,
|
|
2646
|
+
descendants: members.length,
|
|
2647
|
+
segments: [],
|
|
2648
|
+
retry: false,
|
|
2649
|
+
hasAgents: false,
|
|
2650
|
+
own: members.flatMap((row, i) => row.own.map((mark) => ({
|
|
2651
|
+
...mark,
|
|
2652
|
+
tone,
|
|
2653
|
+
alt: i % 2 === 1
|
|
2654
|
+
}))).sort((a, b) => a.startMs - b.startMs),
|
|
2655
|
+
...known.length ? { rollupCostUsd: known.reduce((sum, cost) => sum + cost, 0) } : {},
|
|
2656
|
+
span: {
|
|
2657
|
+
id,
|
|
2658
|
+
kind: "pipeline",
|
|
2659
|
+
name: role,
|
|
2660
|
+
startMs,
|
|
2661
|
+
endMs,
|
|
2662
|
+
status: running ? "running" : failed ? "failed" : "done",
|
|
2663
|
+
detail: members[0].span.detail,
|
|
2664
|
+
meta: {
|
|
2665
|
+
failedAgents: failed,
|
|
2666
|
+
costPartial: known.length < members.length
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
});
|
|
2670
|
+
if (open.has(id)) out.push(...members.map((row, i) => ({
|
|
2671
|
+
...row,
|
|
2672
|
+
family: id,
|
|
2673
|
+
inset: 1,
|
|
2674
|
+
tone,
|
|
2675
|
+
own: row.own.map((mark) => ({
|
|
2676
|
+
...mark,
|
|
2677
|
+
tone,
|
|
2678
|
+
alt: i % 2 === 1
|
|
2679
|
+
}))
|
|
2680
|
+
})));
|
|
2681
|
+
}
|
|
2682
|
+
return out;
|
|
2683
|
+
}
|
|
2684
|
+
/** 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"). */
|
|
2685
|
+
function agentSummary(row) {
|
|
2686
|
+
if (row.id.startsWith("family:")) {
|
|
2687
|
+
const failed = Number(row.span.meta?.failedAgents ?? 0);
|
|
2688
|
+
return `${row.descendants} agents${failed ? ` · ${failed} failed` : ""}`;
|
|
2689
|
+
}
|
|
2690
|
+
if (row.id.startsWith("run:")) {
|
|
2691
|
+
const failed = row.own.filter((mark) => mark.kind === "failed").length;
|
|
2692
|
+
return failed ? `${failed} failed` : "all done";
|
|
2693
|
+
}
|
|
2694
|
+
const counts = row.counts ?? {
|
|
2695
|
+
model: 0,
|
|
2696
|
+
tool: 0,
|
|
2697
|
+
failed: 0
|
|
2698
|
+
};
|
|
2699
|
+
const turns = counts.model + counts.failed;
|
|
2700
|
+
const tools = counts.tool;
|
|
2701
|
+
const state = row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : row.ok ? "done" : "failed");
|
|
2702
|
+
const unit = row.kind === "pipeline" ? ["turn", "turns"] : ["model call", "model calls"];
|
|
2703
|
+
return [
|
|
2704
|
+
state,
|
|
2705
|
+
turns ? `${turns} ${turns === 1 ? unit[0] : unit[1]}` : "",
|
|
2706
|
+
tools ? `${tools} tool ${tools === 1 ? "call" : "calls"}` : ""
|
|
2707
|
+
].filter(Boolean).join(" · ");
|
|
2708
|
+
}
|
|
2709
|
+
/**
|
|
2710
|
+
* The agent view's rows: families folded until opened, and an opened agent's own steps (its turns and tool calls, not its
|
|
2711
|
+
* child agents) nested beneath it with their icons, durations and costs, as a reader drills from the run into one agent.
|
|
2712
|
+
*/
|
|
2713
|
+
function agentViewRows(agents, rows, openFamilies, openSteps) {
|
|
2714
|
+
const base = agentFamilies(agents, openFamilies);
|
|
2715
|
+
if (openSteps.size === 0) return base;
|
|
2716
|
+
const steps = /* @__PURE__ */ new Map();
|
|
2717
|
+
for (const row of rows) {
|
|
2718
|
+
if (row.parentId === void 0 || !openSteps.has(row.parentId) || row.kind === "pipeline") continue;
|
|
2719
|
+
steps.set(row.parentId, [...steps.get(row.parentId) ?? [], row]);
|
|
2720
|
+
}
|
|
2721
|
+
return base.flatMap((row) => openSteps.has(row.id) ? [row, ...stepRuns(steps.get(row.id) ?? []).map((step) => ({
|
|
2722
|
+
...step,
|
|
2723
|
+
stepOf: row.id,
|
|
2724
|
+
inset: (row.inset ?? 0) + 1,
|
|
2725
|
+
tone: row.tone,
|
|
2726
|
+
own: step.own.map((mark) => ({
|
|
2727
|
+
...mark,
|
|
2728
|
+
tone: row.tone
|
|
2729
|
+
}))
|
|
2730
|
+
}))] : [row]);
|
|
2731
|
+
}
|
|
2732
|
+
/** Three or more consecutive steps of one kind and name read as one row ("15 model calls · 4h"), each a mark in its bar. */
|
|
2733
|
+
function stepRuns(steps) {
|
|
2734
|
+
const out = [];
|
|
2735
|
+
for (let i = 0; i < steps.length;) {
|
|
2736
|
+
let j = i;
|
|
2737
|
+
while (j < steps.length && steps[j].kind === steps[i].kind && steps[j].name === steps[i].name) j++;
|
|
2738
|
+
const run = steps.slice(i, j);
|
|
2739
|
+
if (run.length < 3) {
|
|
2740
|
+
out.push(...run);
|
|
2741
|
+
i = j;
|
|
2742
|
+
continue;
|
|
2743
|
+
}
|
|
2744
|
+
const first = run[0];
|
|
2745
|
+
const startMs = Math.min(...run.map((step) => step.span.startMs));
|
|
2746
|
+
const endMs = Math.max(...run.map((step) => step.span.endMs));
|
|
2747
|
+
const failed = run.filter((step) => !step.ok).length;
|
|
2748
|
+
const costs = run.map((step) => step.span.costUsd ?? step.rollupCostUsd).filter((cost) => cost !== void 0);
|
|
2749
|
+
const noun = first.kind === "model" ? "model calls" : first.kind === "tool" ? `${first.name} calls` : `${first.name} steps`;
|
|
2750
|
+
out.push({
|
|
2751
|
+
...first,
|
|
2752
|
+
id: `run:${first.id}`,
|
|
2753
|
+
name: `${run.length} ${noun}`,
|
|
2754
|
+
durationLabel: formatSpanDuration(endMs - startMs),
|
|
2755
|
+
ok: failed === 0,
|
|
2756
|
+
hasChildren: false,
|
|
2757
|
+
descendants: 0,
|
|
2758
|
+
segments: [],
|
|
2759
|
+
own: run.map((step) => ({
|
|
2760
|
+
startMs: step.span.startMs,
|
|
2761
|
+
endMs: step.span.endMs,
|
|
2762
|
+
kind: step.ok ? step.kind === "model" ? "model" : "tool" : "failed",
|
|
2763
|
+
tone: first.tone,
|
|
2764
|
+
id: step.id,
|
|
2765
|
+
count: 1,
|
|
2766
|
+
retry: step.retry
|
|
2767
|
+
})),
|
|
2768
|
+
...costs.length ? { rollupCostUsd: costs.reduce((sum, cost) => sum + cost, 0) } : {},
|
|
2769
|
+
span: {
|
|
2770
|
+
...first.span,
|
|
2771
|
+
id: `run:${first.id}`,
|
|
2772
|
+
name: `${run.length} ${noun}`,
|
|
2773
|
+
startMs,
|
|
2774
|
+
endMs,
|
|
2775
|
+
status: failed ? "failed" : first.span.status,
|
|
2776
|
+
costUsd: void 0
|
|
2777
|
+
}
|
|
2778
|
+
});
|
|
2779
|
+
i = j;
|
|
2780
|
+
}
|
|
2781
|
+
return out;
|
|
2782
|
+
}
|
|
2594
2783
|
//#endregion
|
|
2595
2784
|
//#region src/react/flow-waterfall-icons.tsx
|
|
2596
2785
|
const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
|
|
@@ -2608,7 +2797,52 @@ const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
|
|
|
2608
2797
|
children
|
|
2609
2798
|
});
|
|
2610
2799
|
/** Kind glyphs drawn when the host supplies no `renderIcon`. Brand marks belong to the host. */
|
|
2611
|
-
|
|
2800
|
+
/** Generic tool identity by name: a terminal, a file, a pencil, a lens, a globe or a branch, never a vendor mark. */
|
|
2801
|
+
function toolGlyph(name) {
|
|
2802
|
+
const n = (name ?? "").toLowerCase();
|
|
2803
|
+
if (/bash|shell|exec|terminal|command|run/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("rect", {
|
|
2804
|
+
x: "2",
|
|
2805
|
+
y: "3",
|
|
2806
|
+
width: "12",
|
|
2807
|
+
height: "10",
|
|
2808
|
+
rx: "2"
|
|
2809
|
+
}), /* @__PURE__ */ jsx("path", { d: "m5 7 2 1.5L5 10M8.5 10.5h2.5" })] }));
|
|
2810
|
+
if (/edit|write|patch|apply/.test(n)) return svg(/* @__PURE__ */ jsx("path", { d: "M3 13l1-3.5L10.5 3a1.4 1.4 0 0 1 2 2L6 11.5 3 13ZM9.5 4l2 2" }));
|
|
2811
|
+
if (/grep|search|find|query/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("circle", {
|
|
2812
|
+
cx: "7",
|
|
2813
|
+
cy: "7",
|
|
2814
|
+
r: "4.25"
|
|
2815
|
+
}), /* @__PURE__ */ jsx("path", { d: "m10.25 10.25 3.25 3.25" })] }));
|
|
2816
|
+
if (/read|glob|list|ls|cat|file|view/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M4 2.5h5l3 3v8H4z" }), /* @__PURE__ */ jsx("path", { d: "M9 2.5v3h3M6 9h4M6 11h3" })] }));
|
|
2817
|
+
if (/web|fetch|http|browser|url/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("circle", {
|
|
2818
|
+
cx: "8",
|
|
2819
|
+
cy: "8",
|
|
2820
|
+
r: "5.5"
|
|
2821
|
+
}), /* @__PURE__ */ jsx("path", { d: "M2.5 8h11M8 2.5c2 2 2 9 0 11M8 2.5c-2 2-2 9 0 11" })] }));
|
|
2822
|
+
if (/git|commit|branch|merge/.test(n)) return svg(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2823
|
+
/* @__PURE__ */ jsx("circle", {
|
|
2824
|
+
cx: "5",
|
|
2825
|
+
cy: "4",
|
|
2826
|
+
r: "1.5"
|
|
2827
|
+
}),
|
|
2828
|
+
/* @__PURE__ */ jsx("circle", {
|
|
2829
|
+
cx: "5",
|
|
2830
|
+
cy: "12",
|
|
2831
|
+
r: "1.5"
|
|
2832
|
+
}),
|
|
2833
|
+
/* @__PURE__ */ jsx("circle", {
|
|
2834
|
+
cx: "11",
|
|
2835
|
+
cy: "6",
|
|
2836
|
+
r: "1.5"
|
|
2837
|
+
}),
|
|
2838
|
+
/* @__PURE__ */ jsx("path", { d: "M5 5.5v5M11 7.5c0 2-3 2-6 3" })
|
|
2839
|
+
] }));
|
|
2840
|
+
}
|
|
2841
|
+
function KindGlyph({ kind, name }) {
|
|
2842
|
+
if (kind === "tool") {
|
|
2843
|
+
const glyph = toolGlyph(name);
|
|
2844
|
+
if (glyph) return glyph;
|
|
2845
|
+
}
|
|
2612
2846
|
switch (kind) {
|
|
2613
2847
|
case "pipeline": return svg(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2614
2848
|
/* @__PURE__ */ jsx("circle", {
|
|
@@ -2732,7 +2966,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2732
2966
|
if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
|
|
2733
2967
|
cells.splice(6);
|
|
2734
2968
|
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;
|
|
2969
|
+
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;
|
|
2736
2970
|
const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
|
|
2737
2971
|
return /* @__PURE__ */ jsxs("div", {
|
|
2738
2972
|
id,
|
|
@@ -2842,7 +3076,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2842
3076
|
/* @__PURE__ */ jsx("span", {
|
|
2843
3077
|
className: "fw-icon-tile",
|
|
2844
3078
|
"aria-hidden": true,
|
|
2845
|
-
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3079
|
+
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3080
|
+
kind: span.kind,
|
|
3081
|
+
name: span.name
|
|
3082
|
+
})
|
|
2846
3083
|
}),
|
|
2847
3084
|
/* @__PURE__ */ jsxs("div", {
|
|
2848
3085
|
className: "fw-card-title",
|
|
@@ -2905,10 +3142,10 @@ const at = (startMs, endMs) => ({
|
|
|
2905
3142
|
"--s": startMs,
|
|
2906
3143
|
"--e": Number.isFinite(endMs) ? endMs : OPEN_END
|
|
2907
3144
|
});
|
|
2908
|
-
function Segment({ mark, events, agentView }) {
|
|
2909
|
-
const glyph = mark.kind === "failed" ? agentView ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
|
|
3145
|
+
function Segment({ mark, events, agentView, ending }) {
|
|
3146
|
+
const glyph = mark.kind === "failed" ? agentView && !ending ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
|
|
2910
3147
|
return /* @__PURE__ */ jsx("span", {
|
|
2911
|
-
className: `fw-seg fw-seg-${mark.kind}`,
|
|
3148
|
+
className: `fw-seg fw-seg-${mark.kind}${mark.alt ? " fw-seg-alt" : ""}`,
|
|
2912
3149
|
"data-tone": toneKey(mark.tone),
|
|
2913
3150
|
style: at(mark.startMs, mark.endMs),
|
|
2914
3151
|
onPointerEnter: agentView ? (event) => events.onInspect(mark.id, event.currentTarget) : void 0,
|
|
@@ -2931,7 +3168,7 @@ function endLabel(row, clockMs) {
|
|
|
2931
3168
|
if (!spanRunning(span) && clockMs < span.endMs) return `${formatSpanDuration(Math.max(0, clockMs - span.startMs))}${row.approx ? "~" : ""}`;
|
|
2932
3169
|
const duration = spanRunning(span) ? `${formatSpanDuration(Math.max(span.endMs, clockMs) - span.startMs)}${row.approx ? "~" : ""}` : row.durationLabel;
|
|
2933
3170
|
const cost = span.costUsd ?? (row.rollupCostUsd ? row.rollupCostUsd : void 0);
|
|
2934
|
-
return cost === void 0 ? duration : `${duration} · ${formatCost(cost)}`;
|
|
3171
|
+
return cost === void 0 ? duration : `${duration} · ${span.meta?.costPartial ? "≥" : ""}${formatCost(cost)}`;
|
|
2935
3172
|
}
|
|
2936
3173
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
2937
3174
|
const FlowRow = memo(function FlowRow(props) {
|
|
@@ -2943,9 +3180,13 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2943
3180
|
const invalid = row.durationLabel === "—";
|
|
2944
3181
|
const isAgent = row.kind === "pipeline";
|
|
2945
3182
|
const kind = !row.ok ? "failed" : row.kind;
|
|
2946
|
-
const marks = isAgent || row.hasChildren ? folded && !agentView ? row.segments : row.own : [];
|
|
3183
|
+
const marks = isAgent || row.hasChildren || row.id.startsWith("run:") ? folded && !agentView ? row.segments : row.own : [];
|
|
2947
3184
|
const label = endLabel(row, clockMs);
|
|
2948
3185
|
const inspect = (event) => props.onInspect(row.id, event.currentTarget);
|
|
3186
|
+
const family = agentView && row.id.startsWith("family:");
|
|
3187
|
+
const step = agentView && row.stepOf !== void 0;
|
|
3188
|
+
const opens = family || agentView && isAgent && row.own.length > 0;
|
|
3189
|
+
const ending = agentView && !row.ok ? marks.map((mark) => mark.kind).lastIndexOf("failed") : -1;
|
|
2949
3190
|
return /* @__PURE__ */ jsxs("div", {
|
|
2950
3191
|
role: "row",
|
|
2951
3192
|
"data-span-id": row.id,
|
|
@@ -2958,7 +3199,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2958
3199
|
tabIndex: focusable ? 0 : -1,
|
|
2959
3200
|
"data-tone": toneKey(row.tone),
|
|
2960
3201
|
"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" : ""}`,
|
|
3202
|
+
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" : ""}`,
|
|
2962
3203
|
style: {
|
|
2963
3204
|
"--row-y": `${top}px`,
|
|
2964
3205
|
"--fw-enter-delay": `${motion?.enter ?? 0}ms`
|
|
@@ -2968,14 +3209,30 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2968
3209
|
},
|
|
2969
3210
|
onClick: (event) => {
|
|
2970
3211
|
props.onActivate(row);
|
|
3212
|
+
if (family) {
|
|
3213
|
+
props.onToggle(row);
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
2971
3216
|
const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
|
|
2972
3217
|
if (bar) props.onPin(row.id, bar);
|
|
2973
3218
|
},
|
|
2974
3219
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2975
3220
|
role: "gridcell",
|
|
2976
3221
|
className: "fw-label",
|
|
2977
|
-
style: { "--depth": agentView ? 0 : row.depth },
|
|
3222
|
+
style: { "--depth": agentView ? row.inset ?? 0 : row.depth },
|
|
2978
3223
|
children: [
|
|
3224
|
+
opens && /* @__PURE__ */ jsx("button", {
|
|
3225
|
+
type: "button",
|
|
3226
|
+
tabIndex: -1,
|
|
3227
|
+
className: `fw-toggle${props.expanded ? " fw-open" : ""}`,
|
|
3228
|
+
"aria-label": `${props.expanded ? "Collapse" : "Expand"} ${row.name}`,
|
|
3229
|
+
"aria-expanded": Boolean(props.expanded),
|
|
3230
|
+
onClick: (event) => {
|
|
3231
|
+
event.stopPropagation();
|
|
3232
|
+
props.onToggle(row);
|
|
3233
|
+
},
|
|
3234
|
+
children: /* @__PURE__ */ jsx(ChevronGlyph, {})
|
|
3235
|
+
}),
|
|
2979
3236
|
!agentView && /* @__PURE__ */ jsx("span", {
|
|
2980
3237
|
className: "fw-guides",
|
|
2981
3238
|
"aria-hidden": true
|
|
@@ -2996,10 +3253,13 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2996
3253
|
className: "fw-toggle-space",
|
|
2997
3254
|
"aria-hidden": true
|
|
2998
3255
|
})),
|
|
2999
|
-
!agentView && /* @__PURE__ */ jsx("span", {
|
|
3256
|
+
(!agentView || step) && /* @__PURE__ */ jsx("span", {
|
|
3000
3257
|
className: "fw-icon-tile",
|
|
3001
3258
|
"aria-hidden": true,
|
|
3002
|
-
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3259
|
+
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3260
|
+
kind: span.kind,
|
|
3261
|
+
name: span.name
|
|
3262
|
+
})
|
|
3003
3263
|
}),
|
|
3004
3264
|
/* @__PURE__ */ jsxs("span", {
|
|
3005
3265
|
className: "fw-names",
|
|
@@ -3014,7 +3274,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3014
3274
|
"data-outcome": span.outcome.tone,
|
|
3015
3275
|
title: span.outcome.label,
|
|
3016
3276
|
"aria-hidden": true
|
|
3017
|
-
}) : agentView && /* @__PURE__ */ jsx("span", {
|
|
3277
|
+
}) : agentView && !step && /* @__PURE__ */ jsx("span", {
|
|
3018
3278
|
className: "fw-agent-dot",
|
|
3019
3279
|
"aria-hidden": true
|
|
3020
3280
|
}),
|
|
@@ -3029,9 +3289,9 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3029
3289
|
})
|
|
3030
3290
|
]
|
|
3031
3291
|
}),
|
|
3032
|
-
agentView &&
|
|
3292
|
+
agentView && /* @__PURE__ */ jsx("span", {
|
|
3033
3293
|
className: "fw-subtitle",
|
|
3034
|
-
children: span.detail
|
|
3294
|
+
children: span.detail && span.detail !== props.sharedDetail ? `${span.detail} · ${agentSummary(row)}` : agentSummary(row)
|
|
3035
3295
|
}),
|
|
3036
3296
|
agentView && renderIcon && /* @__PURE__ */ jsx("span", {
|
|
3037
3297
|
className: "fw-detail-icon",
|
|
@@ -3075,7 +3335,8 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3075
3335
|
onPointerLeave: props.onLeave,
|
|
3076
3336
|
onClick: (event) => {
|
|
3077
3337
|
event.stopPropagation();
|
|
3078
|
-
props.
|
|
3338
|
+
if (family) props.onToggle(row);
|
|
3339
|
+
else props.onPin(row.id, event.currentTarget);
|
|
3079
3340
|
},
|
|
3080
3341
|
children: [!isAgent && !row.ok && /* @__PURE__ */ jsx("span", {
|
|
3081
3342
|
className: "fw-glyph",
|
|
@@ -3088,7 +3349,8 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3088
3349
|
marks.map((mark, i) => /* @__PURE__ */ jsx(Segment, {
|
|
3089
3350
|
mark,
|
|
3090
3351
|
events: props,
|
|
3091
|
-
agentView
|
|
3352
|
+
agentView,
|
|
3353
|
+
ending: i === ending
|
|
3092
3354
|
}, `${mark.id}-${i}`)),
|
|
3093
3355
|
/* @__PURE__ */ jsxs("span", {
|
|
3094
3356
|
className: "fw-end",
|
|
@@ -3111,7 +3373,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3111
3373
|
] })
|
|
3112
3374
|
})]
|
|
3113
3375
|
});
|
|
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)));
|
|
3376
|
+
}, (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.sharedDetail === b.sharedDetail && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
|
|
3115
3377
|
/** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
|
|
3116
3378
|
function LinkMark({ link, rowHeight, hot }) {
|
|
3117
3379
|
const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
|
|
@@ -3315,7 +3577,7 @@ function FlowBody(props) {
|
|
|
3315
3577
|
const [viewport, setViewport] = useState(maxHeight);
|
|
3316
3578
|
const latest = useRef(props);
|
|
3317
3579
|
latest.current = props;
|
|
3318
|
-
const height = Math.min(maxHeight, Math.max(rows.length, 1) * rowHeight);
|
|
3580
|
+
const height = props.fixedHeight ? maxHeight : Math.min(maxHeight, Math.max(rows.length, 1) * rowHeight);
|
|
3319
3581
|
const links = useMemo(() => linksFor(rows, props.byId, view), [
|
|
3320
3582
|
rows,
|
|
3321
3583
|
props.byId,
|
|
@@ -3323,6 +3585,7 @@ function FlowBody(props) {
|
|
|
3323
3585
|
]);
|
|
3324
3586
|
const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
|
|
3325
3587
|
const motion = useRowMotion(rows);
|
|
3588
|
+
const [jumped, setJumped] = useState();
|
|
3326
3589
|
const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
|
|
3327
3590
|
const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
|
|
3328
3591
|
const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
|
|
@@ -3504,9 +3767,19 @@ function FlowBody(props) {
|
|
|
3504
3767
|
style: { "--s": marker.at },
|
|
3505
3768
|
title: marker.kind ? `${marker.kind}: ${marker.label}` : marker.label,
|
|
3506
3769
|
"aria-label": `Jump to ${marker.kind ? `${marker.kind} ` : ""}${marker.label}`,
|
|
3770
|
+
"aria-pressed": jumped === marker,
|
|
3507
3771
|
onPointerDown: (event) => event.stopPropagation(),
|
|
3508
|
-
onClick: () =>
|
|
3772
|
+
onClick: () => {
|
|
3773
|
+
setJumped(marker);
|
|
3774
|
+
props.onScrub(marker.at);
|
|
3775
|
+
}
|
|
3509
3776
|
}, `e-${marker.at}-${marker.label}`)),
|
|
3777
|
+
jumped && /* @__PURE__ */ jsx("span", {
|
|
3778
|
+
className: "fw-event-label",
|
|
3779
|
+
"data-tone": jumped.tone ?? "neutral",
|
|
3780
|
+
style: { "--s": jumped.at },
|
|
3781
|
+
children: jumped.label
|
|
3782
|
+
}),
|
|
3510
3783
|
showCursor && /* @__PURE__ */ jsx("span", {
|
|
3511
3784
|
className: "fw-cursor-handle",
|
|
3512
3785
|
"aria-hidden": true
|
|
@@ -3530,6 +3803,8 @@ function FlowBody(props) {
|
|
|
3530
3803
|
clockMs,
|
|
3531
3804
|
renderIcon: props.renderIcon,
|
|
3532
3805
|
fillByAgent: props.fillByAgent,
|
|
3806
|
+
sharedDetail: props.sharedDetail,
|
|
3807
|
+
expanded: props.expandedIds?.has(item.id),
|
|
3533
3808
|
onToggle: props.onToggle,
|
|
3534
3809
|
onInspect: props.onInspect,
|
|
3535
3810
|
onLeave: props.onLeave,
|
|
@@ -3617,7 +3892,7 @@ function FlowBody(props) {
|
|
|
3617
3892
|
})
|
|
3618
3893
|
}, `${marker.at}-${marker.label}`))]
|
|
3619
3894
|
}) : null,
|
|
3620
|
-
showCursor && /* @__PURE__ */ jsx("div", {
|
|
3895
|
+
showCursor && props.cursorLine !== false && /* @__PURE__ */ jsx("div", {
|
|
3621
3896
|
className: "fw-cursor-layer",
|
|
3622
3897
|
"aria-hidden": true,
|
|
3623
3898
|
children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
|
|
@@ -3905,11 +4180,11 @@ function summaryCells(trace, at, durationMs) {
|
|
|
3905
4180
|
errors,
|
|
3906
4181
|
summary: [
|
|
3907
4182
|
["Duration", formatSpanDuration(durationMs)],
|
|
3908
|
-
[
|
|
4183
|
+
...cost.value === "—" ? [] : [[
|
|
3909
4184
|
cost.label,
|
|
3910
4185
|
cost.value,
|
|
3911
4186
|
cost.title
|
|
3912
|
-
],
|
|
4187
|
+
]],
|
|
3913
4188
|
[
|
|
3914
4189
|
"Tokens",
|
|
3915
4190
|
formatCount(tokens),
|
|
@@ -3920,7 +4195,11 @@ function summaryCells(trace, at, durationMs) {
|
|
|
3920
4195
|
modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
|
|
3921
4196
|
modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
|
|
3922
4197
|
],
|
|
3923
|
-
[
|
|
4198
|
+
[
|
|
4199
|
+
"Tool calls",
|
|
4200
|
+
(at ? at.toolCalls : trace.toolCalls)?.toLocaleString() ?? "—",
|
|
4201
|
+
(at ? at.toolCalls : trace.toolCalls) === void 0 ? "The producer did not count tool calls" : void 0
|
|
4202
|
+
],
|
|
3924
4203
|
["Errors", errors.toLocaleString()]
|
|
3925
4204
|
]
|
|
3926
4205
|
};
|
|
@@ -3939,8 +4218,7 @@ function KindLegend({ rows }) {
|
|
|
3939
4218
|
["model", "Model call"],
|
|
3940
4219
|
["tool", "Tool call"],
|
|
3941
4220
|
["waiting", "Waiting"],
|
|
3942
|
-
["handoff", "Handoff"]
|
|
3943
|
-
["failed", "Failed turn"]
|
|
4221
|
+
["handoff", "Handoff"]
|
|
3944
4222
|
].filter(([kind]) => kind === "waiting" ? rows.some((row) => row.kind === "pipeline") : kind === "handoff" ? rows.some((row) => row.span.handoffTo || row.hasAgents) : has(kind));
|
|
3945
4223
|
if (shown.length === 0) return null;
|
|
3946
4224
|
return /* @__PURE__ */ jsx("ul", {
|
|
@@ -3992,7 +4270,7 @@ function OutcomeLegend({ agents }) {
|
|
|
3992
4270
|
}
|
|
3993
4271
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
3994
4272
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
3995
|
-
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect, description }) {
|
|
4273
|
+
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect, description, summary: showSummary = true }) {
|
|
3996
4274
|
const root = useRef(null);
|
|
3997
4275
|
const search = useRef(null);
|
|
3998
4276
|
const slider = useRef(null);
|
|
@@ -4065,17 +4343,22 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4065
4343
|
const pipelines = rows.filter((row) => row.kind === "pipeline");
|
|
4066
4344
|
return pipelines.length ? pipelines : rows.filter((row) => !row.parentId);
|
|
4067
4345
|
}, [rows]);
|
|
4068
|
-
const
|
|
4346
|
+
const sharedDetail = agents.length > 1 && agents.every((row) => row.span.detail && row.span.detail === agents[0]?.span.detail) ? agents[0]?.span.detail : void 0;
|
|
4347
|
+
const [openFamilies, setOpenFamilies] = useState(/* @__PURE__ */ new Set());
|
|
4348
|
+
const [openSteps, setOpenSteps] = useState(/* @__PURE__ */ new Set());
|
|
4349
|
+
const visible = useMemo(() => view === "agents" ? agentViewRows(agents, rows, openFamilies, openSteps) : visibleFlowWaterfallRows(rows, query, collapsed), [
|
|
4069
4350
|
view,
|
|
4070
4351
|
agents,
|
|
4071
4352
|
rows,
|
|
4353
|
+
openFamilies,
|
|
4354
|
+
openSteps,
|
|
4072
4355
|
query,
|
|
4073
4356
|
collapsed
|
|
4074
4357
|
]);
|
|
4075
4358
|
const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
|
|
4076
|
-
const callouts = useMemo(() => view === "agents" ? agentCallouts(
|
|
4359
|
+
const callouts = useMemo(() => view === "agents" ? agentCallouts(visible, clockMs) : {}, [
|
|
4077
4360
|
view,
|
|
4078
|
-
|
|
4361
|
+
visible,
|
|
4079
4362
|
clockMs
|
|
4080
4363
|
]);
|
|
4081
4364
|
const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
|
|
@@ -4177,12 +4460,14 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4177
4460
|
inspect: (_target, _element) => {}
|
|
4178
4461
|
});
|
|
4179
4462
|
const toggleNow = (row) => {
|
|
4463
|
+
const flip = (open) => new Set(open.has(row.id) ? [...open].filter((id) => id !== row.id) : [...open, row.id]);
|
|
4464
|
+
if (row.id.startsWith("family:")) {
|
|
4465
|
+
setOpenFamilies(flip);
|
|
4466
|
+
return;
|
|
4467
|
+
}
|
|
4180
4468
|
onSelect?.(row.span);
|
|
4181
4469
|
if (view === "agents") {
|
|
4182
|
-
|
|
4183
|
-
for (let cursor = row; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) keep.add(cursor.id);
|
|
4184
|
-
setCollapsed(new Set(rows.filter((item) => item.hasChildren && !keep.has(item.id) && item.kind === "pipeline").map((item) => item.id)));
|
|
4185
|
-
setView("spans");
|
|
4470
|
+
setOpenSteps(flip);
|
|
4186
4471
|
setActiveId(row.id);
|
|
4187
4472
|
return;
|
|
4188
4473
|
}
|
|
@@ -4265,11 +4550,13 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4265
4550
|
const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
|
|
4266
4551
|
const speedup = limit > 0 ? busy / limit : 0;
|
|
4267
4552
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
4268
|
-
const
|
|
4553
|
+
const shown = (row) => row && (visible.find((item) => item.id === row.id) ?? row);
|
|
4554
|
+
const cardRow = shown(card ? byId.get(card.target) : void 0);
|
|
4269
4555
|
const cardAgent = cardRow && (() => {
|
|
4270
|
-
for (let cursor = cardRow; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) if (cursor.kind === "pipeline") return cursor;
|
|
4556
|
+
for (let cursor = cardRow; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) if (cursor.kind === "pipeline") return shown(cursor);
|
|
4271
4557
|
})();
|
|
4272
|
-
const
|
|
4558
|
+
const moving = isLive || position !== null || playing;
|
|
4559
|
+
const showCursor = moving || view === "agents";
|
|
4273
4560
|
const overviewMarks = useMemo(() => rows.filter((row) => !row.parentId).flatMap((row) => row.hasChildren ? row.segments : row.kind === "pipeline" ? [] : [{
|
|
4274
4561
|
startMs: row.span.startMs,
|
|
4275
4562
|
endMs: row.span.endMs,
|
|
@@ -4356,6 +4643,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4356
4643
|
agents.length,
|
|
4357
4644
|
" ",
|
|
4358
4645
|
agents.length === 1 ? "agent" : "agents",
|
|
4646
|
+
sharedDetail ? ` · ${sharedDetail}` : "",
|
|
4359
4647
|
done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
|
|
4360
4648
|
]
|
|
4361
4649
|
}),
|
|
@@ -4413,7 +4701,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4413
4701
|
})]
|
|
4414
4702
|
})]
|
|
4415
4703
|
}),
|
|
4416
|
-
/* @__PURE__ */ jsx("dl", {
|
|
4704
|
+
showSummary && /* @__PURE__ */ jsx("dl", {
|
|
4417
4705
|
className: "fw-summary",
|
|
4418
4706
|
children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
|
|
4419
4707
|
className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
|
|
@@ -4425,6 +4713,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4425
4713
|
}, name))
|
|
4426
4714
|
}),
|
|
4427
4715
|
view === "agents" && playback,
|
|
4716
|
+
view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
|
|
4428
4717
|
/* @__PURE__ */ jsx(FlowBody, {
|
|
4429
4718
|
rows: visible,
|
|
4430
4719
|
byId,
|
|
@@ -4434,6 +4723,10 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4434
4723
|
collapsed,
|
|
4435
4724
|
searching: Boolean(query.trim()),
|
|
4436
4725
|
pauses: trace.pauses,
|
|
4726
|
+
cursorLine: moving,
|
|
4727
|
+
sharedDetail,
|
|
4728
|
+
expandedIds: view === "agents" ? /* @__PURE__ */ new Set([...openFamilies, ...openSteps]) : void 0,
|
|
4729
|
+
fixedHeight: isLive,
|
|
4437
4730
|
slowestId: slowest?.id ?? callouts.slowestId,
|
|
4438
4731
|
costliestId: callouts.costliestId,
|
|
4439
4732
|
activeId,
|
|
@@ -4483,7 +4776,6 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4483
4776
|
compact: view === "agents"
|
|
4484
4777
|
})
|
|
4485
4778
|
}),
|
|
4486
|
-
view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
|
|
4487
4779
|
view === "spans" && playback
|
|
4488
4780
|
]
|
|
4489
4781
|
});
|
package/dist/react/live.css
CHANGED
|
@@ -59,7 +59,7 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
|
|
|
59
59
|
.ov-gantt-open { stroke: var(--chart-live-fg, var(--chart-ink)); stroke-width: 1.5; stroke-dasharray: 3 2; }
|
|
60
60
|
.ov-events, .ov-key, .ov-meter-key { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 12px; line-height: 16px; color: var(--chart-live-muted, var(--chart-ink-muted)); }
|
|
61
61
|
.ov-key li, .ov-events li, .ov-meter-key li { display: inline-flex; align-items: center; gap: 8px; }
|
|
62
|
-
.ov-meter-key b { font-weight:
|
|
62
|
+
.ov-meter-key b { font-weight: 600; color: var(--chart-live-fg, var(--chart-ink)); font-family: var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
|
|
63
63
|
.ov-meter-key i, .ov-event-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 2px; }
|
|
64
64
|
[data-segmented] > .ov-meter-track { display: flex; }
|
|
65
65
|
[data-segmented] > .ov-meter-track > span { flex: none; border-radius: 0; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "Static SVG and HTML figures for benchmarks and operational timing. Draws from producer data and refuses what it cannot draw honestly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|