@tangle-network/sandbox-ui 1.1.8 → 2.0.0

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.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/dist/{AgentGraph-DxAeQQch.d.ts → AgentGraph-B4uWLXlD.d.ts} +12 -17
  3. package/dist/AgentGraph-C1DsA-Rd.js +1369 -0
  4. package/dist/{AgentGraphCanvas-CMdOlSlh.js → AgentGraphCanvas-Bcxq-e6E.js} +557 -383
  5. package/dist/{WorkflowGraph-DZ3xejKE.js → WorkflowGraph-BDjc7V_U.js} +3 -2
  6. package/dist/canvas-theme-DGd0fm1H.js +58 -0
  7. package/dist/{chat-4OZD23RA.js → chat-BrlLa4rg.js} +2 -2
  8. package/dist/chat.d.ts +2 -2
  9. package/dist/chat.js +1 -1
  10. package/dist/{dashboard-ClKA3opr.js → dashboard-FMrtIe9S.js} +3 -3
  11. package/dist/dashboard.js +1 -1
  12. package/dist/globals.css +2366 -1801
  13. package/dist/{index-ucJHuuVZ.d.ts → index-5zOVKgDT.d.ts} +2 -2
  14. package/dist/index-BK8KKLNY.d.ts +26 -0
  15. package/dist/{index-72uBu5lu.d.ts → index-D9-IiEpQ.d.ts} +21 -6
  16. package/dist/index.d.ts +5 -6
  17. package/dist/index.js +8 -291
  18. package/dist/integrations.d.ts +2 -1
  19. package/dist/integrations.js +6 -1
  20. package/dist/latest-events-B34ElSTE.js +190 -0
  21. package/dist/{AgentGraph-DJEQrKTs.js → node-ui-Bfnh08U3.js} +40 -1194
  22. package/dist/primitives-CCdx5Hh_.js +23 -0
  23. package/dist/primitives.d.ts +2 -3
  24. package/dist/primitives.js +1 -2
  25. package/dist/{run-Qern6muT.js → run-BqpdpP1K.js} +161 -60
  26. package/dist/run.d.ts +2 -2
  27. package/dist/run.js +3 -1
  28. package/dist/styles.css +2366 -1801
  29. package/dist/tailwind.css +74 -60
  30. package/dist/tokens.css +452 -851
  31. package/dist/{workbench-DOgYO3Zg.js → workbench-BVnZNhP2.js} +1 -1
  32. package/dist/workbench.js +1 -1
  33. package/dist/workflows.d.ts +3 -7
  34. package/dist/workflows.js +2 -1
  35. package/package.json +11 -10
  36. package/dist/elk-loader-Dl9S7jQk.js +0 -12
  37. package/dist/index-BAEvGqrn.d.ts +0 -26
  38. package/dist/primitives-CiwDp4-0.js +0 -32
  39. package/dist/rolldown-runtime-782esTHO.js +0 -26
@@ -1,302 +1,10 @@
1
1
  import { i as modelBrandFor, t as ModelBrandStack } from "./model-brand-bTcMjQtB.js";
2
2
  import { t as ProviderIcon } from "./provider-logo-CPKRPHcS.js";
