@tangle-network/charts 2.0.5 → 2.0.9
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/README.md
CHANGED
|
@@ -337,9 +337,9 @@ const trace = otlpFlowTrace(spans, { price: catalogPrice(await (await fetch(cata
|
|
|
337
337
|
<FlowWaterfall trace={trace} live={running} defaultView="agents" renderIcon={brandMark} />;
|
|
338
338
|
```
|
|
339
339
|
|
|
340
|
-
`defaultView="agents"` opens the agent timeline: one row per agent with its model, thinking and tool marks, hatched waiting while it delegates, elbow connectors to each agent it spawned and back, retry and failure glyphs, and a headline with the measured speedup over sequential (agent busy time over wall time). Expanding an agent opens the span view with that agent unfolded. The span view virtualizes rows (5,000 spans scroll at 58+ fps in headless Chromium), pins ancestors while their children scroll, and labels each bar with its duration and recorded cost. Hover shows a span card; click pins it and zooms the axis to a span too narrow to read. `onSelect(span)` hears each opened row and pinned bar, so a host can open its own drill-down; `markers={[{ at, label, tone }]}` draws labelled instants, such as releases, across the rows. Give an agent span an `outcome` (`{ tone, label }`) and its lane takes the outcome color and the header a legend. `detail` becomes the agent's subtitle. The card is a table of what the row recorded, every value with its unit: model, duration, tokens in, out and cached (the span's `promptTokens`, `completionTokens` and `cachedTokens`, else the sum of its children's), LLM latency (median and p95 of its model calls), cost, LLM calls, tool calls by tool (the name before " · "), failed steps, and retries and restarts (`retryOf`, `marks` of kind `restart`); a value nobody recorded is left out. `facts` (`[{ label, value }]`)
|
|
340
|
+
`defaultView="agents"` opens the agent timeline: one row per agent with its model, thinking and tool marks, hatched waiting while it delegates, elbow connectors to each agent it spawned and back, retry and failure glyphs, and a headline with the measured speedup over sequential (agent busy time over wall time). Expanding an agent opens the span view with that agent unfolded. The span view virtualizes rows (5,000 spans scroll at 58+ fps in headless Chromium), pins ancestors while their children scroll, and labels each bar with its duration and recorded cost. Hover shows a span card; click pins it and zooms the axis to a span too narrow to read. `onSelect(span)` hears each opened row and pinned bar, so a host can open its own drill-down; `markers={[{ at, label, tone }]}` draws labelled instants, such as releases, across the rows. Give an agent span an `outcome` (`{ tone, label }`) and its lane takes the outcome color and the header a legend. `detail` becomes the agent's subtitle. The card is a table of what the row recorded, every value with its unit: model, duration, tokens in, out and cached (the span's `promptTokens`, `completionTokens` and `cachedTokens`, else the sum of its children's), LLM latency (median and p95 of its model calls), cost, LLM calls, tool calls by tool (the name before " · "), failed steps, and retries and restarts (`retryOf`, `marks` of kind `restart`); a value nobody recorded is left out. `facts` (`[{ label, value }]`) supplies authoritative host rows: a matching label replaces the inferred metric, case-insensitively, including explicit unknown or zero values; additional labels append. The last explicit value wins for a repeated label. Agent lane summaries also use `Model calls` and `Tool calls` facts verbatim (`Model calls: 413`), including unknowns and zero; no count is parsed from a formatted fact. Model/Provider identity and Ran for/Cost end labels also honor facts verbatim; full values remain in the card and label title when phone metadata clips. Opaque duration/cost facts do not compete in numeric superlatives. Family lanes summarize agent and failure counts. Synthetic family cards show `Not recorded` for model identity, model/tool counts, model latency, failures or retries when any member supplies that authoritative fact: formatted counts and percentiles cannot be aggregated. Family tokens become unknown if a member supplies a Tokens fact without its own numeric usage. Cost rollups use full numeric span prices when a member supplies a Cost fact; partial known prices retain their lower bound, and an entirely opaque price is Not recorded. Strings and percentiles are never parsed or summed. The card opens at the pointer, above or below its row, whichever side fits the viewport. `onTimeChange` reports where the reader scrubbed (null when live), and markers double as an event track (click, N or P to jump). A partly priced trace shows its floor and coverage (`costCoverage`), and `billedUsd` shows the real charge beside the list price. Ctrl/Cmd+wheel or `+`/`-`/`0` zoom, Shift+wheel pans, the scrub track under the controls shows the whole trace, and arrow keys walk the tree.
|
|
341
341
|
|
|
342
|
-
Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders. `activityAt` supplies trace-relative event times and `medianEventGapMs` preserves producer cadence before tail truncation; without it, child-step activity supplies the samples. Silence beyond five minutes and three median event gaps hatches the tail and names the quiet duration. The Errors filter expands matching agents and steps; its rug locates each error, marking unreported times separately. Containers below 600px use stacked
|
|
342
|
+
Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders. `activityAt` supplies trace-relative event times and `medianEventGapMs` preserves producer cadence before tail truncation; without it, child-step activity supplies the samples. Silence beyond five minutes and three median event gaps hatches the tail and names the quiet duration. The Errors filter expands matching agents and steps; its rug locates each error, marking unreported times separately. Containers below 600px use stacked 52px rows and a Key disclosure. Search supports Cmd/Ctrl+F and keeps ancestor context. Token totals are optional: omit unknown counts, retain measured zero, and guard optional token fields when reading a trace. Missing, partial and nonfinite usage displays a dash, including during playback. Missing cost and authoritative LLM counts stay Unknown; a cost priced from a catalog is labelled list price. Invalid intervals retain a per-row `—` timing mark. The static phase waterfall remains a separate chart for disjoint phases.
|
|
343
343
|
|
|
344
344
|
`scripts/flow-waterfall-demo.mjs` serves the demo routes (reference-shaped fixtures, recorded runs, a synthetic load trace and OTLP files from `FW_DATA_DIR`); `scripts/flow-waterfall-capture.mjs` captures frames, hover cards, fps, first paint and recordings, and with `FW_LIVE_RELAY` records a live session through `scripts/flow-waterfall-live-relay.mjs` with every span's event-to-pixel time.
|
|
345
345
|
|
|
@@ -343,7 +343,7 @@ interface FlowSpan {
|
|
|
343
343
|
handoffTo?: string;
|
|
344
344
|
/** The host's verdict on an agent span, such as shipped or crashed: colors its lane and joins the legend. */
|
|
345
345
|
outcome?: FlowOutcome;
|
|
346
|
-
/**
|
|
346
|
+
/** Authoritative host rows: replace same-label inferred card metrics and lane model/count/duration/cost labels, case-insensitively; unknowns stay unknown. */
|
|
347
347
|
facts?: readonly {
|
|
348
348
|
label: string;
|
|
349
349
|
value: string;
|
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-VOGEAdl8.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. */
|
|
@@ -271,7 +271,7 @@
|
|
|
271
271
|
left: min(calc(var(--x0) + max(var(--w), 3px) + 4px), calc(100% - var(--fw-end-w, 0px) - 4px)); padding: 0 4px; border-radius: var(--radius-tag);
|
|
272
272
|
background: linear-gradient(var(--fw-tint, transparent) 0 0), var(--fw-surface); white-space: nowrap; pointer-events: none; }
|
|
273
273
|
.fw-root .fw-outcome-end { display: inline-flex; pointer-events: auto; }
|
|
274
|
-
.fw-root .fw-duration { font-size: 14px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
|
|
274
|
+
.fw-root .fw-duration { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
|
|
275
275
|
.fw-root .fw-k-failed ~ .fw-end .fw-duration { color: var(--fw-failed); }
|
|
276
276
|
.fw-root .fw-badge { display: inline-flex; flex: none; height: 18px; padding: 0 6px; border-radius: var(--radius-pill); font-size: 12px; line-height: 18px; font-weight: 500; color: var(--fw-muted); background: transparent; box-shadow: inset 0 0 0 1px var(--fw-border); }
|
|
277
277
|
|
|
@@ -375,11 +375,11 @@
|
|
|
375
375
|
.fw-root .fw-key > summary { display: none; }
|
|
376
376
|
.fw-root .fw-key > summary svg { width: 12px; height: 12px; margin-left: 6px; }
|
|
377
377
|
.fw-root .fw-key[open] > summary svg { transform: rotate(90deg); }
|
|
378
|
-
.fw-root .fw-row-state { display: inline-flex; align-items: center; gap: 4px; font-size:
|
|
378
|
+
.fw-root .fw-row-state { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 600; }
|
|
379
379
|
.fw-root .fw-error-rug { position: relative; height: 10px; margin-left: var(--fw-label-col); overflow: hidden; }
|
|
380
380
|
.fw-root .fw-error-tick { position: absolute; width: 2px; height: 8px; background: var(--fw-failed); left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); }
|
|
381
381
|
.fw-root .fw-error-tick[data-unlocated] { left: auto; right: calc(var(--error-index) * 3px); }
|
|
382
|
-
.fw-root .fw-quiet { display: inline-flex; align-items: center; gap: 4px; color: var(--fw-amber); font-size:
|
|
382
|
+
.fw-root .fw-quiet { display: inline-flex; align-items: center; gap: 4px; color: var(--fw-amber); font-size: 13px; font-weight: 500; white-space: nowrap; }
|
|
383
383
|
.fw-root .fw-quiet::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
|
|
384
384
|
.fw-root [data-activity="quiet"] :is(.fw-agent-dot[data-outcome="live"], .fw-spinner) { display: none; }
|
|
385
385
|
.fw-root [data-activity="quiet"] :is(.fw-agent-dot, .fw-spinner) { animation: none !important; }
|
|
@@ -388,7 +388,7 @@
|
|
|
388
388
|
.fw-root .fw-card-error { margin-top: 8px; color: var(--fw-failed); font-size: 13px; line-height: 18px; }
|
|
389
389
|
.fw-root .fw-card .fw-agent-dot:not([data-outcome]) { background: transparent; box-shadow: inset 0 0 0 1.5px var(--fw-muted); }
|
|
390
390
|
@container flow-waterfall (max-width: 599px) {
|
|
391
|
-
.fw-root > * { --fw-label-col: 0px; --fw-bar-shift:
|
|
391
|
+
.fw-root > * { --fw-label-col: 0px; --fw-bar-shift: 18px; --fw-bar-h: 8px; --fw-row-h: 52px; }
|
|
392
392
|
.fw-root .fw-summary > div:nth-child(-n+2) { flex: 1 0 auto; display: flex; align-items: baseline; gap: 8px; padding: 6px 8px; }
|
|
393
393
|
.fw-root .fw-summary > div:nth-child(-n+2) .fw-value { margin: 0; font-size: 20px; line-height: 28px; }
|
|
394
394
|
.fw-root .fw-summary::after { content: ""; flex-basis: 100%; }
|
|
@@ -410,13 +410,13 @@
|
|
|
410
410
|
.fw-root .fw-tick:is(:nth-child(2), :nth-child(3)) { transform: translate(-50%, -50%); }
|
|
411
411
|
.fw-root .fw-tick:nth-child(4) { transform: translate(calc(-100% - 4px), -50%); }
|
|
412
412
|
.fw-root .fw-row { transform: none; top: var(--row-y); contain: style; }
|
|
413
|
-
.fw-root .fw-label { position: absolute; z-index: 3; height: 20px; top: 0; left: 0; right: 0; padding: 0
|
|
413
|
+
.fw-root .fw-label { position: absolute; z-index: 3; height: 20px; top: 0; left: 0; right: 0; padding: 0 8px 0 calc(8px + var(--depth) * 12px); border: 0; gap: 4px; background: transparent; pointer-events: none; }
|
|
414
|
+
.fw-root .fw-row :is(.fw-row-state, .fw-quiet) { position: absolute; top: 20px; left: calc(28px + var(--depth) * 12px); height: 18px; }
|
|
414
415
|
.fw-root .fw-view-agents .fw-names { flex-direction: row; font-size: 14px; line-height: 20px; }
|
|
415
416
|
.fw-root .fw-view-agents :is(.fw-subtitle, .fw-seg-label) { display: none; }
|
|
416
417
|
.fw-root .fw-view-agents .fw-label { padding-left: calc(8px + var(--depth) * 12px); }
|
|
417
418
|
.fw-root .fw-lane { grid-column: 2; }
|
|
418
|
-
.fw-root .fw-end { top:
|
|
419
|
-
.fw-root .fw-duration { font-size: 12px; }
|
|
419
|
+
.fw-root .fw-end { top: 20px; height: 18px; left: auto; right: 4px; max-width: calc(100% - 144px); }
|
|
420
420
|
.fw-root .fw-time { display: block; grid-column: 2; position: absolute; top: -12px; left: var(--fw-progress); transform: translateX(calc(-1 * var(--fw-progress))); width: max-content; max-width: 100%; background: var(--fw-surface); }
|
|
421
421
|
.fw-root .fw-playback { position: relative; padding: 12px 8px 8px; }
|
|
422
422
|
.fw-root .fw-overview { margin-top: 12px; grid-template-columns: 0 minmax(0, 1fr); column-gap: 0; }
|
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, n as FlowOutcome, 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, n as FlowOutcome, 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-VOGEAdl8.js";
|
|
2
2
|
import { CSSProperties, ReactElement, ReactNode } from "react";
|
|
3
3
|
//#region src/react/sparkline.d.ts
|
|
4
4
|
type SparklineDirection = "rising" | "falling" | "flat";
|
package/dist/react/index.js
CHANGED
|
@@ -6500,6 +6500,7 @@ const toolOf = (name) => name?.split(" · ")[0];
|
|
|
6500
6500
|
const toneKey = (tone) => tone > 0 && tone <= 5 ? tone : 0;
|
|
6501
6501
|
const spanFailed = (span) => span.status === "failed";
|
|
6502
6502
|
const spanRunning = (span) => span.status === "running";
|
|
6503
|
+
const spanFact = (span, label) => new Map((span.facts ?? []).map((fact) => [fact.label.toLowerCase(), fact.value])).get(label.toLowerCase());
|
|
6503
6504
|
const ordered = (span) => Number.isFinite(span.startMs) && Number.isFinite(span.endMs) && span.endMs >= span.startMs;
|
|
6504
6505
|
/** 542ms, 6.9s, 23s, 1m 22s, 2h 05m: the precision a reader compares at each scale. */
|
|
6505
6506
|
function formatSpanDuration(ms) {
|
|
@@ -6570,9 +6571,10 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
6570
6571
|
tone: state === "running" ? "live" : state === "done" ? "ok" : state === "needs-you" ? "warn" : state === "failed" ? "failed" : "neutral",
|
|
6571
6572
|
...state === "unknown" ? { ring: true } : {}
|
|
6572
6573
|
};
|
|
6573
|
-
const span =
|
|
6574
|
+
const span = {
|
|
6574
6575
|
...recorded,
|
|
6575
|
-
outcome
|
|
6576
|
+
outcome: recorded.outcome ?? outcome,
|
|
6577
|
+
detail: spanFact(recorded, recorded.kind === "tool" ? "Provider" : "Model") ?? recorded.detail
|
|
6576
6578
|
};
|
|
6577
6579
|
return {
|
|
6578
6580
|
span,
|
|
@@ -6825,12 +6827,14 @@ function agentCallouts(agents, clockMs) {
|
|
|
6825
6827
|
for (const row of agents) {
|
|
6826
6828
|
if (row.durationLabel === "—" || row.span.startMs > clockMs || row.kind !== "pipeline" || row.hasAgents || row.id.startsWith("family:") || row.id.startsWith("run:")) continue;
|
|
6827
6829
|
const ms = (spanRunning(row.span) ? Math.max(row.span.endMs, clockMs) : Math.min(row.span.endMs, clockMs)) - row.span.startMs;
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6830
|
+
if (spanFact(row.span, "Ran for") === void 0) {
|
|
6831
|
+
timed += 1;
|
|
6832
|
+
if (ms > slowestMs) {
|
|
6833
|
+
slowest = row;
|
|
6834
|
+
slowestMs = ms;
|
|
6835
|
+
}
|
|
6832
6836
|
}
|
|
6833
|
-
const cost = row.span.costUsd ?? row.rollupCostUsd;
|
|
6837
|
+
const cost = spanFact(row.span, "Cost") === void 0 ? row.span.costUsd ?? row.rollupCostUsd : void 0;
|
|
6834
6838
|
if (typeof cost !== "number" || !Number.isFinite(cost)) continue;
|
|
6835
6839
|
priced += 1;
|
|
6836
6840
|
if (cost > top) {
|
|
@@ -6949,9 +6953,24 @@ function agentFamilies(agents, open) {
|
|
|
6949
6953
|
const startMs = Math.min(...members.map((row) => row.span.startMs));
|
|
6950
6954
|
const endMs = Math.max(...members.map((row) => row.span.endMs));
|
|
6951
6955
|
const running = members.some((row) => spanRunning(row.span));
|
|
6952
|
-
const known = members.map((row) => row.span.costUsd ?? row.rollupCostUsd).filter((cost) => cost !== void 0);
|
|
6956
|
+
const known = members.map((row) => row.span.costUsd ?? (spanFact(row.span, "Cost") === void 0 ? row.rollupCostUsd : void 0)).filter((cost) => cost !== void 0 && Number.isFinite(cost));
|
|
6953
6957
|
const failed = members.filter((row) => row.span.outcome ? row.span.outcome.tone === "failed" : !row.ok).length;
|
|
6954
6958
|
const outcome = members.every((row) => row.span.outcome && row.span.outcome.tone === members[0].span.outcome?.tone && !row.span.outcome.ring === !members[0].span.outcome?.ring) ? members[0].span.outcome : void 0;
|
|
6959
|
+
const facts = members.flatMap((row) => (row.span.facts ?? []).filter(({ label }) => [
|
|
6960
|
+
"model",
|
|
6961
|
+
"model calls",
|
|
6962
|
+
"tool calls",
|
|
6963
|
+
"model latency",
|
|
6964
|
+
"failures",
|
|
6965
|
+
"retries"
|
|
6966
|
+
].includes(label.toLowerCase()) || label.toLowerCase() === "tokens" && ![
|
|
6967
|
+
row.span.promptTokens,
|
|
6968
|
+
row.span.completionTokens,
|
|
6969
|
+
row.span.cachedTokens
|
|
6970
|
+
].some(Number.isFinite) || label.toLowerCase() === "cost" && known.length === 0)).map(({ label }) => ({
|
|
6971
|
+
label,
|
|
6972
|
+
value: "Not recorded"
|
|
6973
|
+
}));
|
|
6955
6974
|
out.push({
|
|
6956
6975
|
costFloor: known.length < members.length || members.some((row) => row.costFloor),
|
|
6957
6976
|
id,
|
|
@@ -6992,7 +7011,8 @@ function agentFamilies(agents, open) {
|
|
|
6992
7011
|
activityAt: members.flatMap((row) => row.span.activityAt ?? row.own.flatMap((mark) => [mark.startMs, mark.endMs])),
|
|
6993
7012
|
status: running ? "running" : failed ? "failed" : "done",
|
|
6994
7013
|
...outcome ? { outcome } : {},
|
|
6995
|
-
detail: members[0].span.detail,
|
|
7014
|
+
detail: members.every((row) => row.span.detail === members[0].span.detail) ? members[0].span.detail : void 0,
|
|
7015
|
+
facts,
|
|
6996
7016
|
meta: {
|
|
6997
7017
|
failedAgents: failed,
|
|
6998
7018
|
costPartial: known.length < members.length,
|
|
@@ -7025,13 +7045,17 @@ function agentSummary(row) {
|
|
|
7025
7045
|
const failed = row.own.filter((mark) => mark.kind === "failed").length;
|
|
7026
7046
|
return [failed ? `${failed} failed` : "all done"];
|
|
7027
7047
|
}
|
|
7028
|
-
const
|
|
7029
|
-
const
|
|
7030
|
-
|
|
7031
|
-
row.
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7048
|
+
const facts = new Map((row.span.facts ?? []).map(({ label, value }) => [label.toLowerCase(), value]));
|
|
7049
|
+
const calls = [[
|
|
7050
|
+
"Model calls",
|
|
7051
|
+
row.counts?.model,
|
|
7052
|
+
"model"
|
|
7053
|
+
], [
|
|
7054
|
+
"Tool calls",
|
|
7055
|
+
row.counts?.tool,
|
|
7056
|
+
"tool"
|
|
7057
|
+
]].map(([label, count, kind]) => facts.has(label.toLowerCase()) ? `${label}: ${facts.get(label.toLowerCase())}` : count ? `${count} ${kind} ${count === 1 ? "call" : "calls"}` : "");
|
|
7058
|
+
return [row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : !row.ok ? "failed" : row.kind === "pipeline" ? "" : "done"), ...calls].filter(Boolean);
|
|
7035
7059
|
}
|
|
7036
7060
|
/**
|
|
7037
7061
|
* The agent view's rows: families folded until opened, and an opened agent's own steps (its turns and tool calls, not its
|
|
@@ -7115,7 +7139,7 @@ function drawnRow(row, visible) {
|
|
|
7115
7139
|
} : row;
|
|
7116
7140
|
}
|
|
7117
7141
|
/** A row's cost as printed: "≥" when it is a floor. */
|
|
7118
|
-
const rowCostText = (row, cost) => `${row.costFloor ? "≥" : ""}${formatCost(cost)}
|
|
7142
|
+
const rowCostText = (row, cost) => spanFact(row.span, "Cost") ?? (cost === void 0 ? "" : `${row.costFloor ? "≥" : ""}${formatCost(cost)}`);
|
|
7119
7143
|
/** Quiet is elapsed silence beyond both five minutes and three median event gaps. */
|
|
7120
7144
|
function quietActivity(row, now) {
|
|
7121
7145
|
const times = [...row.span.activityAt ?? row.own.flatMap((mark) => [mark.startMs, mark.endMs])].filter(Number.isFinite).sort((a, b) => a - b);
|
|
@@ -7346,8 +7370,8 @@ function metrics(row, clockMs, full) {
|
|
|
7346
7370
|
const restarts = span.marks?.filter((mark) => mark.kind === "restart").length ?? 0;
|
|
7347
7371
|
if (counts?.retried || restarts) out.push(["Retries", [counts?.retried ? plural(counts.retried, "step retried", "steps retried") : "", restarts ? plural(restarts, "restart") : ""].filter(Boolean).join(" · ")]);
|
|
7348
7372
|
if (full && row.hasChildren) out.push(["Steps", row.descendants.toLocaleString()]);
|
|
7349
|
-
|
|
7350
|
-
return out;
|
|
7373
|
+
const facts = (span.facts ?? []).map(({ label, value }) => [label, value]);
|
|
7374
|
+
return [...new Map([...out, ...facts].map((row) => [row[0].toLowerCase(), row])).values()];
|
|
7351
7375
|
}
|
|
7352
7376
|
function Table({ rows }) {
|
|
7353
7377
|
return /* @__PURE__ */ jsx("dl", {
|
|
@@ -7358,7 +7382,7 @@ function Table({ rows }) {
|
|
|
7358
7382
|
/** The span card: what ran, for how long, on what tokens and at what recorded cost, then its input and output. */
|
|
7359
7383
|
function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderIcon, compact }) {
|
|
7360
7384
|
const span = row.span;
|
|
7361
|
-
const failure = span
|
|
7385
|
+
const failure = spanFact(span, "Failure");
|
|
7362
7386
|
const state = runStatus(span.status ?? "unknown");
|
|
7363
7387
|
const meta = statusMeta(state);
|
|
7364
7388
|
const status = meta.label;
|
|
@@ -7572,11 +7596,8 @@ const open = (row, clockMs) => spanRunning(row.span) || row.span.startMs <= cloc
|
|
|
7572
7596
|
function endLabel(row, clockMs) {
|
|
7573
7597
|
const span = row.span;
|
|
7574
7598
|
if (row.durationLabel === "—") return "—";
|
|
7575
|
-
if (row.approx && span.endMs === span.startMs && !spanRunning(span)) return
|
|
7576
|
-
|
|
7577
|
-
const duration = spanRunning(span) ? `${formatSpanDuration(Math.max(span.endMs, clockMs) - span.startMs)}${row.approx ? "~" : ""}` : row.durationLabel;
|
|
7578
|
-
const cost = span.costUsd ?? (row.rollupCostUsd ? row.rollupCostUsd : void 0);
|
|
7579
|
-
return cost === void 0 ? duration : `${duration} · ${rowCostText(row, cost)}`;
|
|
7599
|
+
if (row.approx && span.endMs === span.startMs && !spanRunning(span)) return rowCostText(row, span.costUsd);
|
|
7600
|
+
return [spanFact(span, "Ran for") ?? (!spanRunning(span) && clockMs < span.endMs ? `${formatSpanDuration(Math.max(0, clockMs - span.startMs))}${row.approx ? "~" : ""}` : spanRunning(span) ? `${formatSpanDuration(Math.max(span.endMs, clockMs) - span.startMs)}${row.approx ? "~" : ""}` : row.durationLabel), !spanRunning(span) && clockMs < span.endMs && spanFact(span, "Cost") === void 0 ? "" : rowCostText(row, span.costUsd ?? row.rollupCostUsd)].filter(Boolean).join(" · ");
|
|
7580
7601
|
}
|
|
7581
7602
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
7582
7603
|
const FlowRow = memo(function FlowRow(props) {
|
|
@@ -7826,6 +7847,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
7826
7847
|
})
|
|
7827
7848
|
}), /* @__PURE__ */ jsx("span", {
|
|
7828
7849
|
className: "fw-duration",
|
|
7850
|
+
title: label,
|
|
7829
7851
|
children: label
|
|
7830
7852
|
})]
|
|
7831
7853
|
})
|
|
@@ -8040,7 +8062,7 @@ function linksFor(rows, byId, view) {
|
|
|
8040
8062
|
function FlowBody(props) {
|
|
8041
8063
|
const { rows, view, maxHeight, collapsed, searching, slowestId, activeId, selectedId, clockMs, window: win, trackPx, showCursor, zoomed } = props;
|
|
8042
8064
|
const [narrow, setNarrow] = useState(false);
|
|
8043
|
-
const rowHeight = narrow ?
|
|
8065
|
+
const rowHeight = narrow ? 52 : ROW_HEIGHT[view];
|
|
8044
8066
|
const scroller = useRef(null);
|
|
8045
8067
|
const surface = useRef(null);
|
|
8046
8068
|
useEffect(() => {
|
|
@@ -9301,7 +9323,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9301
9323
|
const errorEnds = Array.from({ length: errors ?? 0 }, (_, index) => Number.isFinite(errorSpans[index]?.endMs) ? errorSpans[index].endMs : void 0);
|
|
9302
9324
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
9303
9325
|
const shown = (row) => row && drawnRow(row, visible);
|
|
9304
|
-
const cardRow = shown(card ? byId.get(card.target) : void 0);
|
|
9326
|
+
const cardRow = shown(card ? byId.get(card.target) ?? visible.find((row) => row.id === card.target) : void 0);
|
|
9305
9327
|
const cardAgent = cardRow && (() => {
|
|
9306
9328
|
for (let cursor = cardRow; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) if (cursor.kind === "pipeline") return shown(cursor);
|
|
9307
9329
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
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",
|