@tangle-network/charts 0.20.0 → 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-Sr8GtLm0.d.ts → flow-trace-DfSefpBM.d.ts} +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -1
- package/dist/react/flow-waterfall.css +28 -9
- package/dist/react/index.d.ts +7 -2
- package/dist/react/index.js +369 -43
- package/dist/react/live.css +25 -24
- package/package.json +1 -1
|
@@ -382,6 +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
|
+
/** `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';
|
|
385
388
|
/** How many model calls `costUsd` covers; when `priced < total` the total is a lower bound and shown as one. */
|
|
386
389
|
costCoverage?: {
|
|
387
390
|
priced: number;
|
|
@@ -389,7 +392,8 @@ interface FlowTrace {
|
|
|
389
392
|
};
|
|
390
393
|
/** The real charge when the producer knows it (for example $0 on a subscription seat), shown beside a list price. */
|
|
391
394
|
billedUsd?: number;
|
|
392
|
-
|
|
395
|
+
/** Tool calls the producer counted; absent means unknown and is shown as a dash, never as zero. */
|
|
396
|
+
toolCalls?: number;
|
|
393
397
|
/** Authoritative LLM request count when supplied; absent means unknown. */
|
|
394
398
|
modelCalls?: number;
|
|
395
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
|
@@ -3337,6 +3337,13 @@ function unitDotLayout(groups, width, height, o = {}) {
|
|
|
3337
3337
|
}
|
|
3338
3338
|
//#endregion
|
|
3339
3339
|
//#region src/otlp-trace.ts
|
|
3340
|
+
/** Attributes that carry cached input apart from fresh input (OpenTelemetry GenAI and Anthropic-style names). */
|
|
3341
|
+
const CACHE_KEYS = [
|
|
3342
|
+
"gen_ai.usage.cache_read_input_tokens",
|
|
3343
|
+
"gen_ai.usage.cache_creation_input_tokens",
|
|
3344
|
+
"gen_ai.usage.input_tokens.cached",
|
|
3345
|
+
"tangle.llm.cache_read_tokens"
|
|
3346
|
+
];
|
|
3340
3347
|
/** Price model calls from an OpenAI-compatible model catalog (`data[].pricing.prompt|completion`, USD per token), such as
|
|
3341
3348
|
* the Router's `/v1/models`. Read the catalog at runtime; never copy its numbers. */
|
|
3342
3349
|
function catalogPrice(catalog) {
|
|
@@ -3475,6 +3482,7 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3475
3482
|
let cost = 0;
|
|
3476
3483
|
let unpriced = 0;
|
|
3477
3484
|
let priced = 0;
|
|
3485
|
+
let cacheSplit = false;
|
|
3478
3486
|
const offset = (iso) => time(iso) - origin;
|
|
3479
3487
|
const isSpawn = (span) => span.kind === "TOOL" && (attrs(span)["traces.agent.spawn"] === true || /spawn/i.test(str(attrs(span)["gen_ai.tool.name"]) ?? span.name));
|
|
3480
3488
|
const groupOfSpan = (span) => groupOf.get(rootOf(span).span_id);
|
|
@@ -3541,6 +3549,7 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3541
3549
|
if (listPrice !== void 0) {
|
|
3542
3550
|
cost += listPrice;
|
|
3543
3551
|
priced++;
|
|
3552
|
+
if (CACHE_KEYS.some((key) => a[key] !== void 0)) cacheSplit = true;
|
|
3544
3553
|
} else if (kind === "model" && !aggregate && (prompt || completion)) unpriced++;
|
|
3545
3554
|
out.push({
|
|
3546
3555
|
id: span.span_id,
|
|
@@ -3574,7 +3583,8 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3574
3583
|
costCoverage: {
|
|
3575
3584
|
priced,
|
|
3576
3585
|
total: priced + unpriced
|
|
3577
|
-
}
|
|
3586
|
+
},
|
|
3587
|
+
...cacheSplit ? {} : { costBound: "upper" }
|
|
3578
3588
|
} : {}
|
|
3579
3589
|
};
|
|
3580
3590
|
}
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
.fw-root [data-tone="3"] { --tone: var(--fw-h3); }
|
|
63
63
|
.fw-root [data-tone="4"] { --tone: var(--fw-h4); }
|
|
64
64
|
.fw-root [data-tone="5"] { --tone: var(--fw-h5); }
|
|
65
|
-
/* Light fills carry enough of the hue to tell agents apart on white (
|
|
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,6 +233,13 @@
|
|
|
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; }
|
|
236
|
+
.fw-root .fw-kinds { margin: 0; padding: 4px 24px 8px; justify-content: flex-start; color: var(--fw-text); }
|
|
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)); }
|
|
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); }
|
|
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); }
|
|
240
|
+
.fw-root .fw-kinds .fw-swatch-handoff { width: 10px; height: 8px; border-left: 1.5px solid var(--fw-muted); border-bottom: 1.5px solid var(--fw-muted); border-bottom-left-radius: 3px; }
|
|
241
|
+
.fw-root .fw-kinds .fw-swatch-failed { width: 12px; height: 8px; border-radius: 2px; box-shadow: inset 0 0 0 1px var(--fw-failed); }
|
|
242
|
+
.fw-root .fw-description { margin: 0; color: var(--fw-muted); font-size: 14px; line-height: 20px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
234
243
|
.fw-root .fw-legend .fw-swatch { width: 12px; height: 8px; border-radius: 2px; background: var(--fill); box-shadow: inset 0 0 0 1px var(--line); }
|
|
235
244
|
.fw-root .fw-legend b { color: var(--fw-text); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
236
245
|
.fw-root .fw-legend .fw-swatch-step { display: inline-grid; place-items: center; width: 12px; height: 12px; border-radius: 3px; background: var(--fw-well); color: var(--fw-failed); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 50%, transparent); }
|
|
@@ -241,11 +250,13 @@
|
|
|
241
250
|
.fw-root .fw-seg-model { background: var(--fill); box-shadow: inset 0 0 0 1px var(--line); }
|
|
242
251
|
.fw-root .fw-seg-tool { background: var(--dim); box-shadow: inset 0 0 0 1px var(--dim-line); }
|
|
243
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); }
|
|
244
|
-
|
|
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); }
|
|
245
256
|
.fw-root .fw-glyph { display: inline-flex; color: currentColor; }
|
|
246
257
|
.fw-root .fw-glyph svg { width: 11px; height: 11px; stroke-width: 2; }
|
|
247
258
|
.fw-root .fw-seg-model .fw-glyph, .fw-root .fw-k-model .fw-glyph { color: light-dark(var(--tone), #fff); }
|
|
248
|
-
.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; }
|
|
249
260
|
.fw-root .fw-duration { font-size: 12px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
|
|
250
261
|
.fw-root .fw-k-failed ~ .fw-end .fw-duration { color: var(--fw-failed); }
|
|
251
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); }
|
|
@@ -258,17 +269,25 @@
|
|
|
258
269
|
opacity: clamp(0, (var(--fw-t) - var(--s)) * 1000, 1);
|
|
259
270
|
}
|
|
260
271
|
.fw-root .fw-view-spans .fw-link { opacity: calc(clamp(0, (var(--fw-t) - var(--s)) * 1000, 1) * .7); }
|
|
261
|
-
/* Agent-view connectors
|
|
262
|
-
|
|
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); }
|
|
263
275
|
.fw-root .fw-link { transition: opacity 160ms var(--fw-ease); }
|
|
264
|
-
.fw-root .fw-link-down { left: calc(var(--x) - 8px); border-left-width: 1.5px; border-bottom-left-radius:
|
|
265
|
-
.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; }
|
|
266
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); }
|
|
267
279
|
.fw-root .fw-link-down::before { left: -3.25px; }
|
|
268
280
|
.fw-root .fw-link-up::before { right: -3.25px; }
|
|
269
281
|
|
|
270
282
|
/* Span card */
|
|
271
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; }
|
|
272
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);
|
|
273
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); }
|
|
274
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";
|
|
@@ -175,6 +175,7 @@ export declare function HBars({ rows, format, color, max, limit }: {
|
|
|
175
175
|
note?: string;
|
|
176
176
|
color?: string;
|
|
177
177
|
href?: string;
|
|
178
|
+
title?: string;
|
|
178
179
|
}[];
|
|
179
180
|
format?: (v: number) => string;
|
|
180
181
|
color?: string;
|
|
@@ -376,14 +377,18 @@ interface FlowWaterfallProps {
|
|
|
376
377
|
/** What a bar's color means: the agent (default), whose hue its steps and handoffs share, with the outcome on the row dot, legend and
|
|
377
378
|
* crash glyph; or its `outcome`. */
|
|
378
379
|
agentFill?: 'outcome' | 'agent';
|
|
380
|
+
/** One line beside the title saying what the run was for, such as its goal. */
|
|
381
|
+
description?: string;
|
|
379
382
|
/** The row the host has selected, such as the agent whose transcript is open: it lifts and, in the agent view, its peers dim.
|
|
380
383
|
* A pinned card alone only highlights its row, so the rest of the run stays readable around it. */
|
|
381
384
|
selectedId?: string;
|
|
382
385
|
/** Escape clears the host's selection, after closing any open card. */
|
|
383
386
|
onDeselect?: () => void;
|
|
387
|
+
/** Show the stat strip (default); a page with its own stat row passes false. */
|
|
388
|
+
summary?: boolean;
|
|
384
389
|
}
|
|
385
390
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
386
391
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
387
|
-
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill, selectedId, onDeselect }: 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;
|
|
388
393
|
//#endregion
|
|
389
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
|
@@ -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", {
|
|
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: [
|
|
@@ -2437,7 +2441,17 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
2437
2441
|
if (childCost === void 0) {
|
|
2438
2442
|
if (child.kind === "model" || child.hasChildren) costKnown = false;
|
|
2439
2443
|
} else cost += childCost;
|
|
2440
|
-
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
|
+
}
|
|
2441
2455
|
}
|
|
2442
2456
|
if (row.hasChildren && costKnown) row.rollupCostUsd = cost;
|
|
2443
2457
|
if (row.hasChildren) {
|
|
@@ -2556,7 +2570,7 @@ function totalsAt(trace, clockMs) {
|
|
|
2556
2570
|
costUsd: trace.costUsd === void 0 ? void 0 : costs ? cost : trace.costUsd,
|
|
2557
2571
|
tokens: counted ? tokens : trace.promptTokens + trace.completionTokens,
|
|
2558
2572
|
modelCalls: trace.modelCalls === void 0 ? void 0 : models,
|
|
2559
|
-
toolCalls: tools,
|
|
2573
|
+
toolCalls: trace.toolCalls === void 0 ? void 0 : tools,
|
|
2560
2574
|
errors
|
|
2561
2575
|
};
|
|
2562
2576
|
}
|
|
@@ -2575,16 +2589,197 @@ function costSummary(costUsd, trace) {
|
|
|
2575
2589
|
};
|
|
2576
2590
|
const coverage = trace.costCoverage;
|
|
2577
2591
|
const partial = coverage !== void 0 && coverage.priced < coverage.total;
|
|
2592
|
+
const upper = !partial && trace.costBound === "upper";
|
|
2578
2593
|
return {
|
|
2579
|
-
value: `${partial ? "≥" : 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` : ""}`,
|
|
2580
2595
|
title: [
|
|
2581
2596
|
partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
|
|
2582
2597
|
list ? "at the model catalog list price; not a bill" : "",
|
|
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" : "",
|
|
2583
2600
|
trace.billedUsd !== void 0 ? `billed ${formatCost(trace.billedUsd)} (the seat charge)` : ""
|
|
2584
2601
|
].filter(Boolean).join("; "),
|
|
2585
2602
|
label
|
|
2586
2603
|
};
|
|
2587
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
|
+
}
|
|
2588
2783
|
//#endregion
|
|
2589
2784
|
//#region src/react/flow-waterfall-icons.tsx
|
|
2590
2785
|
const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
|
|
@@ -2602,7 +2797,52 @@ const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
|
|
|
2602
2797
|
children
|
|
2603
2798
|
});
|
|
2604
2799
|
/** Kind glyphs drawn when the host supplies no `renderIcon`. Brand marks belong to the host. */
|
|
2605
|
-
|
|
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
|
+
}
|
|
2606
2846
|
switch (kind) {
|
|
2607
2847
|
case "pipeline": return svg(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2608
2848
|
/* @__PURE__ */ jsx("circle", {
|
|
@@ -2684,6 +2924,8 @@ const ZoomOutGlyph = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @_
|
|
|
2684
2924
|
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" }));
|
|
2685
2925
|
//#endregion
|
|
2686
2926
|
//#region src/react/flow-waterfall-card.tsx
|
|
2927
|
+
/** Names wrap between words, never at their hyphens ("memscale-engineer-" / "2" read as two names). */
|
|
2928
|
+
const keepWords = (name) => name.replace(/-/g, "‑");
|
|
2687
2929
|
const KIND_LABEL = {
|
|
2688
2930
|
pipeline: "Agent",
|
|
2689
2931
|
model: "LLM call",
|
|
@@ -2724,7 +2966,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2724
2966
|
if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
|
|
2725
2967
|
cells.splice(6);
|
|
2726
2968
|
const kind = span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind];
|
|
2727
|
-
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;
|
|
2728
2970
|
const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
|
|
2729
2971
|
return /* @__PURE__ */ jsxs("div", {
|
|
2730
2972
|
id,
|
|
@@ -2753,7 +2995,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2753
2995
|
}),
|
|
2754
2996
|
/* @__PURE__ */ jsx("span", {
|
|
2755
2997
|
className: "fw-card-who",
|
|
2756
|
-
children: who.name
|
|
2998
|
+
children: keepWords(who.name)
|
|
2757
2999
|
}),
|
|
2758
3000
|
outcome ? /* @__PURE__ */ jsx("span", {
|
|
2759
3001
|
className: "fw-pill fw-pill-outcome",
|
|
@@ -2834,13 +3076,16 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
|
|
|
2834
3076
|
/* @__PURE__ */ jsx("span", {
|
|
2835
3077
|
className: "fw-icon-tile",
|
|
2836
3078
|
"aria-hidden": true,
|
|
2837
|
-
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3079
|
+
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3080
|
+
kind: span.kind,
|
|
3081
|
+
name: span.name
|
|
3082
|
+
})
|
|
2838
3083
|
}),
|
|
2839
3084
|
/* @__PURE__ */ jsxs("div", {
|
|
2840
3085
|
className: "fw-card-title",
|
|
2841
3086
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2842
3087
|
className: "fw-card-name",
|
|
2843
|
-
children: span.name
|
|
3088
|
+
children: keepWords(span.name)
|
|
2844
3089
|
}), span.detail && /* @__PURE__ */ jsx("div", {
|
|
2845
3090
|
className: "fw-muted",
|
|
2846
3091
|
children: span.detail
|
|
@@ -2897,10 +3142,10 @@ const at = (startMs, endMs) => ({
|
|
|
2897
3142
|
"--s": startMs,
|
|
2898
3143
|
"--e": Number.isFinite(endMs) ? endMs : OPEN_END
|
|
2899
3144
|
});
|
|
2900
|
-
function Segment({ mark, events, agentView }) {
|
|
2901
|
-
const glyph = mark.kind === "failed" ? /* @__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;
|
|
2902
3147
|
return /* @__PURE__ */ jsx("span", {
|
|
2903
|
-
className: `fw-seg fw-seg-${mark.kind}`,
|
|
3148
|
+
className: `fw-seg fw-seg-${mark.kind}${mark.alt ? " fw-seg-alt" : ""}`,
|
|
2904
3149
|
"data-tone": toneKey(mark.tone),
|
|
2905
3150
|
style: at(mark.startMs, mark.endMs),
|
|
2906
3151
|
onPointerEnter: agentView ? (event) => events.onInspect(mark.id, event.currentTarget) : void 0,
|
|
@@ -2923,7 +3168,7 @@ function endLabel(row, clockMs) {
|
|
|
2923
3168
|
if (!spanRunning(span) && clockMs < span.endMs) return `${formatSpanDuration(Math.max(0, clockMs - span.startMs))}${row.approx ? "~" : ""}`;
|
|
2924
3169
|
const duration = spanRunning(span) ? `${formatSpanDuration(Math.max(span.endMs, clockMs) - span.startMs)}${row.approx ? "~" : ""}` : row.durationLabel;
|
|
2925
3170
|
const cost = span.costUsd ?? (row.rollupCostUsd ? row.rollupCostUsd : void 0);
|
|
2926
|
-
return cost === void 0 ? duration : `${duration} · ${formatCost(cost)}`;
|
|
3171
|
+
return cost === void 0 ? duration : `${duration} · ${span.meta?.costPartial ? "≥" : ""}${formatCost(cost)}`;
|
|
2927
3172
|
}
|
|
2928
3173
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
2929
3174
|
const FlowRow = memo(function FlowRow(props) {
|
|
@@ -2935,9 +3180,13 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2935
3180
|
const invalid = row.durationLabel === "—";
|
|
2936
3181
|
const isAgent = row.kind === "pipeline";
|
|
2937
3182
|
const kind = !row.ok ? "failed" : row.kind;
|
|
2938
|
-
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 : [];
|
|
2939
3184
|
const label = endLabel(row, clockMs);
|
|
2940
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;
|
|
2941
3190
|
return /* @__PURE__ */ jsxs("div", {
|
|
2942
3191
|
role: "row",
|
|
2943
3192
|
"data-span-id": row.id,
|
|
@@ -2950,7 +3199,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2950
3199
|
tabIndex: focusable ? 0 : -1,
|
|
2951
3200
|
"data-tone": toneKey(row.tone),
|
|
2952
3201
|
"data-outcome": fillByAgent ? void 0 : span.outcome?.tone,
|
|
2953
|
-
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" : ""}`,
|
|
2954
3203
|
style: {
|
|
2955
3204
|
"--row-y": `${top}px`,
|
|
2956
3205
|
"--fw-enter-delay": `${motion?.enter ?? 0}ms`
|
|
@@ -2960,14 +3209,30 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2960
3209
|
},
|
|
2961
3210
|
onClick: (event) => {
|
|
2962
3211
|
props.onActivate(row);
|
|
3212
|
+
if (family) {
|
|
3213
|
+
props.onToggle(row);
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
2963
3216
|
const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
|
|
2964
3217
|
if (bar) props.onPin(row.id, bar);
|
|
2965
3218
|
},
|
|
2966
3219
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2967
3220
|
role: "gridcell",
|
|
2968
3221
|
className: "fw-label",
|
|
2969
|
-
style: { "--depth": agentView ? 0 : row.depth },
|
|
3222
|
+
style: { "--depth": agentView ? row.inset ?? 0 : row.depth },
|
|
2970
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
|
+
}),
|
|
2971
3236
|
!agentView && /* @__PURE__ */ jsx("span", {
|
|
2972
3237
|
className: "fw-guides",
|
|
2973
3238
|
"aria-hidden": true
|
|
@@ -2988,10 +3253,13 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
2988
3253
|
className: "fw-toggle-space",
|
|
2989
3254
|
"aria-hidden": true
|
|
2990
3255
|
})),
|
|
2991
|
-
!agentView && /* @__PURE__ */ jsx("span", {
|
|
3256
|
+
(!agentView || step) && /* @__PURE__ */ jsx("span", {
|
|
2992
3257
|
className: "fw-icon-tile",
|
|
2993
3258
|
"aria-hidden": true,
|
|
2994
|
-
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3259
|
+
children: renderIcon?.(span) ?? /* @__PURE__ */ jsx(KindGlyph, {
|
|
3260
|
+
kind: span.kind,
|
|
3261
|
+
name: span.name
|
|
3262
|
+
})
|
|
2995
3263
|
}),
|
|
2996
3264
|
/* @__PURE__ */ jsxs("span", {
|
|
2997
3265
|
className: "fw-names",
|
|
@@ -3006,7 +3274,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3006
3274
|
"data-outcome": span.outcome.tone,
|
|
3007
3275
|
title: span.outcome.label,
|
|
3008
3276
|
"aria-hidden": true
|
|
3009
|
-
}) : agentView && /* @__PURE__ */ jsx("span", {
|
|
3277
|
+
}) : agentView && !step && /* @__PURE__ */ jsx("span", {
|
|
3010
3278
|
className: "fw-agent-dot",
|
|
3011
3279
|
"aria-hidden": true
|
|
3012
3280
|
}),
|
|
@@ -3021,9 +3289,9 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3021
3289
|
})
|
|
3022
3290
|
]
|
|
3023
3291
|
}),
|
|
3024
|
-
agentView &&
|
|
3292
|
+
agentView && /* @__PURE__ */ jsx("span", {
|
|
3025
3293
|
className: "fw-subtitle",
|
|
3026
|
-
children: span.detail
|
|
3294
|
+
children: span.detail && span.detail !== props.sharedDetail ? `${span.detail} · ${agentSummary(row)}` : agentSummary(row)
|
|
3027
3295
|
}),
|
|
3028
3296
|
agentView && renderIcon && /* @__PURE__ */ jsx("span", {
|
|
3029
3297
|
className: "fw-detail-icon",
|
|
@@ -3067,7 +3335,8 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3067
3335
|
onPointerLeave: props.onLeave,
|
|
3068
3336
|
onClick: (event) => {
|
|
3069
3337
|
event.stopPropagation();
|
|
3070
|
-
props.
|
|
3338
|
+
if (family) props.onToggle(row);
|
|
3339
|
+
else props.onPin(row.id, event.currentTarget);
|
|
3071
3340
|
},
|
|
3072
3341
|
children: [!isAgent && !row.ok && /* @__PURE__ */ jsx("span", {
|
|
3073
3342
|
className: "fw-glyph",
|
|
@@ -3080,7 +3349,8 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3080
3349
|
marks.map((mark, i) => /* @__PURE__ */ jsx(Segment, {
|
|
3081
3350
|
mark,
|
|
3082
3351
|
events: props,
|
|
3083
|
-
agentView
|
|
3352
|
+
agentView,
|
|
3353
|
+
ending: i === ending
|
|
3084
3354
|
}, `${mark.id}-${i}`)),
|
|
3085
3355
|
/* @__PURE__ */ jsxs("span", {
|
|
3086
3356
|
className: "fw-end",
|
|
@@ -3103,7 +3373,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3103
3373
|
] })
|
|
3104
3374
|
})]
|
|
3105
3375
|
});
|
|
3106
|
-
}, (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)));
|
|
3107
3377
|
/** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
|
|
3108
3378
|
function LinkMark({ link, rowHeight, hot }) {
|
|
3109
3379
|
const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
|
|
@@ -3307,7 +3577,7 @@ function FlowBody(props) {
|
|
|
3307
3577
|
const [viewport, setViewport] = useState(maxHeight);
|
|
3308
3578
|
const latest = useRef(props);
|
|
3309
3579
|
latest.current = props;
|
|
3310
|
-
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);
|
|
3311
3581
|
const links = useMemo(() => linksFor(rows, props.byId, view), [
|
|
3312
3582
|
rows,
|
|
3313
3583
|
props.byId,
|
|
@@ -3315,6 +3585,7 @@ function FlowBody(props) {
|
|
|
3315
3585
|
]);
|
|
3316
3586
|
const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
|
|
3317
3587
|
const motion = useRowMotion(rows);
|
|
3588
|
+
const [jumped, setJumped] = useState();
|
|
3318
3589
|
const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
|
|
3319
3590
|
const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
|
|
3320
3591
|
const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
|
|
@@ -3496,9 +3767,19 @@ function FlowBody(props) {
|
|
|
3496
3767
|
style: { "--s": marker.at },
|
|
3497
3768
|
title: marker.kind ? `${marker.kind}: ${marker.label}` : marker.label,
|
|
3498
3769
|
"aria-label": `Jump to ${marker.kind ? `${marker.kind} ` : ""}${marker.label}`,
|
|
3770
|
+
"aria-pressed": jumped === marker,
|
|
3499
3771
|
onPointerDown: (event) => event.stopPropagation(),
|
|
3500
|
-
onClick: () =>
|
|
3772
|
+
onClick: () => {
|
|
3773
|
+
setJumped(marker);
|
|
3774
|
+
props.onScrub(marker.at);
|
|
3775
|
+
}
|
|
3501
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
|
+
}),
|
|
3502
3783
|
showCursor && /* @__PURE__ */ jsx("span", {
|
|
3503
3784
|
className: "fw-cursor-handle",
|
|
3504
3785
|
"aria-hidden": true
|
|
@@ -3522,6 +3803,8 @@ function FlowBody(props) {
|
|
|
3522
3803
|
clockMs,
|
|
3523
3804
|
renderIcon: props.renderIcon,
|
|
3524
3805
|
fillByAgent: props.fillByAgent,
|
|
3806
|
+
sharedDetail: props.sharedDetail,
|
|
3807
|
+
expanded: props.expandedIds?.has(item.id),
|
|
3525
3808
|
onToggle: props.onToggle,
|
|
3526
3809
|
onInspect: props.onInspect,
|
|
3527
3810
|
onLeave: props.onLeave,
|
|
@@ -3609,7 +3892,7 @@ function FlowBody(props) {
|
|
|
3609
3892
|
})
|
|
3610
3893
|
}, `${marker.at}-${marker.label}`))]
|
|
3611
3894
|
}) : null,
|
|
3612
|
-
showCursor && /* @__PURE__ */ jsx("div", {
|
|
3895
|
+
showCursor && props.cursorLine !== false && /* @__PURE__ */ jsx("div", {
|
|
3613
3896
|
className: "fw-cursor-layer",
|
|
3614
3897
|
"aria-hidden": true,
|
|
3615
3898
|
children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
|
|
@@ -3897,11 +4180,11 @@ function summaryCells(trace, at, durationMs) {
|
|
|
3897
4180
|
errors,
|
|
3898
4181
|
summary: [
|
|
3899
4182
|
["Duration", formatSpanDuration(durationMs)],
|
|
3900
|
-
[
|
|
4183
|
+
...cost.value === "—" ? [] : [[
|
|
3901
4184
|
cost.label,
|
|
3902
4185
|
cost.value,
|
|
3903
4186
|
cost.title
|
|
3904
|
-
],
|
|
4187
|
+
]],
|
|
3905
4188
|
[
|
|
3906
4189
|
"Tokens",
|
|
3907
4190
|
formatCount(tokens),
|
|
@@ -3912,7 +4195,11 @@ function summaryCells(trace, at, durationMs) {
|
|
|
3912
4195
|
modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
|
|
3913
4196
|
modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
|
|
3914
4197
|
],
|
|
3915
|
-
[
|
|
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
|
+
],
|
|
3916
4203
|
["Errors", errors.toLocaleString()]
|
|
3917
4204
|
]
|
|
3918
4205
|
};
|
|
@@ -3924,6 +4211,25 @@ const TONE_ORDER = [
|
|
|
3924
4211
|
"failed",
|
|
3925
4212
|
"neutral"
|
|
3926
4213
|
];
|
|
4214
|
+
/** What each mark means, as the agent view draws it; a key only for kinds the run has. */
|
|
4215
|
+
function KindLegend({ rows }) {
|
|
4216
|
+
const has = (kind) => rows.some((row) => row.own.some((mark) => mark.kind === kind) || row.span.kind === kind);
|
|
4217
|
+
const shown = [
|
|
4218
|
+
["model", "Model call"],
|
|
4219
|
+
["tool", "Tool call"],
|
|
4220
|
+
["waiting", "Waiting"],
|
|
4221
|
+
["handoff", "Handoff"]
|
|
4222
|
+
].filter(([kind]) => kind === "waiting" ? rows.some((row) => row.kind === "pipeline") : kind === "handoff" ? rows.some((row) => row.span.handoffTo || row.hasAgents) : has(kind));
|
|
4223
|
+
if (shown.length === 0) return null;
|
|
4224
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
4225
|
+
className: "fw-legend fw-kinds",
|
|
4226
|
+
"aria-label": "Key",
|
|
4227
|
+
children: shown.map(([kind, name]) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
|
|
4228
|
+
className: `fw-swatch-${kind === "waiting" ? "wait" : kind}`,
|
|
4229
|
+
"aria-hidden": true
|
|
4230
|
+
}), name] }, kind))
|
|
4231
|
+
});
|
|
4232
|
+
}
|
|
3927
4233
|
function OutcomeLegend({ agents }) {
|
|
3928
4234
|
const items = /* @__PURE__ */ new Map();
|
|
3929
4235
|
for (const row of agents) {
|
|
@@ -3964,7 +4270,7 @@ function OutcomeLegend({ agents }) {
|
|
|
3964
4270
|
}
|
|
3965
4271
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
3966
4272
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
3967
|
-
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect }) {
|
|
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 }) {
|
|
3968
4274
|
const root = useRef(null);
|
|
3969
4275
|
const search = useRef(null);
|
|
3970
4276
|
const slider = useRef(null);
|
|
@@ -4037,17 +4343,22 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4037
4343
|
const pipelines = rows.filter((row) => row.kind === "pipeline");
|
|
4038
4344
|
return pipelines.length ? pipelines : rows.filter((row) => !row.parentId);
|
|
4039
4345
|
}, [rows]);
|
|
4040
|
-
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), [
|
|
4041
4350
|
view,
|
|
4042
4351
|
agents,
|
|
4043
4352
|
rows,
|
|
4353
|
+
openFamilies,
|
|
4354
|
+
openSteps,
|
|
4044
4355
|
query,
|
|
4045
4356
|
collapsed
|
|
4046
4357
|
]);
|
|
4047
4358
|
const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
|
|
4048
|
-
const callouts = useMemo(() => view === "agents" ? agentCallouts(
|
|
4359
|
+
const callouts = useMemo(() => view === "agents" ? agentCallouts(visible, clockMs) : {}, [
|
|
4049
4360
|
view,
|
|
4050
|
-
|
|
4361
|
+
visible,
|
|
4051
4362
|
clockMs
|
|
4052
4363
|
]);
|
|
4053
4364
|
const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
|
|
@@ -4149,12 +4460,14 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4149
4460
|
inspect: (_target, _element) => {}
|
|
4150
4461
|
});
|
|
4151
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
|
+
}
|
|
4152
4468
|
onSelect?.(row.span);
|
|
4153
4469
|
if (view === "agents") {
|
|
4154
|
-
|
|
4155
|
-
for (let cursor = row; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) keep.add(cursor.id);
|
|
4156
|
-
setCollapsed(new Set(rows.filter((item) => item.hasChildren && !keep.has(item.id) && item.kind === "pipeline").map((item) => item.id)));
|
|
4157
|
-
setView("spans");
|
|
4470
|
+
setOpenSteps(flip);
|
|
4158
4471
|
setActiveId(row.id);
|
|
4159
4472
|
return;
|
|
4160
4473
|
}
|
|
@@ -4237,11 +4550,13 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4237
4550
|
const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
|
|
4238
4551
|
const speedup = limit > 0 ? busy / limit : 0;
|
|
4239
4552
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
4240
|
-
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);
|
|
4241
4555
|
const cardAgent = cardRow && (() => {
|
|
4242
|
-
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);
|
|
4243
4557
|
})();
|
|
4244
|
-
const
|
|
4558
|
+
const moving = isLive || position !== null || playing;
|
|
4559
|
+
const showCursor = moving || view === "agents";
|
|
4245
4560
|
const overviewMarks = useMemo(() => rows.filter((row) => !row.parentId).flatMap((row) => row.hasChildren ? row.segments : row.kind === "pipeline" ? [] : [{
|
|
4246
4561
|
startMs: row.span.startMs,
|
|
4247
4562
|
endMs: row.span.endMs,
|
|
@@ -4305,6 +4620,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4305
4620
|
className: "fw-title",
|
|
4306
4621
|
children: label
|
|
4307
4622
|
}),
|
|
4623
|
+
description && /* @__PURE__ */ jsx("p", {
|
|
4624
|
+
className: "fw-description",
|
|
4625
|
+
title: description,
|
|
4626
|
+
children: description
|
|
4627
|
+
}),
|
|
4308
4628
|
isLive && /* @__PURE__ */ jsx("span", {
|
|
4309
4629
|
className: "fw-state fw-state-live",
|
|
4310
4630
|
children: "Live"
|
|
@@ -4323,6 +4643,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4323
4643
|
agents.length,
|
|
4324
4644
|
" ",
|
|
4325
4645
|
agents.length === 1 ? "agent" : "agents",
|
|
4646
|
+
sharedDetail ? ` · ${sharedDetail}` : "",
|
|
4326
4647
|
done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
|
|
4327
4648
|
]
|
|
4328
4649
|
}),
|
|
@@ -4380,7 +4701,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4380
4701
|
})]
|
|
4381
4702
|
})]
|
|
4382
4703
|
}),
|
|
4383
|
-
/* @__PURE__ */ jsx("dl", {
|
|
4704
|
+
showSummary && /* @__PURE__ */ jsx("dl", {
|
|
4384
4705
|
className: "fw-summary",
|
|
4385
4706
|
children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
|
|
4386
4707
|
className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
|
|
@@ -4392,6 +4713,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4392
4713
|
}, name))
|
|
4393
4714
|
}),
|
|
4394
4715
|
view === "agents" && playback,
|
|
4716
|
+
view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
|
|
4395
4717
|
/* @__PURE__ */ jsx(FlowBody, {
|
|
4396
4718
|
rows: visible,
|
|
4397
4719
|
byId,
|
|
@@ -4401,6 +4723,10 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4401
4723
|
collapsed,
|
|
4402
4724
|
searching: Boolean(query.trim()),
|
|
4403
4725
|
pauses: trace.pauses,
|
|
4726
|
+
cursorLine: moving,
|
|
4727
|
+
sharedDetail,
|
|
4728
|
+
expandedIds: view === "agents" ? /* @__PURE__ */ new Set([...openFamilies, ...openSteps]) : void 0,
|
|
4729
|
+
fixedHeight: isLive,
|
|
4404
4730
|
slowestId: slowest?.id ?? callouts.slowestId,
|
|
4405
4731
|
costliestId: callouts.costliestId,
|
|
4406
4732
|
activeId,
|
package/dist/react/live.css
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
/* Shared live charts. Consumers may set --chart-live-* tokens on an ancestor.
|
|
2
|
-
|
|
1
|
+
/* Shared live charts. Consumers may set --chart-live-* tokens on an ancestor. Sizes are px on the 12/14 type scale and a
|
|
2
|
+
4 px grid, so a host's font size never rescales them. */
|
|
3
|
+
.ov-empty { margin: 0; color: var(--chart-live-faint, #777); font-size: 12px; }
|
|
3
4
|
.ov-plot { position: relative; min-width: 0; }
|
|
4
5
|
.ov-plot svg { display: block; overflow: visible; }
|
|
5
6
|
line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%, transparent); stroke-width: 1; }
|
|
6
|
-
.ov-axis { fill: var(--chart-live-muted, #666); font:
|
|
7
|
+
.ov-axis { fill: var(--chart-live-muted, #666); font: 12px/16px var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
|
|
7
8
|
.ov-crosshair { stroke: var(--chart-live-faint, #777); stroke-dasharray: 3 3; }
|
|
8
|
-
.ov-tip { position: absolute; z-index: 4; display: grid; gap:
|
|
9
|
-
.ov-tip-line { display: grid; grid-template-columns: auto 1fr auto; gap:
|
|
9
|
+
.ov-tip { position: absolute; z-index: 4; display: grid; gap: 4px; min-width: 160px; max-width: 240px; padding: 8px 12px; background: var(--chart-live-raised, #fff); border: 1px solid var(--chart-live-border, #aaa); border-radius: 8px; box-shadow: 0 10px 24px -12px #000; font-size: 12px; line-height: 16px; pointer-events: none; }
|
|
10
|
+
.ov-tip-line { display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: center; color: var(--chart-live-muted, #666); }
|
|
10
11
|
.ov-tip-line b { color: var(--chart-live-fg, #222); font-variant-numeric: tabular-nums; }
|
|
11
|
-
.ov-hbars { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, max-content) minmax(33%, 1fr) max-content; gap:
|
|
12
|
+
.ov-hbars { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, max-content) minmax(33%, 1fr) max-content; gap: 4px 12px; }
|
|
12
13
|
.ov-hbars li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; }
|
|
13
|
-
.ov-hbars li > a, .ov-hbars li > div { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; row-gap:
|
|
14
|
-
.ov-hbars-more { grid-column: 1 / -1; padding:
|
|
14
|
+
.ov-hbars li > a, .ov-hbars li > div { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; row-gap: 0; align-items: center; padding: 4px; border-radius: 6px; color: inherit; text-decoration: none; }
|
|
15
|
+
.ov-hbars-more { grid-column: 1 / -1; padding: 0 4px; font-size: 12px; color: var(--chart-live-faint, #777); }
|
|
15
16
|
.ov-hbars li > a:hover, .ov-hbars li > div:hover { background: var(--chart-live-hover, #eee); text-decoration: none; }
|
|
16
|
-
.ov-hbar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size:
|
|
17
|
-
.ov-hbar-track { height:
|
|
17
|
+
.ov-hbar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; line-height: 20px; color: var(--chart-live-fg, #222); }
|
|
18
|
+
.ov-hbar-track { height: 8px; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
|
|
18
19
|
.ov-hbar-track > span { display: block; height: 100%; border-radius: 3px; }
|
|
19
|
-
.ov-hbar-value { font:
|
|
20
|
-
.ov-hbar-note { grid-column: 1 / -1; font-size:
|
|
21
|
-
.ov-meter { display: grid; gap:
|
|
22
|
-
.ov-meter-track { display: block; height:
|
|
20
|
+
.ov-hbar-value { font: 12px/16px var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; text-align: right; color: var(--chart-live-fg, #222); }
|
|
21
|
+
.ov-hbar-note { grid-column: 1 / -1; font-size: 12px; color: var(--chart-live-faint, #777); }
|
|
22
|
+
.ov-meter { display: grid; gap: 4px; min-width: 0; }
|
|
23
|
+
.ov-meter-track { display: block; height: 8px; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
|
|
23
24
|
.ov-meter-track > span { display: block; height: 100%; border-radius: 3px; }
|
|
24
|
-
.ov-meter-text { font:
|
|
25
|
-
.ov-spark { display: block; width:
|
|
26
|
-
.ov-spark-empty { font-size:
|
|
27
|
-
.ov-tip-line i { display: inline-block; width:
|
|
25
|
+
.ov-meter-text { font: 12px/16px var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; color: var(--chart-live-muted, #666); }
|
|
26
|
+
.ov-spark { display: block; width: 112px; height: 24px; }
|
|
27
|
+
.ov-spark-empty { font-size: 12px; color: var(--chart-live-faint, #777); }
|
|
28
|
+
.ov-tip-line i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 4px; vertical-align: 0; }
|
|
28
29
|
/* Recorded events and spans. Colours come from Brand tokens; kinds also differ by stroke pattern and glyph. */
|
|
29
30
|
.ov-mark { stroke-width: 1.5; fill: none; }
|
|
30
31
|
.ov-mark-pass { stroke: var(--chart-live-pass, var(--chart-pass, var(--status-running))); }
|
|
@@ -44,7 +45,7 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
|
|
|
44
45
|
.ov-verdict-fail .ov-mark-head { fill: var(--chart-live-danger, var(--chart-breach, var(--status-error))); }
|
|
45
46
|
.ov-verdict-unknown .ov-mark { stroke: var(--chart-live-faint, var(--chart-muted)); }
|
|
46
47
|
.ov-verdict-unknown .ov-mark-head { fill: var(--chart-live-faint, var(--chart-muted)); }
|
|
47
|
-
.ov-mark-label { fill: var(--chart-live-fg, var(--chart-ink)); font-size:
|
|
48
|
+
.ov-mark-label { fill: var(--chart-live-fg, var(--chart-ink)); font-size: 12px; paint-order: stroke; stroke: var(--chart-live-surface, var(--chart-canvas)); stroke-width: 3px; stroke-linejoin: round; }
|
|
48
49
|
.ov-band-after-pass { fill: color-mix(in srgb, var(--chart-live-pass, var(--chart-pass, var(--status-running))) 12%, transparent); }
|
|
49
50
|
.ov-band-idle { fill: var(--chart-live-hover, var(--chart-shade)); }
|
|
50
51
|
.ov-band-blocked { fill: color-mix(in srgb, var(--chart-live-danger, var(--chart-breach, var(--status-error))) 9%, transparent); }
|
|
@@ -56,10 +57,10 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
|
|
|
56
57
|
.ov-hatched { background: repeating-linear-gradient(135deg, var(--chart-live-faint, var(--chart-muted)) 0 1px, transparent 1px 5px), color-mix(in srgb, var(--chart-live-faint, var(--chart-muted)) 10%, transparent); }
|
|
57
58
|
.ov-now { stroke: var(--chart-live-fg, var(--chart-ink)); stroke-width: 1.5; }
|
|
58
59
|
.ov-gantt-open { stroke: var(--chart-live-fg, var(--chart-ink)); stroke-width: 1.5; stroke-dasharray: 3 2; }
|
|
59
|
-
.ov-events, .ov-key, .ov-meter-key { list-style: none; margin:
|
|
60
|
-
.ov-key li, .ov-events li, .ov-meter-key li { display: inline-flex; align-items: center; gap:
|
|
61
|
-
.ov-meter-key b { font-weight:
|
|
62
|
-
.ov-meter-key i, .ov-event-swatch { display: inline-block; width:
|
|
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
|
+
.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: 600; color: var(--chart-live-fg, var(--chart-ink)); font-family: var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
|
|
63
|
+
.ov-meter-key i, .ov-event-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 2px; }
|
|
63
64
|
[data-segmented] > .ov-meter-track { display: flex; }
|
|
64
65
|
[data-segmented] > .ov-meter-track > span { flex: none; border-radius: 0; }
|
|
65
66
|
.ov-event-pass { border-left: 2px solid var(--chart-live-pass, var(--chart-pass, var(--status-running))); border-radius: 0; width: 0; }
|
|
@@ -72,7 +73,7 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
|
|
|
72
73
|
.ov-event-after-pass { background: color-mix(in srgb, var(--chart-live-pass, var(--chart-pass, var(--status-running))) 25%, transparent); }
|
|
73
74
|
.ov-event-idle { background: var(--chart-live-hover, var(--chart-shade)); }
|
|
74
75
|
.ov-event-missing { background: repeating-linear-gradient(135deg, var(--chart-live-faint, var(--chart-muted)) 0 1px, transparent 1px 4px); }
|
|
75
|
-
.ov-refusal { margin: 0; color: var(--chart-live-muted, var(--chart-ink-muted)); font-size:
|
|
76
|
+
.ov-refusal { margin: 0; color: var(--chart-live-muted, var(--chart-ink-muted)); font-size: 12px; }
|
|
76
77
|
|
|
77
78
|
/*
|
|
78
79
|
* DotMap: thousands of dots on one canvas. A categorical lens names at most three categories in colour, the first
|
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",
|