3
- import { StatusPill } from "@tangle-network/ui/primitives";
4
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- import { Component, Suspense, lazy, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
6
- import { AlertTriangle, Bell, Bot, Box, Cable, Camera, Check, Circle, Clock, Clock3, Code, GitBranch, LoaderCircle, Repeat, ScanSearch, Sparkles, User, UserCheck, Webhook, Wrench, X, Zap } from "lucide-react";
3
+ import { r as clampPreview } from "./AgentGraph-C1DsA-Rd.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { AlertTriangle, Bell, Box, Cable, Camera, Circle, Clock, Code, GitBranch, Repeat, ScanSearch, Sparkles, UserCheck, Webhook, Zap } from "lucide-react";
6
+ import { RunStatusIcon, statusMeta } from "@tangle-network/ui/run";
7
7
  import { parse } from "yaml";
8
- // src/workflows/agent-hub-css.ts
9
- /**
10
- * The hub layout's styles, scoped under `.ag-root` and shipped with the component
11
- * so a host without the package's Tailwind build still draws it. Colours are the
12
- * package's theme tokens, so light, dark and named themes follow the host.
13
- * Four type sizes (11, 12, 13, 14 px); spacing on a 4 px grid.
14
- */
15
- const AGENT_HUB_CSS = `
16
- .ag-root { --ag-surface: hsl(var(--card)); --ag-fg: hsl(var(--foreground)); --ag-muted: hsl(var(--muted-foreground)); --ag-border: hsl(var(--border));
17
- --ag-line: color-mix(in oklab, var(--ag-fg) 14%, transparent); --ag-accent: hsl(var(--primary));
18
- color: var(--ag-fg); font-size: 13px; line-height: 18px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
19
- .ag-root * { box-sizing: border-box; }
20
- .ag-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-height: 24px; }
21
- .ag-title { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; font-size: 14px; }
22
- .ag-title-mark { width: 14px; height: 14px; border-radius: 4px; background: conic-gradient(from 45deg, var(--ag-accent), color-mix(in oklab, var(--ag-accent) 40%, transparent), var(--ag-accent)); -webkit-mask: radial-gradient(circle at 50% 50%, transparent 2.5px, #000 3px); mask: radial-gradient(circle at 50% 50%, transparent 2.5px, #000 3px); }
23
- .ag-chip { display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 8px; border-radius: 6px; font-size: 12px; font-weight: 500; }
24
- .ag-chip-needs-you { color: var(--agent-graph-needs-you, hsl(38 92% 42%)); background: color-mix(in oklab, var(--agent-graph-needs-you, hsl(38 92% 50%)) 14%, transparent); }
25
- .ag-chip-failed { color: var(--agent-graph-failed, hsl(0 72% 50%)); background: color-mix(in oklab, var(--agent-graph-failed, hsl(0 84% 60%)) 13%, transparent); }
26
- .ag-stats { margin-left: auto; display: inline-flex; gap: 16px; color: var(--ag-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
27
- .ag-stats b { color: var(--ag-fg); font-weight: 500; }
28
- .ag-canvas-wrap { position: relative; border: 1px solid var(--ag-border); border-radius: 12px; background: var(--ag-surface); overflow: hidden; }
29
- .ag-canvas { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
30
- .ag-edges { position: absolute; inset: 0; overflow: visible; }
31
- .ag-edge { fill: none; stroke: var(--ag-line); stroke-width: 1.25; }
32
- .ag-edge-live { fill: none; stroke-width: 1.75; stroke-linecap: round; }
33
- .ag-flow { stroke-dasharray: 28 var(--ag-len); animation: ag-travel 1.15s cubic-bezier(.4,0,.2,1) infinite; }
34
- @keyframes ag-travel { from { stroke-dashoffset: 28px; } to { stroke-dashoffset: calc(-1 * var(--ag-len)); } }
35
- .ag-root g, .ag-node { transition: opacity 180ms cubic-bezier(.2,.8,.2,1); }
36
- .ag-dimmed { opacity: .22; }
37
- .ag-node { position: absolute; display: flex; flex-direction: column; align-items: center; width: 132px; margin-left: -66px; margin-top: calc(var(--ag-tile) / -2);
38
- padding: 0; border: 0; background: none; color: inherit; font: inherit; cursor: pointer; outline: none; }
39
- .ag-tile { position: relative; display: grid; place-items: center; width: var(--ag-tile); height: var(--ag-tile); border-radius: calc(var(--ag-tile) * .26);
40
- background: var(--ag-surface); border: 1px solid var(--ag-border); box-shadow: 0 1px 2px rgb(0 0 0 / .06), 0 4px 12px -6px rgb(0 0 0 / .18);
41
- transition: transform 160ms cubic-bezier(.2,.8,.2,1), box-shadow 160ms; }
42
- .ag-node:hover .ag-tile { transform: translateY(-1px); box-shadow: 0 2px 4px rgb(0 0 0 / .08), 0 8px 18px -8px rgb(0 0 0 / .28); }
43
- .ag-node:focus-visible .ag-tile, .ag-selected .ag-tile { outline: 2px solid var(--ag-accent); outline-offset: 2px; }
44
- .ag-node[style*="--ag-tone"] .ag-tile { border-color: color-mix(in oklab, var(--ag-tone) 55%, var(--ag-border)); background: color-mix(in oklab, var(--ag-tone) 9%, var(--ag-surface)); }
45
- .ag-node-hub .ag-tile::before { content: ""; position: absolute; inset: -28px; z-index: -1; border-radius: 50%; background: radial-gradient(closest-side, color-mix(in oklab, var(--ag-accent) 22%, transparent), transparent); }
46
- .ag-node-you .ag-tile { background: var(--ag-fg); color: var(--ag-surface); border-color: transparent; }
47
- .ag-name { margin-top: 6px; font-size: 13px; font-weight: 500; line-height: 16px; max-width: 132px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
48
- .ag-node-tool .ag-name { font-weight: 400; color: var(--ag-muted); }
49
- .ag-sub { font-size: 11px; line-height: 14px; color: var(--ag-muted); max-width: 132px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
50
- .ag-badge { position: absolute; top: -5px; right: -5px; display: grid; place-items: center; width: 15px; height: 15px; border-radius: 50%; color: #fff; font-size: 10px; font-weight: 700; box-shadow: 0 0 0 2px var(--ag-surface); }
51
- .ag-badge-running { background: var(--ag-surface); color: var(--ag-accent); box-shadow: 0 0 0 1px var(--ag-border); }
52
- .ag-badge-running svg, .ag-spin { animation: ag-spin .9s linear infinite; }
53
- @keyframes ag-spin { to { transform: rotate(360deg); } }
54
- .ag-legend { list-style: none; margin: 0; padding: 0; display: flex; justify-content: center; gap: 20px; color: var(--ag-muted); font-size: 12px; }
55
- .ag-legend li { display: inline-flex; align-items: center; gap: 6px; }
56
- .ag-legend-line { width: 14px; height: 2px; border-radius: 1px; }
57
- .ag-popover { position: absolute; z-index: 5; width: 280px; padding: 12px; border-radius: 12px; background: hsl(var(--popover, var(--card))); color: var(--ag-fg);
58
- border: 1px solid var(--ag-border); box-shadow: 0 16px 40px -12px rgb(0 0 0 / .35), 0 2px 6px rgb(0 0 0 / .08); animation: ag-arrive 150ms cubic-bezier(.2,.8,.2,1); }
59
- @keyframes ag-arrive { from { opacity: 0; transform: translateY(4px); } }
60
- .ag-pop-head { display: flex; align-items: center; gap: 8px; }
61
- .ag-tile-mini { border-radius: 8px; box-shadow: none; flex-shrink: 0; }
62
- .ag-tile-micro { border-radius: 4px; box-shadow: none; flex-shrink: 0; border: 0; background: none; }
63
- .ag-pop-title { display: flex; flex-direction: column; min-width: 0; flex: 1; }
64
- .ag-pop-name { font-size: 14px; font-weight: 600; line-height: 18px; }
65
- .ag-pop-sub { font-size: 12px; color: var(--ag-muted); line-height: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
66
- .ag-pill { height: 20px; padding: 0 8px; border-radius: 6px; font-size: 11px; line-height: 20px; font-weight: 500; color: var(--ag-muted); background: color-mix(in oklab, var(--ag-fg) 7%, transparent); }
67
- .ag-pill-failed { color: var(--agent-graph-failed, hsl(0 72% 50%)); background: color-mix(in oklab, var(--agent-graph-failed, hsl(0 84% 60%)) 13%, transparent); }
68
- .ag-pill-needs-you { color: var(--agent-graph-needs-you, hsl(38 92% 42%)); background: color-mix(in oklab, var(--agent-graph-needs-you, hsl(38 92% 50%)) 14%, transparent); }
69
- .ag-pill-running { color: var(--ag-accent); background: color-mix(in oklab, var(--ag-accent) 12%, transparent); }
70
- .ag-pill-succeeded { color: var(--agent-graph-answer, hsl(152 60% 38%)); background: color-mix(in oklab, var(--agent-graph-answer, hsl(152 60% 45%)) 13%, transparent); }
71
- .ag-close { display: grid; place-items: center; width: 24px; height: 24px; margin-right: -4px; border: 0; border-radius: 6px; background: none; color: var(--ag-muted); cursor: pointer; }
72
- .ag-close:hover { background: color-mix(in oklab, var(--ag-fg) 7%, transparent); color: var(--ag-fg); }
73
- .ag-close:focus-visible { outline: 2px solid var(--ag-accent); }
74
- .ag-pop-summary { margin: 8px 0 0; font-size: 13px; line-height: 18px; }
75
- .ag-pop-calls { list-style: none; margin: 8px 0 0; padding: 8px 0 0; border-top: 1px solid var(--ag-border); display: grid; gap: 6px; }
76
- .ag-pop-calls li { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 16px; min-width: 0; }
77
- .ag-pop-dir { color: var(--ag-muted); width: 12px; flex-shrink: 0; }
78
- .ag-pop-call { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ag-muted); }
79
- .ag-pop-call b { color: var(--ag-fg); font-weight: 500; }
80
- .ag-pop-time { flex-shrink: 0; color: var(--ag-muted); font-variant-numeric: tabular-nums; display: inline-flex; }
81
- .ag-pop-foot { margin: 8px 0 0; padding-top: 8px; border-top: 1px solid var(--ag-border); font-size: 12px; color: var(--ag-muted); display: flex; gap: 6px; }
82
- .ag-pop-foot b { color: var(--ag-fg); font-weight: 600; }
83
- @media (prefers-reduced-motion: reduce) {
84
- .ag-root *, .ag-root *::before { animation: none !important; transition: none !important; }
85
- }
86
- `;
87
- const HUB_METRICS = {
88
- cellX: 184,
89
- cellY: 140,
90
- tile: 48,
91
- hubTile: 60,
92
- label: 34
93
- };
94
- const HUB_METRICS_COMPACT = {
95
- cellX: 112,
96
- cellY: 112,
97
- tile: 40,
98
- hubTile: 48,
99
- label: 30
100
- };
101
- const key = (col, row) => `${col},${row}`;
102
- /** Ring cells around the hub, nearest first: the diagonals, then the sides, then ring two by angle. */
103
- function ringCells(count) {
104
- const cells = [
105
- [-1, -1],
106
- [1, -1],
107
- [-1, 1],
108
- [1, 1],
109
- [-1, 0],
110
- [1, 0],
111
- [0, 1],
112
- [0, -1]
113
- ];
114
- for (let ring = 2; cells.length < count; ring++) {
115
- const outer = [];
116
- for (let c = -ring; c <= ring; c++) for (let r = -ring; r <= ring; r++) if (Math.max(Math.abs(c), Math.abs(r)) === ring) outer.push([c, r]);
117
- outer.sort((a, b) => Math.atan2(a[1], a[0]) - Math.atan2(b[1], b[0]));
118
- cells.push(...outer);
119
- }
120
- return cells.slice(0, count);
121
- }
122
- /** The orchestrator: the agent nobody spawned, else the first. */
123
- function hubAgentId(nodes, edges) {
124
- const spawned = new Set(edges.filter((e) => e.kind === "spawn").map((e) => e.to));
125
- return (nodes.find((n) => !spawned.has(n.id)) ?? nodes[0])?.id;
126
- }
127
- /**
128
- * Place every node on the grid. Agents take ring cells in order; each tool takes
129
- * the first free cell outward from the first agent that called it (beside, then
130
- * above or below, then diagonal), so it sits next to its user.
131
- */
132
- function layoutHub(input) {
133
- const { nodes, edges, tools, calls, you } = input;
134
- const hub = hubAgentId(nodes, edges);
135
- const placements = [];
136
- const taken = /* @__PURE__ */ new Set();
137
- const place = (id, kind, col, row) => {
138
- placements.push({
139
- id,
140
- kind,
141
- col,
142
- row
143
- });
144
- taken.add(key(col, row));
145
- };
146
- if (hub) place(hub, "hub", 0, 0);
147
- const agents = nodes.filter((n) => n.id !== hub);
148
- ringCells(agents.length).forEach(([c, r], i) => place(agents[i].id, "agent", c, r));
149
- const ring = Math.max(1, ...placements.map((p) => Math.max(Math.abs(p.col), Math.abs(p.row))));
150
- if (you) {
151
- const col = -Math.min(ring, 1);
152
- let row = -ring - 1;
153
- while (taken.has(key(col, row))) row--;
154
- place("you", "you", col, row);
155
- }
156
- const cell = new Map(placements.map((p) => [p.id, p]));
157
- const userOf = /* @__PURE__ */ new Map();
158
- for (const call of calls) {
159
- const toolId = tools.some((t) => t.id === call.to) ? call.to : tools.some((t) => t.id === call.from) ? call.from : void 0;
160
- const agentId = toolId === call.to ? call.from : call.to;
161
- if (toolId && cell.has(agentId) && !userOf.has(toolId)) userOf.set(toolId, agentId);
162
- }
163
- for (const tool of tools) {
164
- const owner = cell.get(userOf.get(tool.id) ?? hub ?? "");
165
- const ac = owner?.col ?? 0, ar = owner?.row ?? 0;
166
- const sx = Math.sign(ac) || 1, sy = Math.sign(ar) || 1;
167
- const options = ac === 0 ? [
168
- [0, sy],
169
- [sx, sy],
170
- [-sx, sy],
171
- [sx, 0]
172
- ] : ar === 0 ? [
173
- [sx, 0],
174
- [sx, -1],
175
- [sx, 1],
176
- [0, -1]
177
- ] : [
178
- [sx, 0],
179
- [0, sy],
180
- [sx, sy],
181
- [-sx, sy],
182
- [sx, -sy]
183
- ];
184
- let spot;
185
- for (let step = 1; !spot && step < 6; step++) for (const [dx, dy] of options) {
186
- const c = ac + dx * step, r = ar + dy * step;
187
- if (!taken.has(key(c, r))) {
188
- spot = [c, r];
189
- break;
190
- }
191
- }
192
- place(tool.id, "tool", spot[0], spot[1]);
193
- }
194
- const links = [];
195
- const seen = /* @__PURE__ */ new Set();
196
- const link = (from, to) => {
197
- const k = [from, to].sort().join("|");
198
- if (from === to || seen.has(k) || !taken.size) return;
199
- seen.add(k);
200
- links.push({
201
- from,
202
- to
203
- });
204
- };
205
- if (you && hub) link("you", hub);
206
- for (const agent of agents) {
207
- const parent = edges.find((e) => e.kind === "spawn" && e.to === agent.id)?.from;
208
- link(parent && cell.has(parent) ? parent : hub, agent.id);
209
- }
210
- for (const call of calls) if (cell.has(call.from) && cell.has(call.to)) link(call.from, call.to);
211
- return {
212
- placements,
213
- links,
214
- minCol: Math.min(...placements.map((p) => p.col)),
215
- maxCol: Math.max(...placements.map((p) => p.col)),
216
- minRow: Math.min(...placements.map((p) => p.row)),
217
- maxRow: Math.max(...placements.map((p) => p.row))
218
- };
219
- }
220
- /**
221
- * An orthogonal path between two tile centres with rounded elbows. Leaving the
222
- * hub, and between nodes in one column, it goes vertical first; otherwise
223
- * horizontal first. `lane` shifts a shared run sideways so parallel edges stay
224
- * apart. Returns the SVG path and its length (for the travelling pulse).
225
- */
226
- function orthogonalPath(a, b, verticalFirst, lane = 0, radius = 10) {
227
- const dx = b.x - a.x, dy = b.y - a.y;
228
- if (Math.abs(dx) < 1 || Math.abs(dy) < 1) return {
229
- d: `M${a.x},${a.y} L${b.x},${b.y}`,
230
- length: Math.hypot(dx, dy)
231
- };
232
- const r = Math.min(radius, Math.abs(dx) / 2, Math.abs(dy) / 2);
233
- const sx = Math.sign(dx), sy = Math.sign(dy);
234
- if (verticalFirst) {
235
- const x = a.x + lane;
236
- return {
237
- d: `M${x},${a.y} L${x},${b.y - sy * r} Q${x},${b.y} ${x + sx * r},${b.y} L${b.x},${b.y}`,
238
- length: Math.abs(dy) + Math.abs(b.x - x) - 2 * r + Math.PI * r / 2
239
- };
240
- }
241
- const y = a.y + lane;
242
- return {
243
- d: `M${a.x},${y} L${b.x - sx * r},${y} Q${b.x},${y} ${b.x},${y + sy * r} L${b.x},${b.y}`,
244
- length: Math.abs(dx) + Math.abs(b.y - y) - 2 * r + Math.PI * r / 2
245
- };
246
- }
247
- // src/workflows/format.ts
248
- /**
249
- * Display formatters for the workflow graph's live run-state chips. Pure and
250
- * dependency-free so they can be unit-tested without rendering the graph.
251
- */
252
- /** Human-readable duration: `850ms`, `4.2s`, `1m30s`. Undefined for an absent,
253
- * non-finite, or negative input (e.g. clock skew) so the caller renders no
254
- * chip rather than a confusing `-5ms`. */
255
- function fmtDuration(ms) {
256
- if (ms === void 0 || !Number.isFinite(ms) || ms < 0) return void 0;
257
- if (ms < 1e3) return `${Math.round(ms)}ms`;
258
- const s = ms / 1e3;
259
- if (s < 60) return `${s.toFixed(1)}s`;
260
- const totalSeconds = Math.round(s);
261
- return `${Math.floor(totalSeconds / 60)}m${totalSeconds % 60}s`;
262
- }
263
- /** Human-readable USD cost: `$0`, `$0.0032` (sub-cent, 4dp), `$1.20`. Undefined
264
- * for an absent, non-finite, or negative input — a cost is a billing amount, so
265
- * a negative value is bad data, not a refund to render. */
266
- function fmtCost(usd) {
267
- if (usd === void 0 || !Number.isFinite(usd) || usd < 0) return void 0;
268
- if (usd === 0) return "$0";
269
- return usd < .01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
270
- }
271
- /** Bound host-supplied preview/error text to a fixed length (with an ellipsis)
272
- * before it reaches the DOM, so an oversized payload can't bloat the node tree —
273
- * the card only ever shows a short preview, and CSS clamping is visual only.
274
- * Truncation is code-point aware: it never slices through a surrogate pair (which
275
- * would leave a lone surrogate that renders as the replacement character). */
276
- function clampPreview(text, max = 200) {
277
- if (text.length <= max) return text;
278
- const lastCode = text.charCodeAt(max - 1);
279
- const end = lastCode >= 55296 && lastCode <= 56319 ? max - 1 : max;
280
- return `${text.slice(0, end)}…`;
281
- }
282
- /** Human-readable token usage: `1200/340 tok`. Undefined when neither side is a
283
- * finite, non-negative count, so the caller renders no chip; an invalid side
284
- * (negative / NaN / absent) shows as 0 rather than leaking `-5/NaN tok`. */
285
- function fmtTokens(input, output) {
286
- const valid = (n) => n !== void 0 && Number.isFinite(n) && n >= 0 ? n : void 0;
287
- const i = valid(input);
288
- const o = valid(output);
289
- if (i === void 0 && o === void 0) return void 0;
290
- return `${i ?? 0}/${o ?? 0} tok`;
291
- }
292
- /** A token total in a few characters: `840 tok`, `12.4k tok`, `1.2M tok`.
293
- * Undefined for an absent, non-finite or negative count. */
294
- function fmtTokenTotal(tokens) {
295
- if (tokens == null || !Number.isFinite(tokens) || tokens < 0) return void 0;
296
- if (tokens < 1e3) return `${Math.round(tokens)} tok`;
297
- if (tokens < 1e6) return `${(tokens / 1e3).toFixed(tokens < 1e4 ? 1 : 0)}k tok`;
298
- return `${(tokens / 1e6).toFixed(1)}M tok`;
299
- }
300
8
  // src/workflows/cron.ts
301
9
  /**
302
10
  * Cron → English for a schedule trigger's node label. A raw `0 9 * * 1-5` tells
@@ -1556,34 +1264,9 @@ function buildWorkflowGraph(yaml, options) {
1556
1264
  };
1557
1265
  }
1558
1266
  // src/workflows/node-ui.tsx
1559
- /**
1560
- * Shared presentational atoms for a workflow graph node — the type glyph, the
1561
- * status color/label/badge maps, and the edge coloring. One source of truth so
1562
- * the node card and the edges that point at it read as one system.
1563
- *
1564
- * Colors resolve to the SEMANTIC surface tokens (`--surface-success-text`,
1565
- * `--surface-danger-text`, …), which carry a light AND a dark value tuned for
1566
- * contrast against the card. A literal palette hex (`#22c55e`) or a stock
1567
- * Tailwind shade (`text-red-400`) carries ONE value, so it can only ever hold its
1568
- * contrast in one of the two themes — never use one for a status color.
1569
- *
1570
- * Inline `style` colors here use the RAW brand tokens (`hsl(var(--primary))`,
1571
- * `var(--surface-…)`) — NOT the `--color-*` @theme aliases, which are undefined
1572
- * in a consumer that only imports `tokens.css` (e.g. platform-web), where a
1573
- * `var(--color-*)` would silently resolve to nothing. See the edge-color test.
1574
- */
1575
1267
  /** Track color behind a progress bar — a faint wash of the muted token. */
1576
1268
  const MUTED_TRACK = "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)";
1577
- /** Per-KIND accents — the QUIET identity channel. The 2026-10-09 canvas
1578
- * critique's first blocker: kind and state shared the same channel and hue
1579
- * family (sandbox-kind was green like DONE; structural-kind amber like
1580
- * WAITING; agent-kind indigo like RUNNING). The split:
1581
- * KIND = the glyph-tile tint + a 2px top rule. Hues chosen to NEVER
1582
- * collide with the state family (green/amber/red are reserved
1583
- * for state alone).
1584
- * STATE = the border, ring, glow and fill (see statusBorder).
1585
- * Kind hue duplicates are deliberate: shape + glyph carry identity; the
1586
- * tint is a hint, not the signal. */
1269
+ /** Glyph accents identify the node kind; ui's status metadata owns run state. */
1587
1270
  const KIND_ACCENT = {
1588
1271
  "agent.run": "hsl(var(--primary))",
1589
1272
  "script.run": "var(--surface-info-text)",
@@ -1593,8 +1276,7 @@ const KIND_ACCENT = {
1593
1276
  "line.send": "var(--surface-info-text)",
1594
1277
  notify: "var(--surface-info-text)"
1595
1278
  };
1596
- /** Structural kinds (parallel/foreach/decision) rest SLATE — amber is
1597
- * reserved for `waiting`, full stop. */
1279
+ /** Structural nodes stay neutral; approval status owns warning ink. */
1598
1280
  const TONE_ACCENT = {
1599
1281
  trigger: "hsl(var(--primary))",
1600
1282
  structural: "hsl(var(--muted-foreground))",
@@ -1605,120 +1287,39 @@ const TONE_ACCENT = {
1605
1287
  function kindAccent(tone, kind) {
1606
1288
  return (kind !== void 0 ? KIND_ACCENT[kind] : void 0) ?? TONE_ACCENT[tone];
1607
1289
  }
1608
- /** Resting elevation — the 2026-10-09 critique's depth finding: the card
1609
- * sat FLAT on the canvas (three near-identical greys, borders invisible).
1610
- * The card must sit ON the canvas, not in it: a real two-layer shadow plus a
1611
- * 1px inner top highlight, foreground-token based so both themes get depth
1612
- * without a dark-mode black box. */
1613
- const RESTING_SHADOW = "inset 0 1px 0 hsl(var(--foreground) / 0.07), 0 1px 2px hsl(var(--foreground) / 0.10), 0 12px 32px -14px hsl(var(--foreground) / 0.40)";
1290
+ /** Brand shadow channels stay black in dark mode. */
1291
+ const RESTING_SHADOW = "inset 0 1px 0 hsl(var(--foreground) / 0.06), 0 1px 3px hsl(var(--shadow-color) / 0.08)";
1614
1292
  /** The resting card border: visible against the canvas in BOTH themes —
1615
1293
  * `--border` vanished on dark. */
1616
1294
  const RESTING_BORDER = "hsl(var(--foreground) / 0.14)";
1617
- const STATUS_QUEUED = "hsl(var(--muted-foreground))";
1618
- const STATUS_DONE = "var(--surface-success-text)";
1619
- const STATUS_FAILED = "var(--surface-danger-text)";
1620
- const STATUS_RUNNING = "hsl(var(--primary))";
1621
- /** A run blocked on a human borrows the warning surface — the same amber the
1622
- * design system uses for "needs attention". It must never read as the primary
1623
- * "running" accent: the workflow is not working, it is waiting on the viewer. */
1624
- const STATUS_WAITING = "var(--surface-warning-text)";
1625
- const STATUS_COLOR = {
1626
- queued: STATUS_QUEUED,
1627
- running: STATUS_RUNNING,
1628
- waiting: STATUS_WAITING,
1629
- succeeded: STATUS_DONE,
1630
- failed: STATUS_FAILED,
1631
- skipped: STATUS_QUEUED
1632
- };
1633
- /** An edge reads by the status of the node it points AT — with the 2026-10-09
1634
- * fix: SETTLED edges go quiet. A succeeded run used to paint every edge green
1635
- * ("the green wall"); now only the live front carries color: running keeps
1636
- * primary, waiting amber, failed danger, and succeeded/queued sit at a quiet
1637
- * neutral so the eye finds the front, not the history. */
1295
+ /** Workflow waiting means human approval; peer waiting remains neutral in agent views. */
1296
+ const workflowStatus = (status) => status === "waiting" ? "needs-you" : status === "skipped" ? "stopped" : status;
1297
+ const workflowMeta = (status) => statusMeta(workflowStatus(status));
1298
+ const workflowStatuses = [
1299
+ "queued",
1300
+ "running",
1301
+ "waiting",
1302
+ "succeeded",
1303
+ "failed",
1304
+ "skipped"
1305
+ ];
1306
+ `${statusMeta("running").token}`;
1307
+ const STATUS_COLOR = Object.fromEntries(workflowStatuses.map((status) => [status, `var(${workflowMeta(status).token})`]));
1308
+ const STATUS_LABEL = Object.fromEntries(workflowStatuses.map((status) => [status, workflowMeta(status).label]));
1309
+ /** Retained public styling contract, derived from the owner's status metadata. */
1310
+ const STATUS_PILL = Object.fromEntries(workflowStatuses.map((status) => [status, {
1311
+ color: STATUS_COLOR[status],
1312
+ background: "transparent",
1313
+ borderColor: "hsl(var(--border))"
1314
+ }]));
1638
1315
  function edgeColor(status) {
1639
- if (status === "running") return STATUS_RUNNING;
1640
- if (status === "waiting") return STATUS_WAITING;
1641
- if (status === "failed") return STATUS_FAILED;
1642
- return "color-mix(in srgb, hsl(var(--muted-foreground)) 38%, transparent)";
1316
+ return status === "failed" || status === "waiting" ? STATUS_COLOR[status] : "color-mix(in srgb, hsl(var(--muted-foreground)) 80%, transparent)";
1643
1317
  }
1644
- const STATUS_LABEL = {
1645
- queued: "Queued",
1646
- running: "Running",
1647
- waiting: "Waiting on you",
1648
- succeeded: "Done",
1649
- failed: "Failed",
1650
- skipped: "Not taken"
1651
- };
1652
- /** @deprecated `StatusPill` now renders ui's StatusPill from `STATUS_TONE`;
1653
- * kept for consumers that styled their own pill from it.
1654
- * Status pill styling — a tinted well in the status color. Built from the
1655
- * semantic surface trio (bg/border/text) so the pill keeps its contrast in both
1656
- * themes rather than washing out in one of them. */
1657
- const STATUS_PILL = {
1658
- queued: {
1659
- background: "color-mix(in srgb, hsl(var(--muted-foreground)) 12%, transparent)",
1660
- color: "hsl(var(--muted-foreground))",
1661
- borderColor: "color-mix(in srgb, hsl(var(--muted-foreground)) 28%, transparent)"
1662
- },
1663
- running: {
1664
- background: "color-mix(in srgb, hsl(var(--primary)) 14%, transparent)",
1665
- color: "hsl(var(--primary))",
1666
- borderColor: "color-mix(in srgb, hsl(var(--primary)) 40%, transparent)"
1667
- },
1668
- waiting: {
1669
- background: "var(--surface-warning-bg)",
1670
- color: "var(--surface-warning-text)",
1671
- borderColor: "var(--surface-warning-border)"
1672
- },
1673
- succeeded: {
1674
- background: "var(--surface-success-bg)",
1675
- color: "var(--surface-success-text)",
1676
- borderColor: "var(--surface-success-border)"
1677
- },
1678
- failed: {
1679
- background: "var(--surface-danger-bg)",
1680
- color: "var(--surface-danger-text)",
1681
- borderColor: "var(--surface-danger-border)"
1682
- },
1683
- skipped: {
1684
- background: "transparent",
1685
- color: "hsl(var(--muted-foreground))",
1686
- borderColor: "color-mix(in srgb, hsl(var(--muted-foreground)) 28%, transparent)"
1687
- }
1688
- };
1689
- /** The card's border + ring once a node has live run state. The emphasis
1690
- * law (the critique's second blocker, inverted): the EXCEPTION is the
1691
- * loudest thing on the canvas —
1692
- * failed saturated danger + a halo wider than running's
1693
- * waiting amber + glow (the node the viewer must act on)
1694
- * running primary + glow (the live front)
1695
- * succeeded QUIET: a neutral border, no ring, no glow — done is the
1696
- * resting state of a healthy run and must not shout */
1697
1318
  function statusBorder(status) {
1698
- switch (status) {
1699
- case "failed": return {
1700
- borderColor: STATUS_FAILED,
1701
- boxShadow: `0 0 0 2px color-mix(in srgb, ${STATUS_FAILED} 55%, transparent), 0 0 32px -4px color-mix(in srgb, ${STATUS_FAILED} 70%, transparent), inset 0 1px 0 hsl(var(--foreground) / 0.07), 0 1px 2px hsl(var(--foreground) / 0.10), 0 12px 32px -14px hsl(var(--foreground) / 0.40)`
1702
- };
1703
- case "running":
1704
- case "waiting": {
1705
- const color = STATUS_COLOR[status];
1706
- return {
1707
- borderColor: color,
1708
- boxShadow: `0 0 0 1px color-mix(in srgb, ${color} 45%, transparent), 0 0 24px -6px ${color}`
1709
- };
1710
- }
1711
- case "queued": return { borderColor: "hsl(var(--border))" };
1712
- case "skipped": return {
1713
- borderColor: "hsl(var(--muted-foreground) / 0.45)",
1714
- borderStyle: "dashed",
1715
- opacity: .5
1716
- };
1717
- case "succeeded": return {
1718
- borderColor: RESTING_BORDER,
1719
- boxShadow: RESTING_SHADOW
1720
- };
1721
- }
1319
+ return {
1320
+ borderColor: status === "failed" || status === "waiting" ? STATUS_COLOR[status] : RESTING_BORDER,
1321
+ boxShadow: RESTING_SHADOW
1322
+ };
1722
1323
  }
1723
1324
  /** Determinate progress fraction for a node's status: queued reads near-empty,
1724
1325
  * running and waiting a partial (the run reached this node but has not finished
@@ -1879,25 +1480,15 @@ const STATUS_TONE = {
1879
1480
  * tone and label as every other status in the product. `shrink-0` because the
1880
1481
  * longest label is two words ("Waiting on you") and the pill is often nested a
1881
1482
  * level below the flex row it sits in. */
1882
- function StatusPill$1({ status }) {
1883
- if (status === "succeeded") return /* @__PURE__ */ jsxs("span", {
1884
- className: "inline-flex shrink-0 items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-medium leading-4",
1885
- style: {
1886
- background: "color-mix(in srgb, hsl(var(--muted-foreground)) 10%, transparent)",
1887
- color: "hsl(var(--muted-foreground))",
1888
- borderColor: "color-mix(in srgb, hsl(var(--muted-foreground)) 26%, transparent)"
1889
- },
1890
- children: [/* @__PURE__ */ jsx(Check, {
1891
- className: "size-2.5",
1892
- strokeWidth: 3,
1483
+ function StatusPill({ status }) {
1484
+ return /* @__PURE__ */ jsxs("span", {
1485
+ className: "inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs",
1486
+ style: { color: STATUS_COLOR[status] },
1487
+ children: [/* @__PURE__ */ jsx(RunStatusIcon, {
1488
+ status: workflowStatus(status),
1893
1489
  "aria-hidden": true
1894
1490
  }), STATUS_LABEL[status]]
1895
1491
  });
1896
- return /* @__PURE__ */ jsx(StatusPill, {
1897
- tone: STATUS_TONE[status],
1898
- className: "shrink-0",
1899
- children: STATUS_LABEL[status]
1900
- });
1901
1492
  }
1902
1493
  /**
1903
1494
  * The run status FOOTER: a progress bar (queued = near-empty, running = pulsing
@@ -1992,749 +1583,4 @@ function NodeMark({ kind, provider, model, accent, tile }) {
1992
1583
  })
1993
1584
  });
1994
1585
  }
1995
- // src/workflows/agent-hub-parts.tsx
1996
- /**
1997
- * What the `AgentGraph` hub layout draws: a node (a brand-marked tile with its
1998
- * name and model under it), the status badge on its corner, and the popover of
1999
- * its recent calls. Status reads as a glyph and a word as well as a colour.
2000
- */
2001
- /** One colour per call kind, shared by edges, the legend and popover rows. */
2002
- const CALL_COLOR = {
2003
- call: "var(--agent-graph-call, hsl(217 91% 60%))",
2004
- answer: `var(--agent-graph-answer, ${STATUS_COLOR.succeeded})`,
2005
- "needs-you": `var(--agent-graph-needs-you, ${STATUS_COLOR.waiting})`,
2006
- failed: `var(--agent-graph-failed, ${STATUS_COLOR.failed})`
2007
- };
2008
- const CALL_LABEL = {
2009
- call: "Call",
2010
- answer: "Answer",
2011
- "needs-you": "Needs you",
2012
- failed: "Failed"
2013
- };
2014
- function NodeIcon({ entity, size }) {
2015
- if (entity.kind === "you") return /* @__PURE__ */ jsx(User, {
2016
- size: size * .42,
2017
- strokeWidth: 2.2,
2018
- "aria-hidden": true
2019
- });
2020
- if (entity.kind === "tool") {
2021
- const provider = entity.tool?.provider;
2022
- if (!provider) return /* @__PURE__ */ jsx(Wrench, {
2023
- size: size * .4,
2024
- "aria-hidden": true
2025
- });
2026
- return /* @__PURE__ */ jsx(ProviderIcon, {
2027
- id: provider,
2028
- displayName: entity.label,
2029
- size: Math.round(size * .46),
2030
- className: "rounded-[4px]"
2031
- });
2032
- }
2033
- const brand = entity.agent?.model ? modelBrandFor(entity.agent.model) : null;
2034
- return brand ? /* @__PURE__ */ jsx(ModelBrandStack, {
2035
- identity: brand,
2036
- size: "sm"
2037
- }) : /* @__PURE__ */ jsx(Bot, {
2038
- size: size * .44,
2039
- "aria-hidden": true
2040
- });
2041
- }
2042
- /** The corner badge: done, needs you, failed or running, each with its own glyph. */
2043
- function StatusBadge({ status }) {
2044
- if (status === "succeeded") return /* @__PURE__ */ jsx("span", {
2045
- className: "ag-badge",
2046
- style: { background: STATUS_COLOR.succeeded },
2047
- children: /* @__PURE__ */ jsx(Check, {
2048
- size: 9,
2049
- strokeWidth: 3
2050
- })
2051
- });
2052
- if (status === "needs-you") return /* @__PURE__ */ jsx("span", {
2053
- className: "ag-badge",
2054
- style: { background: STATUS_COLOR.waiting },
2055
- children: /* @__PURE__ */ jsx(Clock3, {
2056
- size: 9,
2057
- strokeWidth: 3
2058
- })
2059
- });
2060
- if (status === "failed") return /* @__PURE__ */ jsx("span", {
2061
- className: "ag-badge",
2062
- style: { background: STATUS_COLOR.failed },
2063
- children: "!"
2064
- });
2065
- if (status === "running") return /* @__PURE__ */ jsx("span", {
2066
- className: "ag-badge ag-badge-running",
2067
- children: /* @__PURE__ */ jsx(LoaderCircle, {
2068
- size: 11,
2069
- strokeWidth: 2.5
2070
- })
2071
- });
2072
- return null;
2073
- }
2074
- function HubNode({ entity, x, y, tile, dimmed, selected, buttonRef, onOpen, onHover, onKeyDown }) {
2075
- const tone = entity.status === "needs-you" ? STATUS_COLOR.waiting : entity.status === "failed" ? STATUS_COLOR.failed : void 0;
2076
- const subTone = entity.status === "needs-you" ? STATUS_COLOR.waiting : entity.status === "failed" ? STATUS_COLOR.failed : void 0;
2077
- const sub = entity.status === "needs-you" ? "Needs you" : entity.status === "failed" ? "Error" : entity.sub;
2078
- return /* @__PURE__ */ jsxs("button", {
2079
- ref: buttonRef,
2080
- type: "button",
2081
- "data-agent-graph-node": entity.id,
2082
- "data-kind": entity.kind,
2083
- "aria-label": `${entity.label}${entity.sub ? `, ${entity.sub}` : ""}${entity.status ? `, ${entity.status === "needs-you" ? "needs you" : entity.status}` : ""}`,
2084
- "aria-expanded": selected,
2085
- className: `ag-node ag-node-${entity.kind}${dimmed ? " ag-dimmed" : ""}${selected ? " ag-selected" : ""}`,
2086
- style: {
2087
- left: x,
2088
- top: y,
2089
- "--ag-tile": `${tile}px`,
2090
- ...tone ? { "--ag-tone": tone } : {}
2091
- },
2092
- onClick: onOpen,
2093
- onPointerEnter: () => onHover(true),
2094
- onPointerLeave: () => onHover(false),
2095
- onFocus: () => onHover(true),
2096
- onBlur: () => onHover(false),
2097
- onKeyDown,
2098
- children: [
2099
- /* @__PURE__ */ jsxs("span", {
2100
- className: "ag-tile",
2101
- children: [/* @__PURE__ */ jsx(NodeIcon, {
2102
- entity,
2103
- size: tile
2104
- }), /* @__PURE__ */ jsx(StatusBadge, { status: entity.status })]
2105
- }),
2106
- /* @__PURE__ */ jsx("span", {
2107
- className: "ag-name",
2108
- children: entity.label
2109
- }),
2110
- sub && /* @__PURE__ */ jsx("span", {
2111
- className: "ag-sub",
2112
- style: subTone ? { color: subTone } : void 0,
2113
- children: sub
2114
- })
2115
- ]
2116
- });
2117
- }
2118
- const fmtMs = (ms) => ms < 1e3 ? `${Math.round(ms / 100) * 100 || Math.round(ms)}ms` : `${(ms / 1e3).toFixed(ms < 1e4 ? 1 : 0)}s`;
2119
- /** A node's recent calls: who it called or was called by, what for, how long, and what failed. */
2120
- function HubPopover({ entity, entities, calls, now, left, top, summary, closeRef, onClose }) {
2121
- const mine = calls.filter((c) => (c.from === entity.id || c.to === entity.id) && c.startedAt <= now).sort((a, b) => b.startedAt - a.startedAt);
2122
- const recent = mine.slice(0, 5);
2123
- const lastMinute = mine.filter((c) => now - c.startedAt <= 6e4).length;
2124
- const status = entity.status === "needs-you" ? "Needs you" : entity.status === "failed" ? "Error" : entity.status === "running" ? "Running" : entity.status === "succeeded" ? "Done" : void 0;
2125
- const cost = entity.agent?.costUsd;
2126
- return /* @__PURE__ */ jsxs("div", {
2127
- role: "dialog",
2128
- "aria-label": `${entity.label} calls`,
2129
- className: "ag-popover",
2130
- style: {
2131
- left,
2132
- top
2133
- },
2134
- onKeyDown: (event) => {
2135
- if (event.key === "Escape") {
2136
- event.stopPropagation();
2137
- onClose();
2138
- }
2139
- },
2140
- children: [
2141
- /* @__PURE__ */ jsxs("div", {
2142
- className: "ag-pop-head",
2143
- children: [
2144
- /* @__PURE__ */ jsx("span", {
2145
- className: "ag-tile ag-tile-mini",
2146
- style: { "--ag-tile": "28px" },
2147
- children: /* @__PURE__ */ jsx(NodeIcon, {
2148
- entity,
2149
- size: 28
2150
- })
2151
- }),
2152
- /* @__PURE__ */ jsxs("span", {
2153
- className: "ag-pop-title",
2154
- children: [/* @__PURE__ */ jsx("span", {
2155
- className: "ag-pop-name",
2156
- children: entity.label
2157
- }), (entity.sub || entity.tool?.detail) && /* @__PURE__ */ jsx("span", {
2158
- className: "ag-pop-sub",
2159
- children: entity.tool?.detail ?? entity.sub
2160
- })]
2161
- }),
2162
- status && /* @__PURE__ */ jsx("span", {
2163
- className: `ag-pill ag-pill-${entity.status}`,
2164
- children: status
2165
- }),
2166
- /* @__PURE__ */ jsx("button", {
2167
- ref: closeRef,
2168
- type: "button",
2169
- className: "ag-close",
2170
- "aria-label": "Close",
2171
- onClick: onClose,
2172
- children: /* @__PURE__ */ jsx(X, { size: 14 })
2173
- })
2174
- ]
2175
- }),
2176
- summary && /* @__PURE__ */ jsx("p", {
2177
- className: "ag-pop-summary",
2178
- children: summary
2179
- }),
2180
- recent.length > 0 && /* @__PURE__ */ jsx("ul", {
2181
- className: "ag-pop-calls",
2182
- children: recent.map((c) => {
2183
- const outgoing = c.from === entity.id;
2184
- const other = entities.get(outgoing ? c.to : c.from);
2185
- const running = c.durationMs == null;
2186
- return /* @__PURE__ */ jsxs("li", { children: [
2187
- /* @__PURE__ */ jsx("span", {
2188
- className: "ag-pop-dir",
2189
- "aria-label": outgoing ? "to" : "from",
2190
- children: outgoing ? "→" : "←"
2191
- }),
2192
- other && /* @__PURE__ */ jsx("span", {
2193
- className: "ag-tile ag-tile-micro",
2194
- style: { "--ag-tile": "16px" },
2195
- children: /* @__PURE__ */ jsx(NodeIcon, {
2196
- entity: other,
2197
- size: 16
2198
- })
2199
- }),
2200
- /* @__PURE__ */ jsxs("span", {
2201
- className: "ag-pop-call",
2202
- children: [/* @__PURE__ */ jsx("b", { children: other?.label ?? (outgoing ? c.to : c.from) }), c.label ? ` · ${c.label}` : ""]
2203
- }),
2204
- /* @__PURE__ */ jsx("span", {
2205
- className: "ag-pop-time",
2206
- style: c.kind === "failed" ? { color: CALL_COLOR.failed } : void 0,
2207
- children: c.kind === "failed" ? "Failed" : running ? /* @__PURE__ */ jsx(LoaderCircle, {
2208
- size: 12,
2209
- className: "ag-spin",
2210
- "aria-label": "running"
2211
- }) : fmtMs(c.durationMs)
2212
- })
2213
- ] }, c.id);
2214
- })
2215
- }),
2216
- /* @__PURE__ */ jsxs("p", {
2217
- className: "ag-pop-foot",
2218
- children: [
2219
- cost != null && /* @__PURE__ */ jsx("b", { children: fmtCost(cost) }),
2220
- lastMinute,
2221
- " ",
2222
- lastMinute === 1 ? "call" : "calls",
2223
- " in the last minute"
2224
- ]
2225
- })
2226
- ]
2227
- });
2228
- }
2229
- // src/workflows/canvas-theme.ts
2230
- /** Motion is opt-OUT, so it is only used where the reader's preference can be
2231
- * read and says nothing against it. Somewhere without `matchMedia` cannot say
2232
- * a reader tolerates movement, so it gets none. */
2233
- function motionAllowed() {
2234
- return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === false;
2235
- }
2236
- /** React Flow adds its own .light/.dark class below the graph wrapper. Read
2237
- * that wrapper's inherited scheme so a nested WorkspaceLayout theme wins over
2238
- * the page root without React Flow's class influencing the answer. */
2239
- function currentColorMode(anchor) {
2240
- if (typeof document === "undefined" || typeof window === "undefined") return "light";
2241
- const scheme = window.getComputedStyle(anchor ?? document.documentElement).colorScheme;
2242
- if (scheme.startsWith("dark")) return "dark";
2243
- if (scheme.startsWith("light")) return "light";
2244
- return (anchor ?? document.documentElement).closest(".dark") ? "dark" : "light";
2245
- }
2246
- function useColorMode(wrapperRef, hasGraph) {
2247
- const [mode, setMode] = useState(() => currentColorMode(null));
2248
- useLayoutEffect(() => {
2249
- const anchor = hasGraph ? wrapperRef.current : null;
2250
- const update = () => setMode(currentColorMode(anchor));
2251
- update();
2252
- const observer = new MutationObserver(update);
2253
- for (let el = anchor ?? document.documentElement; el; el = el.parentElement) observer.observe(el, {
2254
- attributes: true,
2255
- attributeFilter: [
2256
- "class",
2257
- "data-theme",
2258
- "data-sandbox-theme",
2259
- "style"
2260
- ]
2261
- });
2262
- return () => observer.disconnect();
2263
- }, [wrapperRef, hasGraph]);
2264
- return mode;
2265
- }
2266
- /** The selected node's ring. An `outline` rather than a border or a box-shadow
2267
- * because both of those are already spoken for — the border carries tone and
2268
- * run status, the shadow carries the running/waiting glow — and an outline
2269
- * composes with both without disturbing the laid-out box. */
2270
- const SELECTION_OUTLINE = {
2271
- outline: "2px solid hsl(var(--primary))",
2272
- outlineOffset: 2
2273
- };
2274
- // src/workflows/AgentHubCanvas.tsx
2275
- /**
2276
- * The hub layout of `AgentGraph`: who asked (You), the orchestrator, the agents
2277
- * it runs and the tools they call, with each call travelling along its edge in
2278
- * its kind's colour. Plain DOM and SVG on a fixed grid, so it renders on the
2279
- * server, scales to its container, and costs no graph library.
2280
- */
2281
- /** A call shows on its edge while it runs and for this long after it ends. */
2282
- const ACTIVE_MS = 2600;
2283
- const PAD_X = 84;
2284
- const PAD_Y = 36;
2285
- function useWidth(ref) {
2286
- const [width, setWidth] = useState(0);
2287
- useLayoutEffect(() => {
2288
- const el = ref.current;
2289
- if (!el) return;
2290
- setWidth(el.clientWidth);
2291
- const observer = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));
2292
- observer.observe(el);
2293
- return () => observer.disconnect();
2294
- }, [ref]);
2295
- return width;
2296
- }
2297
- function useNow(now) {
2298
- const [tick, setTick] = useState(() => Date.now());
2299
- useEffect(() => {
2300
- if (now !== void 0) return;
2301
- const timer = setInterval(() => setTick(Date.now()), 500);
2302
- return () => clearInterval(timer);
2303
- }, [now]);
2304
- return now ?? tick;
2305
- }
2306
- function AgentHubCanvas({ nodes, edges, tools = [], calls = [], you, now: nowProp, title = "Agent graph", selectedId, onSelect, className }) {
2307
- const wrap = useRef(null);
2308
- const width = useWidth(wrap);
2309
- const now = useNow(nowProp);
2310
- const [hover, setHover] = useState(null);
2311
- const [open, setOpen] = useState(selectedId ?? null);
2312
- const buttons = useRef(/* @__PURE__ */ new Map());
2313
- const closeRef = useRef(null);
2314
- const animate = useMemo(() => motionAllowed(), []);
2315
- const visibleCalls = useMemo(() => calls.filter((c) => c.startedAt <= now), [calls, now]);
2316
- const layout = useMemo(() => layoutHub({
2317
- nodes,
2318
- edges,
2319
- tools,
2320
- calls,
2321
- you: Boolean(you)
2322
- }), [
2323
- nodes,
2324
- edges,
2325
- tools,
2326
- calls,
2327
- you
2328
- ]);
2329
- const span = (m) => ({
2330
- w: (layout.maxCol - layout.minCol) * m.cellX + 168,
2331
- h: (layout.maxRow - layout.minRow) * m.cellY + 72 + m.hubTile + m.label
2332
- });
2333
- const metrics = width && width / span(HUB_METRICS).w < .75 ? HUB_METRICS_COMPACT : HUB_METRICS;
2334
- const { w, h } = span(metrics);
2335
- const scale = width ? Math.min(1, width / w) : 1;
2336
- const entities = useMemo(() => {
2337
- const map = /* @__PURE__ */ new Map();
2338
- for (const p of layout.placements) if (p.kind === "you") map.set(p.id, {
2339
- id: p.id,
2340
- kind: "you",
2341
- label: typeof you === "object" && you.label ? you.label : "You"
2342
- });
2343
- else if (p.kind === "tool") {
2344
- const tool = tools.find((t) => t.id === p.id);
2345
- map.set(p.id, {
2346
- id: p.id,
2347
- kind: "tool",
2348
- label: tool.label,
2349
- tool
2350
- });
2351
- } else {
2352
- const agent = nodes.find((n) => n.id === p.id);
2353
- map.set(p.id, {
2354
- id: p.id,
2355
- kind: p.kind,
2356
- label: agent.label,
2357
- sub: agent.model ? shortModel(agent.model) : void 0,
2358
- status: agent.status,
2359
- agent
2360
- });
2361
- }
2362
- return map;
2363
- }, [
2364
- layout,
2365
- nodes,
2366
- tools,
2367
- you
2368
- ]);
2369
- const at = (id) => {
2370
- const p = layout.placements.find((q) => q.id === id);
2371
- return {
2372
- x: (p.col - layout.minCol) * metrics.cellX + PAD_X,
2373
- y: (p.row - layout.minRow) * metrics.cellY + PAD_Y + metrics.hubTile / 2
2374
- };
2375
- };
2376
- const edgeState = (from, to) => {
2377
- let latest;
2378
- for (const c of visibleCalls) if (c.from === from && c.to === to || c.from === to && c.to === from) {
2379
- if (!latest || c.startedAt > latest.startedAt) latest = c;
2380
- }
2381
- if (!latest) return void 0;
2382
- const end = latest.durationMs == null ? Infinity : latest.startedAt + latest.durationMs;
2383
- return now - end <= ACTIVE_MS ? latest : void 0;
2384
- };
2385
- const focus = hover ?? open;
2386
- const related = useMemo(() => {
2387
- if (!focus) return null;
2388
- const set = /* @__PURE__ */ new Set([focus]);
2389
- for (const l of layout.links) {
2390
- if (l.from === focus) set.add(l.to);
2391
- if (l.to === focus) set.add(l.from);
2392
- }
2393
- return set;
2394
- }, [focus, layout]);
2395
- const agents = nodes.length;
2396
- const lastMinute = visibleCalls.filter((c) => now - c.startedAt <= 6e4).length;
2397
- const costs = nodes.map((n) => n.costUsd);
2398
- const cost = costs.every((c) => c != null) ? costs.reduce((s, c) => s + (c ?? 0), 0) : void 0;
2399
- const chips = nodes.filter((n) => n.status === "needs-you" || n.status === "failed");
2400
- const openNode = (id) => {
2401
- const next = open === id ? null : id;
2402
- setOpen(next);
2403
- if (next) onSelect?.(next);
2404
- };
2405
- useEffect(() => {
2406
- if (open) closeRef.current?.focus({ preventScroll: true });
2407
- }, [open]);
2408
- const keydown = (id) => (event) => {
2409
- const dir = {
2410
- ArrowLeft: [-1, 0],
2411
- ArrowRight: [1, 0],
2412
- ArrowUp: [0, -1],
2413
- ArrowDown: [0, 1]
2414
- }[event.key];
2415
- if (event.key === "Escape") {
2416
- setOpen(null);
2417
- return;
2418
- }
2419
- if (!dir) return;
2420
- event.preventDefault();
2421
- const from = at(id);
2422
- let best, bestScore = Infinity;
2423
- for (const p of layout.placements) {
2424
- if (p.id === id) continue;
2425
- const to = at(p.id);
2426
- const dx = to.x - from.x, dy = to.y - from.y;
2427
- const along = dx * dir[0] + dy * dir[1];
2428
- if (along <= 0) continue;
2429
- const score = along + Math.abs(dx * dir[1] - dy * dir[0]) * 2;
2430
- if (score < bestScore) {
2431
- bestScore = score;
2432
- best = p.id;
2433
- }
2434
- }
2435
- if (best) buttons.current.get(best)?.focus();
2436
- };
2437
- const openEntity = open ? entities.get(open) : void 0;
2438
- const popover = openEntity && (() => {
2439
- const p = at(openEntity.id);
2440
- const left = (p.x + 140 + 280) * scale < (width || w) ? (p.x + 40) * scale : Math.max(8, (p.x - 40) * scale - 280);
2441
- const top = Math.max(8, (p.y - metrics.hubTile / 2 - 8) * scale);
2442
- const summary = openEntity.kind === "you" ? typeof you === "object" ? you.summary : void 0 : openEntity.agent?.turn ?? void 0;
2443
- return /* @__PURE__ */ jsx(HubPopover, {
2444
- entity: openEntity,
2445
- entities,
2446
- calls: visibleCalls,
2447
- now,
2448
- left,
2449
- top,
2450
- summary,
2451
- closeRef,
2452
- onClose: () => {
2453
- const id = open;
2454
- setOpen(null);
2455
- buttons.current.get(id)?.focus();
2456
- }
2457
- });
2458
- })();
2459
- const lanes = /* @__PURE__ */ new Map();
2460
- const laneOf = (node) => {
2461
- const n = lanes.get(node) ?? 0;
2462
- lanes.set(node, n + 1);
2463
- return (n % 2 ? 1 : -1) * Math.ceil(n / 2) * 6;
2464
- };
2465
- return /* @__PURE__ */ jsxs("section", {
2466
- className: `ag-root ${className ?? ""}`,
2467
- "aria-label": typeof title === "string" ? title : "Agent graph",
2468
- children: [
2469
- /* @__PURE__ */ jsx("style", { children: AGENT_HUB_CSS }),
2470
- /* @__PURE__ */ jsxs("header", {
2471
- className: "ag-head",
2472
- children: [
2473
- /* @__PURE__ */ jsxs("span", {
2474
- className: "ag-title",
2475
- children: [/* @__PURE__ */ jsx("span", {
2476
- className: "ag-title-mark",
2477
- "aria-hidden": true
2478
- }), title]
2479
- }),
2480
- chips.map((n) => /* @__PURE__ */ jsxs("span", {
2481
- className: `ag-chip ag-chip-${n.status}`,
2482
- children: [
2483
- n.status === "failed" ? "!" : "◷",
2484
- " ",
2485
- n.label,
2486
- " ",
2487
- n.status === "failed" ? "failed" : "needs you"
2488
- ]
2489
- }, n.id)),
2490
- /* @__PURE__ */ jsxs("span", {
2491
- className: "ag-stats",
2492
- children: [
2493
- /* @__PURE__ */ jsxs("span", { children: [
2494
- /* @__PURE__ */ jsx("b", { children: agents }),
2495
- " ",
2496
- agents === 1 ? "agent" : "agents"
2497
- ] }),
2498
- /* @__PURE__ */ jsxs("span", { children: [
2499
- /* @__PURE__ */ jsx("b", { children: tools.length }),
2500
- " ",
2501
- tools.length === 1 ? "tool" : "tools"
2502
- ] }),
2503
- /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("b", { children: lastMinute }), " calls/min"] }),
2504
- /* @__PURE__ */ jsxs("span", {
2505
- title: cost === void 0 ? "Not every agent recorded its cost" : void 0,
2506
- children: [/* @__PURE__ */ jsx("b", { children: cost === void 0 ? "Unknown" : fmtCost(cost) }), cost === void 0 ? " cost" : ""]
2507
- })
2508
- ]
2509
- })
2510
- ]
2511
- }),
2512
- /* @__PURE__ */ jsxs("div", {
2513
- ref: wrap,
2514
- className: "ag-canvas-wrap",
2515
- style: { height: h * scale },
2516
- onKeyDown: (e) => {
2517
- if (e.key === "Escape") setOpen(null);
2518
- },
2519
- children: [/* @__PURE__ */ jsxs("div", {
2520
- className: "ag-canvas",
2521
- style: {
2522
- width: w,
2523
- height: h,
2524
- transform: `scale(${scale})`
2525
- },
2526
- children: [/* @__PURE__ */ jsx("svg", {
2527
- className: "ag-edges",
2528
- width: w,
2529
- height: h,
2530
- "aria-hidden": true,
2531
- children: layout.links.map((l) => {
2532
- const a = at(l.from), b = at(l.to);
2533
- const fromHub = layout.placements.find((p) => p.id === l.from)?.kind === "hub";
2534
- const sameCol = Math.abs(a.x - b.x) < 1;
2535
- const { d, length } = orthogonalPath(a, b, l.from !== "you" && (fromHub || sameCol), fromHub ? laneOf(l.from) : 0);
2536
- const live = edgeState(l.from, l.to);
2537
- const touches = !related || related.has(l.from) && related.has(l.to);
2538
- const forward = live ? live.from === l.from : true;
2539
- return /* @__PURE__ */ jsxs("g", {
2540
- className: touches ? void 0 : "ag-dimmed",
2541
- children: [/* @__PURE__ */ jsx("path", {
2542
- d,
2543
- className: "ag-edge"
2544
- }), live && /* @__PURE__ */ jsx("path", {
2545
- d,
2546
- className: `ag-edge-live${animate && live.kind !== "failed" ? " ag-flow" : ""}`,
2547
- style: {
2548
- stroke: CALL_COLOR[live.kind],
2549
- "--ag-len": `${length}px`,
2550
- animationDirection: forward ? "normal" : "reverse"
2551
- }
2552
- })]
2553
- }, `${l.from}-${l.to}`);
2554
- })
2555
- }), layout.placements.map((p) => {
2556
- const entity = entities.get(p.id);
2557
- const { x, y } = at(p.id);
2558
- const tile = p.kind === "hub" ? metrics.hubTile : p.kind === "you" ? metrics.tile - 8 : metrics.tile;
2559
- return /* @__PURE__ */ jsx(HubNode, {
2560
- entity,
2561
- x,
2562
- y,
2563
- tile,
2564
- dimmed: Boolean(related && !related.has(p.id)),
2565
- selected: open === p.id,
2566
- buttonRef: (el) => {
2567
- if (el) buttons.current.set(p.id, el);
2568
- else buttons.current.delete(p.id);
2569
- },
2570
- onOpen: () => openNode(p.id),
2571
- onHover: (on) => setHover((prev) => on ? p.id : prev === p.id ? null : prev),
2572
- onKeyDown: keydown(p.id)
2573
- }, p.id);
2574
- })]
2575
- }), popover]
2576
- }),
2577
- /* @__PURE__ */ jsx("ul", {
2578
- className: "ag-legend",
2579
- "aria-label": "Edge colours",
2580
- children: [
2581
- "call",
2582
- "answer",
2583
- "needs-you",
2584
- "failed"
2585
- ].map((k) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
2586
- className: "ag-legend-line",
2587
- style: { background: CALL_COLOR[k] }
2588
- }), CALL_LABEL[k]] }, k))
2589
- })
2590
- ]
2591
- });
2592
- }
2593
- // src/workflows/WorkflowGraphLazy.tsx
2594
- /**
2595
- * Lazy + error-isolated boundary for {@link WorkflowGraph}. React Flow (~50KB)
2596
- * is only needed on the workflow detail page and on workflow proposal cards, so
2597
- * it's code-split out of the always-loaded app shell. The Suspense fallback
2598
- * keeps the caller's height so layout doesn't jump; the error boundary degrades
2599
- * to the raw YAML if the chunk fails
2600
- * to load (offline) or React Flow throws, instead of crashing the panel tree.
2601
- *
2602
- * The chunk import auto-retries a transient failure (a flaky network or an
2603
- * HMR/cache miss) before degrading, and the fallback offers a manual retry that
2604
- * re-attempts the import — so a recoverable failure isn't permanently pinned to
2605
- * the YAML view. A chunk that is gone for good (e.g. an old hash after a deploy)
2606
- * still degrades to the fully-legible YAML; the host can recover those globally
2607
- * via a Vite `vite:preloadError` reload guard.
2608
- */
2609
- /** Re-run `factory` up to `retries` times with a short backoff before giving up.
2610
- * Covers a transient dynamic-import rejection (network blip, a chunk briefly
2611
- * unavailable behind a CDN) without bothering the user. Exported for tests. */
2612
- function retryImport(factory, retries = 2, delayMs = 350) {
2613
- return factory().catch((err) => {
2614
- if (retries <= 0) throw err;
2615
- return new Promise((resolve, reject) => {
2616
- setTimeout(() => retryImport(factory, retries - 1, delayMs).then(resolve, reject), delayMs);
2617
- });
2618
- });
2619
- }
2620
- function lazyWorkflowGraph() {
2621
- return lazy(() => retryImport(() => import("./WorkflowGraph-DZ3xejKE.js").then((m) => ({ default: m.WorkflowGraph }))));
2622
- }
2623
- var GraphErrorBoundary = class extends Component {
2624
- state = { hasError: false };
2625
- static getDerivedStateFromError() {
2626
- return { hasError: true };
2627
- }
2628
- render() {
2629
- return this.state.hasError ? this.props.fallback : this.props.children;
2630
- }
2631
- };
2632
- /** Graceful degradation when the graph can't render: the workflow is still
2633
- * fully legible as YAML, with a retry for a recoverable load failure. */
2634
- function GraphFallback({ yaml, className, onRetry }) {
2635
- return /* @__PURE__ */ jsxs("div", {
2636
- className: `overflow-auto rounded-lg border border-border bg-background p-2 ${className ?? ""}`,
2637
- children: [/* @__PURE__ */ jsxs("div", {
2638
- className: "mb-1 flex items-center justify-between gap-2",
2639
- children: [/* @__PURE__ */ jsx("p", {
2640
- className: "text-muted-foreground text-xs",
2641
- children: "Couldn't render the graph — showing the definition."
2642
- }), /* @__PURE__ */ jsx("button", {
2643
- type: "button",
2644
- onClick: onRetry,
2645
- className: "shrink-0 rounded border border-border px-1.5 py-0.5 text-muted-foreground text-xs transition hover:text-foreground",
2646
- children: "Retry"
2647
- })]
2648
- }), /* @__PURE__ */ jsx("pre", {
2649
- className: "text-foreground text-xs",
2650
- children: /* @__PURE__ */ jsx("code", { children: yaml })
2651
- })]
2652
- });
2653
- }
2654
- function WorkflowGraph(props) {
2655
- const [attempt, setAttempt] = useState(0);
2656
- const Impl = useMemo(() => lazyWorkflowGraph(), [attempt]);
2657
- return /* @__PURE__ */ jsx(GraphErrorBoundary, {
2658
- fallback: /* @__PURE__ */ jsx(GraphFallback, {
2659
- ...props,
2660
- onRetry: () => setAttempt((a) => a + 1)
2661
- }),
2662
- children: /* @__PURE__ */ jsx(Suspense, {
2663
- fallback: /* @__PURE__ */ jsx("div", {
2664
- className: `flex items-center justify-center rounded-lg border border-border border-dashed text-text-muted text-xs ${props.className ?? ""}`,
2665
- children: "Loading graph…"
2666
- }),
2667
- children: /* @__PURE__ */ jsx(Impl, { ...props })
2668
- })
2669
- }, `${props.yaml}::${attempt}`);
2670
- }
2671
- // src/workflows/AgentGraph.tsx
2672
- /**
2673
- * `AgentGraph` — the topology of a multi-agent run, for a run page's side pane.
2674
- *
2675
- * Lazy and error-isolated like `WorkflowGraph`: React Flow and ELK load on
2676
- * first render, a transient chunk failure retries, and a graph that cannot
2677
- * render degrades to a plain list of the agents (still selectable) instead of
2678
- * taking the pane down.
2679
- */
2680
- function lazyCanvas() {
2681
- return lazy(() => retryImport(() => import("./AgentGraphCanvas-CMdOlSlh.js").then((m) => ({ default: m.AgentGraphCanvas }))));
2682
- }
2683
- /** The agents as a list, for when the canvas cannot load. */
2684
- function AgentListFallback({ nodes, selectedId, onSelect, className, onRetry }) {
2685
- return /* @__PURE__ */ jsxs("div", {
2686
- className: `overflow-auto rounded-lg border border-border bg-background p-2 ${className ?? ""}`,
2687
- children: [/* @__PURE__ */ jsxs("div", {
2688
- className: "mb-1 flex items-center justify-between gap-2",
2689
- children: [/* @__PURE__ */ jsx("p", {
2690
- className: "text-muted-foreground text-xs",
2691
- children: "Couldn't draw the graph — showing the agents."
2692
- }), /* @__PURE__ */ jsx("button", {
2693
- type: "button",
2694
- onClick: onRetry,
2695
- className: "shrink-0 rounded border border-border px-1.5 py-0.5 text-muted-foreground text-xs transition hover:text-foreground",
2696
- children: "Retry"
2697
- })]
2698
- }), /* @__PURE__ */ jsx("ul", {
2699
- className: "space-y-0.5",
2700
- children: nodes.map((n) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
2701
- type: "button",
2702
- "aria-pressed": selectedId === n.id,
2703
- onClick: () => onSelect?.(n.id),
2704
- className: "flex w-full items-center justify-between gap-2 rounded px-2 py-1 text-left text-foreground text-xs hover:bg-muted aria-pressed:bg-muted",
2705
- children: [/* @__PURE__ */ jsx("span", {
2706
- className: "truncate",
2707
- children: n.label
2708
- }), /* @__PURE__ */ jsx("span", {
2709
- className: "shrink-0 text-muted-foreground",
2710
- children: n.status
2711
- })]
2712
- }) }, n.id))
2713
- })]
2714
- });
2715
- }
2716
- function AgentGraph(props) {
2717
- if (props.layout === "hub") {
2718
- if (props.nodes.length === 0 && props.emptyState) return /* @__PURE__ */ jsx(Fragment, { children: props.emptyState });
2719
- return /* @__PURE__ */ jsx(AgentHubCanvas, { ...props });
2720
- }
2721
- return /* @__PURE__ */ jsx(AgentGraphTree, { ...props });
2722
- }
2723
- function AgentGraphTree(props) {
2724
- const [attempt, setAttempt] = useState(0);
2725
- const Canvas = useMemo(() => lazyCanvas(), [attempt]);
2726
- return /* @__PURE__ */ jsx(GraphErrorBoundary, {
2727
- fallback: /* @__PURE__ */ jsx(AgentListFallback, {
2728
- ...props,
2729
- onRetry: () => setAttempt((a) => a + 1)
2730
- }),
2731
- children: /* @__PURE__ */ jsx(Suspense, {
2732
- fallback: /* @__PURE__ */ jsx("div", {
2733
- className: `flex items-center justify-center rounded-lg border border-border border-dashed text-muted-foreground text-xs ${props.className ?? ""}`,
2734
- children: "Loading graph…"
2735
- }),
2736
- children: /* @__PURE__ */ jsx(Canvas, { ...props })
2737
- })
2738
- }, attempt);
2739
- }
2740
- export { buildWorkflowGraph as A, clampPreview as B, problemSurface as C, TRIGGER_NODE_ID as D, COMPACT_NODE_SIZE as E, worstSeverity as F, fmtDuration as H, actionPathLabel as I, humanizeIdentifier as L, triggerNodeId as M, triggerNodeIndex as N, actionNodeId as O, wfEdgeId as P, parseActionPath as R, problemBorder as S, statusBorder as T, fmtTokenTotal as U, fmtCost as V, fmtTokens as W, StatusFooter as _, useColorMode as a, emptySlotLabel as b, NodeMark as c, RESTING_BORDER as d, RESTING_SHADOW as f, STATUS_TONE as g, STATUS_PILL as h, motionAllowed as i, isStructuralKind as j, branchNodeId as k, ProblemMarker as l, STATUS_LABEL as m, WorkflowGraph as n, KIND_ACCENT as o, STATUS_COLOR as p, SELECTION_OUTLINE as r, KIND_ICON as s, AgentGraph as t, ProblemMessages as u, StatusPill$1 as v, problemTitle as w, kindAccent as x, edgeColor as y, shortModel as z };
1586
+ export { worstSeverity as A, actionNodeId as C, triggerNodeId as D, isStructuralKind as E, humanizeIdentifier as M, parseActionPath as N, triggerNodeIndex as O, shortModel as P, TRIGGER_NODE_ID as S, buildWorkflowGraph as T, problemBorder as _, ProblemMessages as a, statusBorder as b, STATUS_COLOR as c, STATUS_TONE as d, StatusFooter as f, kindAccent as g, emptySlotLabel as h, ProblemMarker as i, actionPathLabel as j, wfEdgeId as k, STATUS_LABEL as l, edgeColor as m, KIND_ICON as n, RESTING_BORDER as o, StatusPill as p, NodeMark as r, RESTING_SHADOW as s, KIND_ACCENT as t, STATUS_PILL as u, problemSurface as v, branchNodeId as w, COMPACT_NODE_SIZE as x, problemTitle as y };