@tangle-network/sandbox-ui 2.0.11 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{node-ui-D8dbX86X.js → AgentGraph-CqLe8WCH.js} +43 -123
- package/dist/AgentGraph-P7l-jZwo.d.ts +4 -0
- package/dist/{WorkflowGraph-BtJnA5iY.js → WorkflowGraph-s3RZ-ANZ.js} +6 -7
- package/dist/{dashboard-FMrtIe9S.js → dashboard-D3JhmRhe.js} +4 -4
- package/dist/dashboard.js +1 -1
- package/dist/globals.css +1550 -87
- package/dist/{index-BK8KKLNY.d.ts → index-CEtc2U5v.d.ts} +2 -2
- package/dist/{index-BKchjsHk.d.ts → index-q-I_XQu4.d.ts} +6 -16
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -7
- package/dist/motion-2Ine0j_s.js +1 -0
- package/dist/primitives-znfPK9NP.js +23 -0
- package/dist/primitives.d.ts +1 -1
- package/dist/primitives.js +1 -1
- package/dist/{record-D-evY-c_.js → record-Dt9gqJee.js} +67 -24
- package/dist/run/record.js +1 -1
- package/dist/{run-CJyFiBG9.js → run-DBTbnQrC.js} +7 -6
- package/dist/run.d.ts +1 -1
- package/dist/run.js +2 -3
- package/dist/styles.css +1550 -87
- package/dist/tailwind.css +1800 -79
- package/dist/{workbench-BVnZNhP2.js → workbench-D9Bm4vMq.js} +1 -1
- package/dist/workbench.js +1 -1
- package/dist/workflows.d.ts +4 -59
- package/dist/workflows.js +62 -2
- package/dist/{workspace-CdcRUl-H.js → workspace-CJnBqCqj.js} +7 -7
- package/dist/workspace.js +1 -1
- package/package.json +5 -6
- package/dist/AgentGraph-CFlI9qta.js +0 -1380
- package/dist/AgentGraph-DzdpPFEI.d.ts +0 -132
- package/dist/AgentGraphCanvas-CmxReSqa.js +0 -1403
- package/dist/canvas-theme-aqWjGbfu.js +0 -55
- package/dist/latest-events-v_vXRyd7.js +0 -169
- package/dist/motion-Bn7ESHrj.js +0 -6
- package/dist/primitives-CCdx5Hh_.js +0 -23
|
@@ -1,1403 +0,0 @@
|
|
|
1
|
-
import { t as MOTION_CONTROL } from "./motion-Bn7ESHrj.js";
|
|
2
|
-
import { d as quietActivity, f as syncAmbient, g as useRunNow, h as useMotionAllowed, i as fmtCost, m as useAttention, o as fmtEventTime, p as tokenMilliseconds, s as fmtTokenTotal, u as ambientStyle } from "./AgentGraph-CFlI9qta.js";
|
|
3
|
-
import { t as LatestEvents } from "./latest-events-v_vXRyd7.js";
|
|
4
|
-
import { P as shortModel, m as edgeColor, o as RESTING_BORDER, r as NodeMark, s as RESTING_SHADOW, t as KIND_ACCENT } from "./node-ui-D8dbX86X.js";
|
|
5
|
-
import { i as loadElk, r as useColorMode, t as SELECTION_OUTLINE } from "./canvas-theme-aqWjGbfu.js";
|
|
6
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
|
-
import { createPortal } from "react-dom";
|
|
9
|
-
import { ChevronDown, Wrench } from "lucide-react";
|
|
10
|
-
import { RunStatusIcon, statusMeta } from "@tangle-network/ui/run";
|
|
11
|
-
import { Background, BaseEdge, Controls, EdgeLabelRenderer, Handle, MarkerType, MiniMap, Position, ReactFlow, useInternalNode } from "@xyflow/react";
|
|
12
|
-
import "@xyflow/react/dist/style.css";
|
|
13
|
-
// src/workflows/agent-graph-layout.ts
|
|
14
|
-
/** Every agent card's size. Fixed, so a status or turn update never moves a card. */
|
|
15
|
-
const AGENT_CARD = {
|
|
16
|
-
width: 240,
|
|
17
|
-
height: 88
|
|
18
|
-
};
|
|
19
|
-
/** A live card: room for the header, a two-line thinking line and the last three events. Fixed for the same reason. */
|
|
20
|
-
const LIVE_AGENT_CARD = {
|
|
21
|
-
width: 296,
|
|
22
|
-
height: 164
|
|
23
|
-
};
|
|
24
|
-
const GRID_GAP = 10;
|
|
25
|
-
const FRAME_PAD = 12;
|
|
26
|
-
const LAYER_GAP = 56;
|
|
27
|
-
const SIBLING_GAP = 24;
|
|
28
|
-
function buildAgentTopology(nodes, edges) {
|
|
29
|
-
const ids = /* @__PURE__ */ new Set();
|
|
30
|
-
const agentIds = [];
|
|
31
|
-
for (const n of nodes) {
|
|
32
|
-
if (ids.has(n.id)) continue;
|
|
33
|
-
ids.add(n.id);
|
|
34
|
-
agentIds.push(n.id);
|
|
35
|
-
}
|
|
36
|
-
const seen = /* @__PURE__ */ new Set();
|
|
37
|
-
const spawns = [];
|
|
38
|
-
const children = /* @__PURE__ */ new Map();
|
|
39
|
-
const parentCount = /* @__PURE__ */ new Map();
|
|
40
|
-
for (const e of edges) {
|
|
41
|
-
if (e.kind !== "spawn" || e.from === e.to) continue;
|
|
42
|
-
if (!ids.has(e.from) || !ids.has(e.to)) continue;
|
|
43
|
-
const key = `${e.from}\u0000${e.to}`;
|
|
44
|
-
if (seen.has(key)) continue;
|
|
45
|
-
seen.add(key);
|
|
46
|
-
spawns.push({
|
|
47
|
-
from: e.from,
|
|
48
|
-
to: e.to
|
|
49
|
-
});
|
|
50
|
-
children.set(e.from, [...children.get(e.from) ?? [], e.to]);
|
|
51
|
-
parentCount.set(e.to, (parentCount.get(e.to) ?? 0) + 1);
|
|
52
|
-
}
|
|
53
|
-
const groups = [];
|
|
54
|
-
const groupOf = /* @__PURE__ */ new Map();
|
|
55
|
-
for (const parentId of agentIds) {
|
|
56
|
-
const leaves = (children.get(parentId) ?? []).filter((c) => !children.has(c) && parentCount.get(c) === 1);
|
|
57
|
-
if (leaves.length < 4) continue;
|
|
58
|
-
const id = `agent-group:${parentId}`;
|
|
59
|
-
groups.push({
|
|
60
|
-
id,
|
|
61
|
-
parentId,
|
|
62
|
-
memberIds: leaves
|
|
63
|
-
});
|
|
64
|
-
for (const leaf of leaves) groupOf.set(leaf, id);
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
key: JSON.stringify([agentIds, spawns.map((s) => [s.from, s.to])]),
|
|
68
|
-
agentIds,
|
|
69
|
-
spawns,
|
|
70
|
-
groups,
|
|
71
|
-
groupOf
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function gridSize(members, columns, card, collapsed = false) {
|
|
75
|
-
if (collapsed) return {
|
|
76
|
-
width: card.width + 24,
|
|
77
|
-
height: 80
|
|
78
|
-
};
|
|
79
|
-
const cols = Math.max(1, Math.min(columns, members));
|
|
80
|
-
const rows = Math.ceil(members / cols);
|
|
81
|
-
return {
|
|
82
|
-
width: cols * card.width + (cols - 1) * GRID_GAP + 24,
|
|
83
|
-
height: 80 + rows * card.height + (rows - 1) * GRID_GAP + FRAME_PAD
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
/** The boxes the layouter places: each ungrouped agent, and each grid as one block. */
|
|
87
|
-
function layoutUnits(topology, columns, card, collapsed) {
|
|
88
|
-
const units = [];
|
|
89
|
-
const ownerGroup = new Map(topology.groups.map((group) => [group.parentId, group.id]));
|
|
90
|
-
for (const id of topology.agentIds) if (!topology.groupOf.has(id) && !ownerGroup.has(id)) units.push({
|
|
91
|
-
id,
|
|
92
|
-
width: card.width,
|
|
93
|
-
height: card.height
|
|
94
|
-
});
|
|
95
|
-
for (const g of topology.groups) units.push({
|
|
96
|
-
id: g.id,
|
|
97
|
-
...gridSize(g.memberIds.length, columns, card, collapsed.has(g.id))
|
|
98
|
-
});
|
|
99
|
-
const unitOf = (id) => topology.groupOf.get(id) ?? ownerGroup.get(id) ?? id;
|
|
100
|
-
const seen = /* @__PURE__ */ new Set();
|
|
101
|
-
const links = [];
|
|
102
|
-
for (const s of topology.spawns) {
|
|
103
|
-
const from = unitOf(s.from);
|
|
104
|
-
const to = unitOf(s.to);
|
|
105
|
-
const key = `${from}\u0000${to}`;
|
|
106
|
-
if (from === to || seen.has(key)) continue;
|
|
107
|
-
seen.add(key);
|
|
108
|
-
links.push({
|
|
109
|
-
from,
|
|
110
|
-
to
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
units,
|
|
115
|
-
links
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
async function placeWithElk(units, links, direction) {
|
|
119
|
-
const elk = await loadElk();
|
|
120
|
-
if (!elk) return null;
|
|
121
|
-
const graph = {
|
|
122
|
-
id: "root",
|
|
123
|
-
layoutOptions: {
|
|
124
|
-
"elk.algorithm": "layered",
|
|
125
|
-
"elk.direction": direction === "LR" ? "RIGHT" : "DOWN",
|
|
126
|
-
"elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES",
|
|
127
|
-
"elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
|
|
128
|
-
"elk.layered.nodePlacement.bk.fixedAlignment": "BALANCED",
|
|
129
|
-
"elk.layered.spacing.nodeNodeBetweenLayers": String(LAYER_GAP),
|
|
130
|
-
"elk.spacing.nodeNode": String(SIBLING_GAP),
|
|
131
|
-
"elk.spacing.componentComponent": String(48),
|
|
132
|
-
"elk.separateConnectedComponents": "true",
|
|
133
|
-
"elk.padding": "[top=0,left=0,bottom=0,right=0]"
|
|
134
|
-
},
|
|
135
|
-
children: units.map((u) => ({
|
|
136
|
-
id: u.id,
|
|
137
|
-
width: u.width,
|
|
138
|
-
height: u.height
|
|
139
|
-
})),
|
|
140
|
-
edges: links.map((l, i) => ({
|
|
141
|
-
id: `e${i}`,
|
|
142
|
-
sources: [l.from],
|
|
143
|
-
targets: [l.to]
|
|
144
|
-
}))
|
|
145
|
-
};
|
|
146
|
-
try {
|
|
147
|
-
const out = await elk.layout(graph);
|
|
148
|
-
const at = /* @__PURE__ */ new Map();
|
|
149
|
-
for (const c of out.children ?? []) at.set(c.id, {
|
|
150
|
-
x: c.x ?? 0,
|
|
151
|
-
y: c.y ?? 0
|
|
152
|
-
});
|
|
153
|
-
return at;
|
|
154
|
-
} catch {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/** Without ELK: one row (TB) or column (LR) per spawn depth, each centred. */
|
|
159
|
-
function placeByDepth(units, links, direction) {
|
|
160
|
-
const parents = /* @__PURE__ */ new Map();
|
|
161
|
-
for (const l of links) parents.set(l.to, [...parents.get(l.to) ?? [], l.from]);
|
|
162
|
-
const depth = /* @__PURE__ */ new Map();
|
|
163
|
-
const depthOf = (id, path) => {
|
|
164
|
-
const known = depth.get(id);
|
|
165
|
-
if (known !== void 0) return known;
|
|
166
|
-
if (path.has(id)) return 0;
|
|
167
|
-
path.add(id);
|
|
168
|
-
const d = Math.max(-1, ...(parents.get(id) ?? []).map((p) => depthOf(p, path))) + 1;
|
|
169
|
-
path.delete(id);
|
|
170
|
-
depth.set(id, d);
|
|
171
|
-
return d;
|
|
172
|
-
};
|
|
173
|
-
const layers = [];
|
|
174
|
-
for (const u of units) (layers[depthOf(u.id, /* @__PURE__ */ new Set())] ??= []).push(u);
|
|
175
|
-
const along = (u) => direction === "TB" ? u.width : u.height;
|
|
176
|
-
const across = (u) => direction === "TB" ? u.height : u.width;
|
|
177
|
-
const extents = layers.map((l) => l.reduce((sum, u) => sum + along(u), 0) + (l.length - 1) * SIBLING_GAP);
|
|
178
|
-
const widest = Math.max(...extents);
|
|
179
|
-
const at = /* @__PURE__ */ new Map();
|
|
180
|
-
let offset = 0;
|
|
181
|
-
layers.forEach((layer, i) => {
|
|
182
|
-
let cursor = (widest - extents[i]) / 2;
|
|
183
|
-
for (const u of layer) {
|
|
184
|
-
at.set(u.id, direction === "TB" ? {
|
|
185
|
-
x: cursor,
|
|
186
|
-
y: offset
|
|
187
|
-
} : {
|
|
188
|
-
x: offset,
|
|
189
|
-
y: cursor
|
|
190
|
-
});
|
|
191
|
-
cursor += along(u) + SIBLING_GAP;
|
|
192
|
-
}
|
|
193
|
-
offset += Math.max(...layer.map(across)) + LAYER_GAP;
|
|
194
|
-
});
|
|
195
|
-
return at;
|
|
196
|
-
}
|
|
197
|
-
/** Lay the run out with every grid `columns` wide. */
|
|
198
|
-
async function layoutAgents(topology, direction, columns, card = AGENT_CARD, collapsed = /* @__PURE__ */ new Set()) {
|
|
199
|
-
const { units, links } = layoutUnits(topology, columns, card, collapsed);
|
|
200
|
-
const placed = await placeWithElk(units, links, direction) ?? placeByDepth(units, links, direction);
|
|
201
|
-
const positions = /* @__PURE__ */ new Map();
|
|
202
|
-
const frames = [];
|
|
203
|
-
let width = 0;
|
|
204
|
-
let height = 0;
|
|
205
|
-
for (const u of units) {
|
|
206
|
-
const p = placed.get(u.id) ?? {
|
|
207
|
-
x: 0,
|
|
208
|
-
y: 0
|
|
209
|
-
};
|
|
210
|
-
width = Math.max(width, p.x + u.width);
|
|
211
|
-
height = Math.max(height, p.y + u.height);
|
|
212
|
-
positions.set(u.id, p);
|
|
213
|
-
}
|
|
214
|
-
for (const g of topology.groups) {
|
|
215
|
-
const origin = positions.get(g.id) ?? {
|
|
216
|
-
x: 0,
|
|
217
|
-
y: 0
|
|
218
|
-
};
|
|
219
|
-
positions.delete(g.id);
|
|
220
|
-
const closed = collapsed.has(g.id);
|
|
221
|
-
const size = gridSize(g.memberIds.length, columns, card, closed);
|
|
222
|
-
frames.push({
|
|
223
|
-
group: g,
|
|
224
|
-
...origin,
|
|
225
|
-
...size,
|
|
226
|
-
collapsed: closed
|
|
227
|
-
});
|
|
228
|
-
positions.set(g.parentId, {
|
|
229
|
-
x: origin.x + FRAME_PAD,
|
|
230
|
-
y: origin.y + 8
|
|
231
|
-
});
|
|
232
|
-
if (closed) continue;
|
|
233
|
-
const cols = Math.max(1, Math.min(columns, g.memberIds.length));
|
|
234
|
-
g.memberIds.forEach((id, i) => {
|
|
235
|
-
positions.set(id, {
|
|
236
|
-
x: origin.x + FRAME_PAD + i % cols * (card.width + GRID_GAP),
|
|
237
|
-
y: origin.y + 80 + Math.floor(i / cols) * (card.height + GRID_GAP)
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
columns,
|
|
243
|
-
positions,
|
|
244
|
-
frames,
|
|
245
|
-
width,
|
|
246
|
-
height
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
// src/workflows/agent-graph-parts.tsx
|
|
250
|
-
/**
|
|
251
|
-
* What `AgentGraph` draws: an agent's card, the frame around a grid of leaf
|
|
252
|
-
* workers, and the curved edge a message or delegation travels along.
|
|
253
|
-
*
|
|
254
|
-
* The visual vocabulary is the workflow graph's own (`node-ui.tsx`): the same
|
|
255
|
-
* model brand mark, status colours, resting elevation and selection ring, so an
|
|
256
|
-
* agent run and a workflow run read as one system.
|
|
257
|
-
*/
|
|
258
|
-
/** Workflow state adaptation stays at its boundary; agent states use ui directly. */
|
|
259
|
-
function agentStatus(status) {
|
|
260
|
-
const meta = statusMeta(status);
|
|
261
|
-
const flow = status === "needs-you" ? "waiting" : status === "waiting" || status === "idle" || status === "unknown" ? "queued" : status === "stopped" ? "succeeded" : status;
|
|
262
|
-
return {
|
|
263
|
-
...meta,
|
|
264
|
-
color: `var(${meta.token})`,
|
|
265
|
-
flow
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
function StatusGlyph({ status, size }) {
|
|
269
|
-
return status === "running" ? /* @__PURE__ */ jsx("span", {
|
|
270
|
-
"aria-hidden": true,
|
|
271
|
-
"data-motion": "essential",
|
|
272
|
-
className: "ag-running-dot",
|
|
273
|
-
style: ambientStyle,
|
|
274
|
-
onAnimationStart: syncAmbient
|
|
275
|
-
}) : /* @__PURE__ */ jsx(RunStatusIcon, {
|
|
276
|
-
status,
|
|
277
|
-
size,
|
|
278
|
-
"aria-hidden": true
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
function cardBorder(status) {
|
|
282
|
-
if (status === "needs-you" || status === "failed") return {
|
|
283
|
-
borderColor: agentStatus(status).color,
|
|
284
|
-
borderWidth: status === "needs-you" ? 1.5 : 1
|
|
285
|
-
};
|
|
286
|
-
return { borderColor: RESTING_BORDER };
|
|
287
|
-
}
|
|
288
|
-
/** The words a reader without the picture receives for one agent. */
|
|
289
|
-
function agentSummary(agent) {
|
|
290
|
-
const cost = fmtCost(agent.costUsd ?? void 0);
|
|
291
|
-
const tokens = fmtTokenTotal(agent.tokens);
|
|
292
|
-
return [
|
|
293
|
-
agent.label,
|
|
294
|
-
agent.role,
|
|
295
|
-
agent.harness,
|
|
296
|
-
agent.model,
|
|
297
|
-
agentStatus(agent.status).label,
|
|
298
|
-
agent.turn,
|
|
299
|
-
agent.result,
|
|
300
|
-
cost,
|
|
301
|
-
tokens
|
|
302
|
-
].filter(Boolean).join(", ");
|
|
303
|
-
}
|
|
304
|
-
/** Set on each card's React Flow wrapper — the element that takes keyboard
|
|
305
|
-
* focus — so the card inside can draw the focus ring. */
|
|
306
|
-
const AGENT_NODE_CLASS = "group/agent !outline-none";
|
|
307
|
-
const HANDLE_CLASS = "!h-1 !w-1 !min-w-0 !border-0 !bg-transparent opacity-0";
|
|
308
|
-
function Handles({ direction, source = true }) {
|
|
309
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Handle, {
|
|
310
|
-
type: "target",
|
|
311
|
-
position: direction === "LR" ? Position.Left : Position.Top,
|
|
312
|
-
isConnectable: false,
|
|
313
|
-
className: HANDLE_CLASS
|
|
314
|
-
}), source && /* @__PURE__ */ jsx(Handle, {
|
|
315
|
-
type: "source",
|
|
316
|
-
position: direction === "LR" ? Position.Right : Position.Bottom,
|
|
317
|
-
isConnectable: false,
|
|
318
|
-
className: HANDLE_CLASS
|
|
319
|
-
})] });
|
|
320
|
-
}
|
|
321
|
-
function AgentCardView({ agent, selected, direction, now: fixed, onOpen, parentLabel, handles = false }) {
|
|
322
|
-
const now = useRunNow(fixed);
|
|
323
|
-
const quiet = quietActivity(agent, now);
|
|
324
|
-
const s = agentStatus(agent.status);
|
|
325
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
326
|
-
"data-status": agent.status,
|
|
327
|
-
"data-activity": quiet ? "quiet" : void 0,
|
|
328
|
-
"data-selected": selected || void 0,
|
|
329
|
-
title: agentSummary(agent),
|
|
330
|
-
className: `ag-agent-card relative flex h-full w-full flex-col gap-0.5 rounded-[var(--radius-card)] border bg-card px-3 py-2 text-left ${MOTION_CONTROL}`,
|
|
331
|
-
style: {
|
|
332
|
-
boxShadow: RESTING_SHADOW,
|
|
333
|
-
...cardBorder(agent.status),
|
|
334
|
-
...selected ? SELECTION_OUTLINE : {}
|
|
335
|
-
},
|
|
336
|
-
children: [
|
|
337
|
-
handles && /* @__PURE__ */ jsx(Handles, { direction }),
|
|
338
|
-
/* @__PURE__ */ jsxs("div", {
|
|
339
|
-
className: "flex min-w-0 items-center gap-2",
|
|
340
|
-
children: [
|
|
341
|
-
/* @__PURE__ */ jsx("span", {
|
|
342
|
-
className: "grayscale hover:grayscale-0",
|
|
343
|
-
children: /* @__PURE__ */ jsx(NodeMark, {
|
|
344
|
-
kind: "agent.run",
|
|
345
|
-
model: agent.model ?? void 0,
|
|
346
|
-
accent: KIND_ACCENT["agent.run"],
|
|
347
|
-
tile: 18
|
|
348
|
-
})
|
|
349
|
-
}),
|
|
350
|
-
/* @__PURE__ */ jsxs("span", {
|
|
351
|
-
className: "min-w-0 flex-1 truncate text-sm font-semibold leading-[18px]",
|
|
352
|
-
children: [parentLabel && /* @__PURE__ */ jsxs("span", {
|
|
353
|
-
className: "ag-parent-label",
|
|
354
|
-
children: [parentLabel, " / "]
|
|
355
|
-
}), agent.label]
|
|
356
|
-
}),
|
|
357
|
-
/* @__PURE__ */ jsx("span", {
|
|
358
|
-
className: "text-xs text-muted-foreground tabular-nums",
|
|
359
|
-
title: agent.costUsd == null ? "Cost not reported" : "Cost",
|
|
360
|
-
children: fmtCost(agent.costUsd ?? void 0) ?? "—"
|
|
361
|
-
})
|
|
362
|
-
]
|
|
363
|
-
}),
|
|
364
|
-
/* @__PURE__ */ jsx("p", {
|
|
365
|
-
className: "m-0 line-clamp-2 min-h-4 text-xs text-foreground leading-4",
|
|
366
|
-
children: (agent.status === "failed" ? agent.result ?? agent.turn : agent.turn) || "—"
|
|
367
|
-
}),
|
|
368
|
-
/* @__PURE__ */ jsxs("div", {
|
|
369
|
-
className: "mt-auto flex items-center justify-between gap-1 text-xs",
|
|
370
|
-
style: { color: quiet ? "var(--surface-warning-text)" : s.color },
|
|
371
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
372
|
-
className: "flex items-center gap-1",
|
|
373
|
-
children: [/* @__PURE__ */ jsx(StatusGlyph, {
|
|
374
|
-
status: agent.status,
|
|
375
|
-
size: 16
|
|
376
|
-
}), quiet?.label ?? s.label]
|
|
377
|
-
}), agent.status === "needs-you" && /* @__PURE__ */ jsx("button", {
|
|
378
|
-
type: "button",
|
|
379
|
-
className: "ag-live-respond nodrag nopan",
|
|
380
|
-
disabled: !onOpen,
|
|
381
|
-
onClick: (event) => {
|
|
382
|
-
event.stopPropagation();
|
|
383
|
-
onOpen?.();
|
|
384
|
-
},
|
|
385
|
-
children: "Respond"
|
|
386
|
-
})]
|
|
387
|
-
})
|
|
388
|
-
]
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
function AgentCard({ data }) {
|
|
392
|
-
return /* @__PURE__ */ jsx(AgentCardView, {
|
|
393
|
-
...data,
|
|
394
|
-
handles: true
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
function AgentFrameBox({ data }) {
|
|
398
|
-
const { frame, direction } = data;
|
|
399
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
400
|
-
className: "relative h-full w-full rounded-2xl border",
|
|
401
|
-
style: {
|
|
402
|
-
background: "color-mix(in srgb, hsl(var(--muted-foreground)) 7%, transparent)",
|
|
403
|
-
borderColor: "hsl(var(--foreground) / 0.08)"
|
|
404
|
-
},
|
|
405
|
-
children: [/* @__PURE__ */ jsx(Handles, { direction }), /* @__PURE__ */ jsxs("button", {
|
|
406
|
-
type: "button",
|
|
407
|
-
className: "nodrag nopan absolute right-3 top-2 flex h-16 w-8 flex-col items-center justify-center gap-1 rounded text-sm text-muted-foreground hover:bg-muted focus-visible:outline",
|
|
408
|
-
"aria-label": `${frame.collapsed ? "Expand" : "Collapse"} ${data.label} team`,
|
|
409
|
-
"aria-expanded": !frame.collapsed,
|
|
410
|
-
title: `${data.label} · ${frame.group.memberIds.length} agents`,
|
|
411
|
-
onClick: (event) => {
|
|
412
|
-
event.stopPropagation();
|
|
413
|
-
data.onToggle();
|
|
414
|
-
},
|
|
415
|
-
children: [/* @__PURE__ */ jsx(ChevronDown, {
|
|
416
|
-
size: 16,
|
|
417
|
-
className: frame.collapsed ? "-rotate-90" : void 0
|
|
418
|
-
}), /* @__PURE__ */ jsx("span", { children: frame.group.memberIds.length })]
|
|
419
|
-
})]
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
/** Resting message and delegation edges stay neutral. */
|
|
423
|
-
const MESSAGE_EDGE_COLOR = {
|
|
424
|
-
message: "color-mix(in srgb, hsl(var(--muted-foreground)) 70%, transparent)",
|
|
425
|
-
delegation: "color-mix(in srgb, hsl(var(--muted-foreground)) 80%, transparent)"
|
|
426
|
-
};
|
|
427
|
-
function boxOf(node) {
|
|
428
|
-
return {
|
|
429
|
-
x: node.internals.positionAbsolute.x,
|
|
430
|
-
y: node.internals.positionAbsolute.y,
|
|
431
|
-
w: node.measured.width ?? node.width ?? 0,
|
|
432
|
-
h: node.measured.height ?? node.height ?? 0
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
/** Where the line from a box's centre toward `toward` leaves the box, `gap`px out. */
|
|
436
|
-
function exitPoint(b, toward, gap) {
|
|
437
|
-
const cx = b.x + b.w / 2;
|
|
438
|
-
const cy = b.y + b.h / 2;
|
|
439
|
-
const dx = toward.x - cx;
|
|
440
|
-
const dy = toward.y - cy;
|
|
441
|
-
const len = Math.hypot(dx, dy);
|
|
442
|
-
if (len === 0) return {
|
|
443
|
-
x: cx,
|
|
444
|
-
y: cy
|
|
445
|
-
};
|
|
446
|
-
const scale = Math.min(dx === 0 ? Number.POSITIVE_INFINITY : b.w / 2 / Math.abs(dx), dy === 0 ? Number.POSITIVE_INFINITY : b.h / 2 / Math.abs(dy));
|
|
447
|
-
return {
|
|
448
|
-
x: cx + dx * scale + dx / len * gap,
|
|
449
|
-
y: cy + dy * scale + dy / len * gap
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* A curve from one card's border to another's, bowed to the right of its
|
|
454
|
-
* direction of travel: two agents messaging each other both ways draw two
|
|
455
|
-
* separate arcs instead of one line over another, and a curve never hides
|
|
456
|
-
* under the straight spawn edges of the tree.
|
|
457
|
-
*/
|
|
458
|
-
function messageCurve(a, b) {
|
|
459
|
-
const ac = {
|
|
460
|
-
x: a.x + a.w / 2,
|
|
461
|
-
y: a.y + a.h / 2
|
|
462
|
-
};
|
|
463
|
-
const bc = {
|
|
464
|
-
x: b.x + b.w / 2,
|
|
465
|
-
y: b.y + b.h / 2
|
|
466
|
-
};
|
|
467
|
-
const dx = bc.x - ac.x;
|
|
468
|
-
const dy = bc.y - ac.y;
|
|
469
|
-
const len = Math.hypot(dx, dy) || 1;
|
|
470
|
-
const bend = Math.min(90, Math.max(24, len * .22));
|
|
471
|
-
const control = {
|
|
472
|
-
x: (ac.x + bc.x) / 2 - dy / len * bend,
|
|
473
|
-
y: (ac.y + bc.y) / 2 + dx / len * bend
|
|
474
|
-
};
|
|
475
|
-
const start = exitPoint(a, control, 2);
|
|
476
|
-
const end = exitPoint(b, control, 4);
|
|
477
|
-
return {
|
|
478
|
-
path: `M${start.x},${start.y} Q${control.x},${control.y} ${end.x},${end.y}`,
|
|
479
|
-
mid: {
|
|
480
|
-
x: .25 * start.x + .5 * control.x + .25 * end.x,
|
|
481
|
-
y: .25 * start.y + .5 * control.y + .25 * end.y
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
function AgentMessageEdgeView({ id, source, target, data, markerEnd }) {
|
|
486
|
-
const from = useInternalNode(source);
|
|
487
|
-
const to = useInternalNode(target);
|
|
488
|
-
if (!from || !to || !data) return null;
|
|
489
|
-
const { path, mid } = messageCurve(boxOf(from), boxOf(to));
|
|
490
|
-
const color = MESSAGE_EDGE_COLOR[data.kind] ?? MESSAGE_EDGE_COLOR.message;
|
|
491
|
-
const count = data.count ?? 1;
|
|
492
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(BaseEdge, {
|
|
493
|
-
id,
|
|
494
|
-
path,
|
|
495
|
-
markerEnd,
|
|
496
|
-
interactionWidth: 10,
|
|
497
|
-
style: {
|
|
498
|
-
stroke: color,
|
|
499
|
-
strokeWidth: 1.25,
|
|
500
|
-
fill: "none"
|
|
501
|
-
}
|
|
502
|
-
}), count > 1 && /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsxs("div", {
|
|
503
|
-
title: data.label ? `${data.label} ×${count}` : `${count} ${data.kind}s`,
|
|
504
|
-
className: "nodrag nopan pointer-events-auto absolute rounded-full border bg-card px-1.5 font-medium text-[10px] text-muted-foreground tabular-nums leading-4 shadow-sm",
|
|
505
|
-
style: {
|
|
506
|
-
transform: `translate(-50%, -50%) translate(${mid.x}px, ${mid.y}px)`,
|
|
507
|
-
borderColor: color
|
|
508
|
-
},
|
|
509
|
-
children: ["×", count]
|
|
510
|
-
}) })] });
|
|
511
|
-
}
|
|
512
|
-
// src/workflows/agent-live-card.tsx
|
|
513
|
-
const SETTLED = /* @__PURE__ */ new Set(["succeeded", "stopped"]);
|
|
514
|
-
function middleLabel(label) {
|
|
515
|
-
return label.length <= 52 ? label : `${label.slice(0, 32)} … ${label.slice(-17)}`;
|
|
516
|
-
}
|
|
517
|
-
/** One shell survives settling. A failure retains its evidence and absolute event times. */
|
|
518
|
-
function LiveAgentCardView({ agent, selected, direction = "TB", now: fixed, handles = false, onOpen, parentLabel, renderEventIcon }) {
|
|
519
|
-
const now = useRunNow(fixed);
|
|
520
|
-
const ref = useRef(null);
|
|
521
|
-
useAttention(ref, agent.status);
|
|
522
|
-
const settled = SETTLED.has(agent.status);
|
|
523
|
-
const failed = agent.status === "failed";
|
|
524
|
-
const quiet = quietActivity(agent, now);
|
|
525
|
-
const s = agentStatus(agent.status);
|
|
526
|
-
const meta = [agent.model ? shortModel(agent.model) : void 0, agent.effort].filter(Boolean).join(" · ");
|
|
527
|
-
const events = (agent.events ?? []).slice(-3);
|
|
528
|
-
const cost = Number.isFinite(agent.costUsd) ? fmtCost(agent.costUsd) : void 0;
|
|
529
|
-
const metrics = [
|
|
530
|
-
cost,
|
|
531
|
-
!failed && agent.startedAt != null ? `${fmtEventTime(agent.startedAt, now, false)} elapsed` : void 0,
|
|
532
|
-
Number.isFinite(agent.commits) ? `${agent.commits} ${agent.commits === 1 ? "commit" : "commits"}` : void 0
|
|
533
|
-
].filter(Boolean).join(" · ") || "—";
|
|
534
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
535
|
-
className: "ag-live-slot",
|
|
536
|
-
children: [handles && /* @__PURE__ */ jsx(Handles, { direction }), /* @__PURE__ */ jsxs("div", {
|
|
537
|
-
ref,
|
|
538
|
-
className: `ag-live-card${settled ? " ag-live-chip" : ""}`,
|
|
539
|
-
"data-status": agent.status,
|
|
540
|
-
"data-sparse": events.length === 0 || void 0,
|
|
541
|
-
"data-activity": quiet ? "quiet" : agent.activity ?? void 0,
|
|
542
|
-
"data-selected": selected || void 0,
|
|
543
|
-
title: agentSummary(agent),
|
|
544
|
-
style: {
|
|
545
|
-
boxShadow: RESTING_SHADOW,
|
|
546
|
-
...cardBorder(agent.status),
|
|
547
|
-
...selected ? SELECTION_OUTLINE : {},
|
|
548
|
-
["--ag-status"]: s.color
|
|
549
|
-
},
|
|
550
|
-
children: [
|
|
551
|
-
settled && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(StatusGlyph, {
|
|
552
|
-
status: agent.status,
|
|
553
|
-
size: 16
|
|
554
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
555
|
-
className: "ag-live-chip-status",
|
|
556
|
-
children: s.label
|
|
557
|
-
})] }),
|
|
558
|
-
/* @__PURE__ */ jsxs("div", {
|
|
559
|
-
className: "ag-live-head",
|
|
560
|
-
children: [
|
|
561
|
-
/* @__PURE__ */ jsx("span", {
|
|
562
|
-
className: "grayscale hover:grayscale-0",
|
|
563
|
-
children: /* @__PURE__ */ jsx(NodeMark, {
|
|
564
|
-
kind: "agent.run",
|
|
565
|
-
model: agent.model ?? void 0,
|
|
566
|
-
accent: KIND_ACCENT["agent.run"],
|
|
567
|
-
tile: 18
|
|
568
|
-
})
|
|
569
|
-
}),
|
|
570
|
-
/* @__PURE__ */ jsxs("div", {
|
|
571
|
-
className: "ag-live-names",
|
|
572
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
573
|
-
className: "ag-live-name",
|
|
574
|
-
children: [parentLabel && /* @__PURE__ */ jsxs("span", {
|
|
575
|
-
className: "ag-parent-label",
|
|
576
|
-
children: [parentLabel, " / "]
|
|
577
|
-
}), agent.label]
|
|
578
|
-
}), !settled && meta && /* @__PURE__ */ jsx("span", {
|
|
579
|
-
className: "ag-live-meta",
|
|
580
|
-
children: meta
|
|
581
|
-
})]
|
|
582
|
-
}),
|
|
583
|
-
/* @__PURE__ */ jsxs("div", {
|
|
584
|
-
className: "ag-live-stats",
|
|
585
|
-
children: [failed && onOpen ? /* @__PURE__ */ jsxs("button", {
|
|
586
|
-
type: "button",
|
|
587
|
-
className: "ag-live-pill ag-live-inspect nodrag nopan",
|
|
588
|
-
onClick: (event) => {
|
|
589
|
-
event.stopPropagation();
|
|
590
|
-
onOpen();
|
|
591
|
-
},
|
|
592
|
-
children: [
|
|
593
|
-
/* @__PURE__ */ jsx(StatusGlyph, {
|
|
594
|
-
status: agent.status,
|
|
595
|
-
size: 16
|
|
596
|
-
}),
|
|
597
|
-
s.label,
|
|
598
|
-
/* @__PURE__ */ jsx("span", { children: "Inspect" })
|
|
599
|
-
]
|
|
600
|
-
}) : /* @__PURE__ */ jsxs("span", {
|
|
601
|
-
className: `ag-live-pill${quiet ? " ag-live-quiet" : ""}`,
|
|
602
|
-
children: [/* @__PURE__ */ jsx(StatusGlyph, {
|
|
603
|
-
status: agent.status,
|
|
604
|
-
size: 16
|
|
605
|
-
}), quiet?.label ?? s.label]
|
|
606
|
-
}), !settled && /* @__PURE__ */ jsx("span", {
|
|
607
|
-
className: "ag-live-accounting",
|
|
608
|
-
title: metrics,
|
|
609
|
-
children: metrics
|
|
610
|
-
})]
|
|
611
|
-
})
|
|
612
|
-
]
|
|
613
|
-
}),
|
|
614
|
-
settled ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
615
|
-
className: "ag-live-chip-result",
|
|
616
|
-
children: agent.result ?? agent.turn ?? s.label
|
|
617
|
-
}), cost && /* @__PURE__ */ jsx("span", {
|
|
618
|
-
className: "ag-live-chip-cost",
|
|
619
|
-
title: "Cost",
|
|
620
|
-
children: cost
|
|
621
|
-
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
622
|
-
className: "ag-live-thinking-row",
|
|
623
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
624
|
-
className: `ag-live-thinking${failed ? " ag-live-failure" : ""}`,
|
|
625
|
-
children: failed ? agent.result ?? events.at(-1)?.label ?? agent.turn ?? s.label : agent.turn || "—"
|
|
626
|
-
}), agent.status === "needs-you" && /* @__PURE__ */ jsx("button", {
|
|
627
|
-
type: "button",
|
|
628
|
-
className: "ag-live-respond nodrag nopan",
|
|
629
|
-
disabled: !onOpen,
|
|
630
|
-
title: onOpen ? void 0 : "Responses unavailable in this view",
|
|
631
|
-
onClick: (event) => {
|
|
632
|
-
event.stopPropagation();
|
|
633
|
-
onOpen?.();
|
|
634
|
-
},
|
|
635
|
-
children: "Respond"
|
|
636
|
-
})]
|
|
637
|
-
}), events.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
638
|
-
className: "ag-live-events-clip",
|
|
639
|
-
children: /* @__PURE__ */ jsx("ol", {
|
|
640
|
-
className: "ag-live-events",
|
|
641
|
-
"aria-label": "Latest events",
|
|
642
|
-
children: events.map((event) => /* @__PURE__ */ jsxs("li", {
|
|
643
|
-
title: event.label,
|
|
644
|
-
children: [
|
|
645
|
-
renderEventIcon?.(event) ?? /* @__PURE__ */ jsx(Wrench, {
|
|
646
|
-
size: 12,
|
|
647
|
-
strokeWidth: 1.75,
|
|
648
|
-
"aria-hidden": true
|
|
649
|
-
}),
|
|
650
|
-
/* @__PURE__ */ jsx("span", {
|
|
651
|
-
className: "ag-live-event-label",
|
|
652
|
-
children: middleLabel(event.label)
|
|
653
|
-
}),
|
|
654
|
-
/* @__PURE__ */ jsxs("time", {
|
|
655
|
-
className: "ag-live-event-at",
|
|
656
|
-
dateTime: Number.isFinite(event.at) ? new Date(event.at).toISOString() : void 0,
|
|
657
|
-
children: [failed && event.at != null ? "at " : "", fmtEventTime(event.at, now, failed)]
|
|
658
|
-
})
|
|
659
|
-
]
|
|
660
|
-
}, event.id))
|
|
661
|
-
})
|
|
662
|
-
})] })
|
|
663
|
-
]
|
|
664
|
-
})]
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
function LiveAgentCard({ data }) {
|
|
668
|
-
return /* @__PURE__ */ jsx(LiveAgentCardView, {
|
|
669
|
-
...data,
|
|
670
|
-
handles: true
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
// src/workflows/AgentGraphCanvas.tsx
|
|
674
|
-
const NODE_TYPES = {
|
|
675
|
-
agent: AgentCard,
|
|
676
|
-
liveAgent: LiveAgentCard,
|
|
677
|
-
agentFrame: AgentFrameBox
|
|
678
|
-
};
|
|
679
|
-
const EDGE_TYPES = { agentMessage: AgentMessageEdgeView };
|
|
680
|
-
/** The fit's margin, in screen pixels. */
|
|
681
|
-
const FIT_PADDING = 16;
|
|
682
|
-
const PHONE_PAGE = 24;
|
|
683
|
-
/** The canvas's size, once it has one. */
|
|
684
|
-
function useCanvasSize() {
|
|
685
|
-
const [size, setSize] = useState(null);
|
|
686
|
-
const observer = useRef(null);
|
|
687
|
-
return [size, useCallback((el) => {
|
|
688
|
-
observer.current?.disconnect();
|
|
689
|
-
observer.current = null;
|
|
690
|
-
if (!el || typeof ResizeObserver === "undefined") return;
|
|
691
|
-
const viewport = el.querySelector(".ag-graph-viewport") ?? el;
|
|
692
|
-
const read = () => {
|
|
693
|
-
const r = viewport.getBoundingClientRect();
|
|
694
|
-
if (r.width === 0 || r.height === 0) return;
|
|
695
|
-
setSize((prev) => prev && Math.abs(prev.width - r.width) < 1 && Math.abs(prev.height - r.height) < 1 ? prev : {
|
|
696
|
-
width: r.width,
|
|
697
|
-
height: r.height
|
|
698
|
-
});
|
|
699
|
-
};
|
|
700
|
-
observer.current = new ResizeObserver(read);
|
|
701
|
-
observer.current.observe(viewport);
|
|
702
|
-
read();
|
|
703
|
-
}, [])];
|
|
704
|
-
}
|
|
705
|
-
/** Keep the selected card inside the actual viewport without resetting the reader's camera. */
|
|
706
|
-
function revealSelection(inst, wrapper, id) {
|
|
707
|
-
const node = inst.getInternalNode(id), box = wrapper?.querySelector(".ag-graph-viewport")?.getBoundingClientRect();
|
|
708
|
-
const width = node?.measured.width ?? node?.width, height = node?.measured.height ?? node?.height;
|
|
709
|
-
if (!node || !box || !width || !height) return;
|
|
710
|
-
const current = inst.getViewport(), position = node.internals.positionAbsolute;
|
|
711
|
-
const zoom = current.zoom;
|
|
712
|
-
const x = Math.max(FIT_PADDING - position.x * zoom, Math.min(box.width - FIT_PADDING - (position.x + width) * zoom, current.x));
|
|
713
|
-
const y = Math.max(FIT_PADDING - position.y * zoom, Math.min(box.height - FIT_PADDING - (position.y + height) * zoom, current.y));
|
|
714
|
-
if (x !== current.x || y !== current.y || zoom !== current.zoom) inst.setViewport({
|
|
715
|
-
x,
|
|
716
|
-
y,
|
|
717
|
-
zoom
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB", className, emptyState, live = false, renderEventIcon, showLatestEvents = true, now }) {
|
|
721
|
-
const card = live ? LIVE_AGENT_CARD : AGENT_CARD;
|
|
722
|
-
const clock = useRunNow(now);
|
|
723
|
-
const animateMotion = useMotionAllowed();
|
|
724
|
-
const detailsId = useId();
|
|
725
|
-
const hoverTimer = useRef(void 0);
|
|
726
|
-
const hoverTarget = useRef(null);
|
|
727
|
-
const returningFocus = useRef(false);
|
|
728
|
-
const keepDetails = () => clearTimeout(hoverTimer.current);
|
|
729
|
-
const hideDetails = () => {
|
|
730
|
-
keepDetails();
|
|
731
|
-
setHovered(null);
|
|
732
|
-
};
|
|
733
|
-
const leaveDetails = () => {
|
|
734
|
-
keepDetails();
|
|
735
|
-
hoverTimer.current = setTimeout(() => setHovered(null), tokenMilliseconds("--duration-fast", wrapperRef.current));
|
|
736
|
-
};
|
|
737
|
-
useEffect(() => () => clearTimeout(hoverTimer.current), []);
|
|
738
|
-
const [hovered, setHovered] = useState(null);
|
|
739
|
-
const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
|
|
740
|
-
const [phonePage, setPhonePage] = useState(0);
|
|
741
|
-
const wrapperRef = useRef(null);
|
|
742
|
-
const [canvas, measureCanvas] = useCanvasSize();
|
|
743
|
-
const colorMode = useColorMode(wrapperRef, nodes.length > 0);
|
|
744
|
-
const narrow = canvas !== null && canvas.width < 600;
|
|
745
|
-
const nextTopology = buildAgentTopology(nodes, edges);
|
|
746
|
-
const topologyRef = useRef(nextTopology);
|
|
747
|
-
if (topologyRef.current.key !== nextTopology.key) topologyRef.current = nextTopology;
|
|
748
|
-
const topology = topologyRef.current;
|
|
749
|
-
const columnKey = `${direction}:${live}:${topology.key}`;
|
|
750
|
-
const columnRef = useRef(null);
|
|
751
|
-
if (canvas && !narrow && columnRef.current?.key !== columnKey) columnRef.current = {
|
|
752
|
-
key: columnKey,
|
|
753
|
-
columns: Math.max(1, Math.min(6, Math.floor((canvas.width - 32) / (card.width + 10))))
|
|
754
|
-
};
|
|
755
|
-
const columns = columnRef.current?.columns ?? 1;
|
|
756
|
-
const layoutKey = JSON.stringify([
|
|
757
|
-
direction,
|
|
758
|
-
live,
|
|
759
|
-
topology.key,
|
|
760
|
-
columns,
|
|
761
|
-
[...collapsed]
|
|
762
|
-
]);
|
|
763
|
-
const [placed, setPlaced] = useState(null);
|
|
764
|
-
useEffect(() => {
|
|
765
|
-
if (narrow) return;
|
|
766
|
-
let active = true;
|
|
767
|
-
layoutAgents(topology, direction, columns, card, collapsed).then((layout) => {
|
|
768
|
-
if (active) setPlaced({
|
|
769
|
-
key: layoutKey,
|
|
770
|
-
layout
|
|
771
|
-
});
|
|
772
|
-
});
|
|
773
|
-
return () => {
|
|
774
|
-
active = false;
|
|
775
|
-
};
|
|
776
|
-
}, [layoutKey, narrow]);
|
|
777
|
-
const layout = placed?.key === layoutKey ? placed.layout : null;
|
|
778
|
-
useEffect(() => {
|
|
779
|
-
const group = selectedId && topology.groupOf.get(selectedId);
|
|
780
|
-
if (group) setCollapsed((current) => {
|
|
781
|
-
if (!current.has(group)) return current;
|
|
782
|
-
const next = new Set(current);
|
|
783
|
-
next.delete(group);
|
|
784
|
-
return next;
|
|
785
|
-
});
|
|
786
|
-
}, [selectedId, topology.key]);
|
|
787
|
-
const byId = useMemo(() => new Map(nodes.map((n) => [n.id, n])), [nodes]);
|
|
788
|
-
const rfRef = useRef(null);
|
|
789
|
-
const flowNodes = useMemo(() => {
|
|
790
|
-
if (!layout) return [];
|
|
791
|
-
const frames = layout.frames.map((frame) => ({
|
|
792
|
-
id: frame.group.id,
|
|
793
|
-
type: "agentFrame",
|
|
794
|
-
position: {
|
|
795
|
-
x: frame.x,
|
|
796
|
-
y: frame.y
|
|
797
|
-
},
|
|
798
|
-
width: frame.width,
|
|
799
|
-
height: frame.height,
|
|
800
|
-
measured: rfRef.current?.getInternalNode(frame.group.id)?.measured,
|
|
801
|
-
zIndex: -1,
|
|
802
|
-
selectable: false,
|
|
803
|
-
focusable: false,
|
|
804
|
-
draggable: false,
|
|
805
|
-
data: {
|
|
806
|
-
frame,
|
|
807
|
-
direction,
|
|
808
|
-
label: byId.get(frame.group.parentId)?.label ?? "Agent",
|
|
809
|
-
onToggle: () => {
|
|
810
|
-
if (!frame.collapsed && selectedId && frame.group.memberIds.includes(selectedId)) onSelect?.(frame.group.parentId);
|
|
811
|
-
setCollapsed((current) => {
|
|
812
|
-
const next = new Set(current);
|
|
813
|
-
next.has(frame.group.id) ? next.delete(frame.group.id) : next.add(frame.group.id);
|
|
814
|
-
return next;
|
|
815
|
-
});
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}));
|
|
819
|
-
const cards = [];
|
|
820
|
-
for (const id of topology.agentIds) {
|
|
821
|
-
const agent = byId.get(id);
|
|
822
|
-
const position = layout.positions.get(id);
|
|
823
|
-
if (!agent || !position) continue;
|
|
824
|
-
const selected = selectedId === id, owned = layout.frames.find((frame) => frame.group.parentId === id);
|
|
825
|
-
const width = owned ? owned.width - 64 : card.width, height = owned ? 64 : card.height;
|
|
826
|
-
const measured = rfRef.current?.getInternalNode(id)?.measured;
|
|
827
|
-
cards.push({
|
|
828
|
-
id,
|
|
829
|
-
type: live ? "liveAgent" : "agent",
|
|
830
|
-
position,
|
|
831
|
-
width,
|
|
832
|
-
height,
|
|
833
|
-
measured: measured?.width === width && measured.height === height ? measured : void 0,
|
|
834
|
-
className: `${AGENT_NODE_CLASS}${owned ? " ag-frame-owner" : ""}`,
|
|
835
|
-
draggable: false,
|
|
836
|
-
ariaRole: "button",
|
|
837
|
-
ariaLabel: agentSummary(agent),
|
|
838
|
-
domAttributes: {
|
|
839
|
-
"aria-pressed": selected,
|
|
840
|
-
"aria-describedby": hovered?.id === id ? detailsId : void 0
|
|
841
|
-
},
|
|
842
|
-
data: {
|
|
843
|
-
agent,
|
|
844
|
-
selected,
|
|
845
|
-
direction,
|
|
846
|
-
now: clock,
|
|
847
|
-
onOpen: onSelect ? () => onSelect(agent.id) : void 0,
|
|
848
|
-
renderEventIcon
|
|
849
|
-
}
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
return [...frames, ...cards];
|
|
853
|
-
}, [
|
|
854
|
-
layout,
|
|
855
|
-
topology,
|
|
856
|
-
byId,
|
|
857
|
-
selectedId,
|
|
858
|
-
direction,
|
|
859
|
-
live,
|
|
860
|
-
card,
|
|
861
|
-
clock,
|
|
862
|
-
onSelect,
|
|
863
|
-
renderEventIcon,
|
|
864
|
-
hovered
|
|
865
|
-
]);
|
|
866
|
-
const flowEdges = useMemo(() => {
|
|
867
|
-
if (!layout) return [];
|
|
868
|
-
const out = [];
|
|
869
|
-
const seen = /* @__PURE__ */ new Set();
|
|
870
|
-
for (const s of topology.spawns) {
|
|
871
|
-
const source = topology.groups.find((group) => group.parentId === s.from)?.id ?? s.from, target = topology.groupOf.get(s.to) ?? s.to;
|
|
872
|
-
if (source === target) continue;
|
|
873
|
-
const id = `spawn:${source}:${target}`;
|
|
874
|
-
if (seen.has(id)) continue;
|
|
875
|
-
seen.add(id);
|
|
876
|
-
out.push({
|
|
877
|
-
id,
|
|
878
|
-
source,
|
|
879
|
-
target,
|
|
880
|
-
type: "smoothstep",
|
|
881
|
-
pathOptions: { borderRadius: 14 },
|
|
882
|
-
selectable: false,
|
|
883
|
-
focusable: false,
|
|
884
|
-
style: {
|
|
885
|
-
stroke: edgeColor(void 0),
|
|
886
|
-
strokeWidth: 1.5
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
const ids = new Set(layout.positions.keys());
|
|
891
|
-
const messages = /* @__PURE__ */ new Map();
|
|
892
|
-
for (const e of edges) {
|
|
893
|
-
if (e.kind === "spawn" || e.from === e.to || !ids.has(e.from) || !ids.has(e.to)) continue;
|
|
894
|
-
if (![selectedId, hovered?.id].some((id) => id === e.from || id === e.to)) continue;
|
|
895
|
-
const id = `${e.kind}:${e.from}:${e.to}`;
|
|
896
|
-
const prior = messages.get(id);
|
|
897
|
-
const count = (prior?.data?.count ?? 0) + Math.max(1, e.count ?? 1);
|
|
898
|
-
const color = MESSAGE_EDGE_COLOR[e.kind] ?? MESSAGE_EDGE_COLOR.message;
|
|
899
|
-
messages.set(id, {
|
|
900
|
-
id,
|
|
901
|
-
source: e.from,
|
|
902
|
-
target: e.to,
|
|
903
|
-
type: "agentMessage",
|
|
904
|
-
selectable: false,
|
|
905
|
-
focusable: false,
|
|
906
|
-
zIndex: 0,
|
|
907
|
-
markerEnd: {
|
|
908
|
-
type: MarkerType.ArrowClosed,
|
|
909
|
-
width: 12,
|
|
910
|
-
height: 12,
|
|
911
|
-
color
|
|
912
|
-
},
|
|
913
|
-
ariaLabel: `${e.kind === "delegation" ? "Delegation" : "Message"} from ${byId.get(e.from)?.label ?? e.from} to ${byId.get(e.to)?.label ?? e.to}${count > 1 ? `, ${count} times` : ""}`,
|
|
914
|
-
data: {
|
|
915
|
-
kind: e.kind,
|
|
916
|
-
count,
|
|
917
|
-
label: e.label ?? prior?.data?.label
|
|
918
|
-
}
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
return [...out, ...messages.values()];
|
|
922
|
-
}, [
|
|
923
|
-
layout,
|
|
924
|
-
topology,
|
|
925
|
-
edges,
|
|
926
|
-
byId,
|
|
927
|
-
selectedId,
|
|
928
|
-
hovered
|
|
929
|
-
]);
|
|
930
|
-
const [ready, setReady] = useState(false);
|
|
931
|
-
const [shown, setShown] = useState(false);
|
|
932
|
-
const selection = useRef(selectedId);
|
|
933
|
-
selection.current = selectedId;
|
|
934
|
-
const framed = useRef(null);
|
|
935
|
-
useEffect(() => {
|
|
936
|
-
if (narrow) {
|
|
937
|
-
rfRef.current = null;
|
|
938
|
-
setReady(false);
|
|
939
|
-
setShown(false);
|
|
940
|
-
framed.current = null;
|
|
941
|
-
}
|
|
942
|
-
}, [narrow]);
|
|
943
|
-
const layoutIdentity = layout ? `${layoutKey}:${layout.columns}` : null;
|
|
944
|
-
const minZoom = layout && canvas ? Math.max(.02, Math.min(.2, (canvas.width - 32) / layout.width, (canvas.height - 32) / layout.height)) : .2;
|
|
945
|
-
useEffect(() => {
|
|
946
|
-
const inst = rfRef.current;
|
|
947
|
-
if (!ready || !inst || !layoutIdentity || !canvas) return;
|
|
948
|
-
const relayout = framed.current !== layoutIdentity;
|
|
949
|
-
const preserveView = !relayout;
|
|
950
|
-
if (preserveView && !selection.current) return;
|
|
951
|
-
const animate = framed.current !== null && relayout && animateMotion;
|
|
952
|
-
framed.current = layoutIdentity;
|
|
953
|
-
const frame = requestAnimationFrame(() => {
|
|
954
|
-
if (preserveView) revealSelection(inst, wrapperRef.current, selection.current);
|
|
955
|
-
else {
|
|
956
|
-
const id = selection.current ?? topology.agentIds[0], position = layout?.positions.get(id), width = inst.getInternalNode(id)?.width ?? card.width;
|
|
957
|
-
const x = layout.width + 32 <= canvas.width ? (canvas.width - layout.width) / 2 : position ? Math.max(FIT_PADDING - position.x, canvas.width / 2 - position.x - width / 2) : FIT_PADDING;
|
|
958
|
-
const y = layout.height + 32 <= canvas.height ? FIT_PADDING : FIT_PADDING - (position?.y ?? 0);
|
|
959
|
-
inst.setViewport({
|
|
960
|
-
x,
|
|
961
|
-
y,
|
|
962
|
-
zoom: 1
|
|
963
|
-
}, { duration: animate ? tokenMilliseconds("--duration-base", wrapperRef.current) : 0 });
|
|
964
|
-
}
|
|
965
|
-
setShown(true);
|
|
966
|
-
});
|
|
967
|
-
return () => cancelAnimationFrame(frame);
|
|
968
|
-
}, [
|
|
969
|
-
ready,
|
|
970
|
-
layoutIdentity,
|
|
971
|
-
canvas,
|
|
972
|
-
minZoom,
|
|
973
|
-
animateMotion,
|
|
974
|
-
selectedId
|
|
975
|
-
]);
|
|
976
|
-
const select = useCallback((id) => {
|
|
977
|
-
if (byId.has(id)) {
|
|
978
|
-
onSelect?.(id);
|
|
979
|
-
wrapperRef.current?.querySelector(`[data-agent-id="${CSS.escape(id)}"]`)?.scrollIntoView({ block: "nearest" });
|
|
980
|
-
}
|
|
981
|
-
}, [byId, onSelect]);
|
|
982
|
-
const handleNodeClick = useCallback((_event, node) => select(node.id), [select]);
|
|
983
|
-
const handleKeyDown = useCallback((event) => {
|
|
984
|
-
if (!(event.target instanceof HTMLElement)) return;
|
|
985
|
-
const id = event.target.closest(".react-flow__node")?.getAttribute("data-id");
|
|
986
|
-
if (!id || !byId.has(id)) return;
|
|
987
|
-
if (event.key === "Escape") {
|
|
988
|
-
event.preventDefault();
|
|
989
|
-
event.stopPropagation();
|
|
990
|
-
wrapperRef.current?.focus();
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
if (!event.target.classList.contains("react-flow__node")) return;
|
|
994
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
995
|
-
event.preventDefault();
|
|
996
|
-
event.stopPropagation();
|
|
997
|
-
select(id);
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
const origin = layout?.positions.get(id);
|
|
1001
|
-
const vector = {
|
|
1002
|
-
ArrowLeft: [-1, 0],
|
|
1003
|
-
ArrowRight: [1, 0],
|
|
1004
|
-
ArrowUp: [0, -1],
|
|
1005
|
-
ArrowDown: [0, 1]
|
|
1006
|
-
}[event.key];
|
|
1007
|
-
const target = event.key === "Home" ? topology.agentIds[0] : origin && vector ? [...layout?.positions ?? []].filter(([key, p]) => byId.has(key) && key !== id && (p.x - origin.x) * vector[0] + (p.y - origin.y) * vector[1] > 0).sort((a, b) => Math.hypot(a[1].x - origin.x, a[1].y - origin.y) - Math.hypot(b[1].x - origin.x, b[1].y - origin.y))[0]?.[0] : null;
|
|
1008
|
-
if (!target) return;
|
|
1009
|
-
event.preventDefault();
|
|
1010
|
-
event.stopPropagation();
|
|
1011
|
-
const position = layout?.positions.get(target), size = rfRef.current?.getInternalNode(target)?.measured;
|
|
1012
|
-
if (position) rfRef.current?.setCenter(position.x + (size?.width ?? card.width) / 2, position.y + (size?.height ?? card.height) / 2, { zoom: 1 });
|
|
1013
|
-
const focus = () => wrapperRef.current?.querySelector(`.react-flow__node[data-id="${CSS.escape(target)}"]`);
|
|
1014
|
-
const visible = focus();
|
|
1015
|
-
if (visible) visible.focus();
|
|
1016
|
-
else requestAnimationFrame(() => requestAnimationFrame(() => focus()?.focus()));
|
|
1017
|
-
}, [
|
|
1018
|
-
byId,
|
|
1019
|
-
select,
|
|
1020
|
-
layout,
|
|
1021
|
-
topology,
|
|
1022
|
-
card,
|
|
1023
|
-
animateMotion
|
|
1024
|
-
]);
|
|
1025
|
-
const activityRank = (agent) => agent.status === "needs-you" ? 0 : agent.status === "failed" ? 1 : quietActivity(agent, clock) ? 2 : agent.status === "waiting" ? 3 : agent.status === "running" ? 4 : 5;
|
|
1026
|
-
const parentOf = new Map(edges.filter((e) => e.kind === "spawn").map((e) => [e.to, e.from]));
|
|
1027
|
-
const depthOf = (id) => {
|
|
1028
|
-
const seen = /* @__PURE__ */ new Set([id]);
|
|
1029
|
-
let depth = 0;
|
|
1030
|
-
while (parentOf.has(id) && !seen.has(parentOf.get(id))) {
|
|
1031
|
-
id = parentOf.get(id);
|
|
1032
|
-
seen.add(id);
|
|
1033
|
-
depth++;
|
|
1034
|
-
}
|
|
1035
|
-
return depth;
|
|
1036
|
-
};
|
|
1037
|
-
const readingOrder = useRef([]), known = new Set(readingOrder.current);
|
|
1038
|
-
readingOrder.current = [...readingOrder.current.filter((id) => byId.has(id)), ...nodes.filter((agent) => !known.has(agent.id)).sort((a, b) => activityRank(a) - activityRank(b)).map((agent) => agent.id)];
|
|
1039
|
-
const phoneNodes = readingOrder.current.map((id) => byId.get(id));
|
|
1040
|
-
useEffect(() => {
|
|
1041
|
-
const at = phoneNodes.findIndex((node) => node.id === selectedId);
|
|
1042
|
-
if (at >= 0) setPhonePage(Math.floor(at / PHONE_PAGE));
|
|
1043
|
-
}, [selectedId]);
|
|
1044
|
-
useEffect(() => {
|
|
1045
|
-
if (!narrow || !selectedId) return;
|
|
1046
|
-
const frame = requestAnimationFrame(() => wrapperRef.current?.querySelector(`[data-agent-id="${CSS.escape(selectedId)}"]`)?.scrollIntoView({ block: "nearest" }));
|
|
1047
|
-
return () => cancelAnimationFrame(frame);
|
|
1048
|
-
}, [
|
|
1049
|
-
selectedId,
|
|
1050
|
-
phonePage,
|
|
1051
|
-
narrow
|
|
1052
|
-
]);
|
|
1053
|
-
const first = Math.min(phonePage * PHONE_PAGE, Math.max(0, Math.ceil(nodes.length / PHONE_PAGE) - 1) * PHONE_PAGE);
|
|
1054
|
-
const counts = [
|
|
1055
|
-
"needs-you",
|
|
1056
|
-
"failed",
|
|
1057
|
-
"quiet"
|
|
1058
|
-
].map((status) => {
|
|
1059
|
-
const matches = nodes.filter((agent) => status === "quiet" ? quietActivity(agent, clock) : agent.status === status);
|
|
1060
|
-
return {
|
|
1061
|
-
status,
|
|
1062
|
-
matches,
|
|
1063
|
-
current: matches.find((agent) => agent.id === selectedId) ?? matches[0]
|
|
1064
|
-
};
|
|
1065
|
-
});
|
|
1066
|
-
const caption = /* @__PURE__ */ jsxs("div", {
|
|
1067
|
-
className: "flex min-h-10 shrink-0 flex-wrap items-center gap-x-3 gap-y-1 px-3 py-1 text-xs text-muted-foreground",
|
|
1068
|
-
"aria-label": "Run status",
|
|
1069
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
1070
|
-
className: "shrink-0 whitespace-nowrap",
|
|
1071
|
-
children: [nodes.length, " agents"]
|
|
1072
|
-
}), counts.filter((group) => group.matches.length).map((group) => /* @__PURE__ */ jsxs("button", {
|
|
1073
|
-
type: "button",
|
|
1074
|
-
className: "ag-status-button rounded-full border border-border px-2 py-1",
|
|
1075
|
-
style: { color: group.status === "quiet" ? "var(--surface-warning-text)" : agentStatus(group.status).color },
|
|
1076
|
-
onClick: () => {
|
|
1077
|
-
const index = group.matches.findIndex((agent) => agent.id === selectedId);
|
|
1078
|
-
const next = group.matches[(index + 1) % group.matches.length];
|
|
1079
|
-
select(next.id);
|
|
1080
|
-
const p = layout?.positions.get(next.id), size = rfRef.current?.getInternalNode(next.id)?.measured;
|
|
1081
|
-
if (p) rfRef.current?.setCenter(p.x + (size?.width ?? card.width) / 2, p.y + (size?.height ?? card.height) / 2, {
|
|
1082
|
-
zoom: 1,
|
|
1083
|
-
duration: animateMotion ? tokenMilliseconds("--duration-base", wrapperRef.current) : 0
|
|
1084
|
-
});
|
|
1085
|
-
},
|
|
1086
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
1087
|
-
className: "shrink-0",
|
|
1088
|
-
children: [
|
|
1089
|
-
group.matches.length,
|
|
1090
|
-
" ",
|
|
1091
|
-
group.status === "needs-you" ? "needs you" : group.status
|
|
1092
|
-
]
|
|
1093
|
-
}), /* @__PURE__ */ jsxs("span", {
|
|
1094
|
-
className: "ag-caption-target",
|
|
1095
|
-
children: [group.current.label, group.status === "failed" && /* @__PURE__ */ jsxs("span", {
|
|
1096
|
-
className: "font-normal",
|
|
1097
|
-
children: [" · ", group.current.result ?? group.current.turn ?? "Failure cause not recorded"]
|
|
1098
|
-
})]
|
|
1099
|
-
})]
|
|
1100
|
-
}, group.status))]
|
|
1101
|
-
});
|
|
1102
|
-
if (nodes.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1103
|
-
className: `flex items-center justify-center rounded-lg border border-border border-dashed p-4 text-center text-muted-foreground text-xs ${className ?? ""}`,
|
|
1104
|
-
children: emptyState ?? "No agents yet"
|
|
1105
|
-
});
|
|
1106
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1107
|
-
ref: (el) => {
|
|
1108
|
-
wrapperRef.current = el;
|
|
1109
|
-
measureCanvas(el);
|
|
1110
|
-
},
|
|
1111
|
-
className: `agent-graph ag-live relative flex flex-col ${className ?? ""}`,
|
|
1112
|
-
tabIndex: -1,
|
|
1113
|
-
onKeyDownCapture: (event) => {
|
|
1114
|
-
if (event.target instanceof HTMLElement && event.target.closest("[data-agent-metrics]")) return;
|
|
1115
|
-
if (event.key === "Tab" && !event.shiftKey && hovered) {
|
|
1116
|
-
event.preventDefault();
|
|
1117
|
-
document.getElementById(detailsId)?.focus();
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
if (event.key === "Escape") hideDetails();
|
|
1121
|
-
handleKeyDown(event);
|
|
1122
|
-
},
|
|
1123
|
-
onFocusCapture: (event) => {
|
|
1124
|
-
if (!(event.target instanceof HTMLElement) || returningFocus.current) return;
|
|
1125
|
-
keepDetails();
|
|
1126
|
-
if (event.target.closest("[data-agent-metrics]")) return;
|
|
1127
|
-
if (!event.target.matches(":focus-visible")) {
|
|
1128
|
-
hideDetails();
|
|
1129
|
-
return;
|
|
1130
|
-
}
|
|
1131
|
-
const node = event.target.closest(".react-flow__node, [data-agent-id]");
|
|
1132
|
-
const id = node?.dataset.id ?? node?.dataset.agentId;
|
|
1133
|
-
hoverTarget.current = node ?? null;
|
|
1134
|
-
setHovered(id && byId.has(id) ? {
|
|
1135
|
-
id,
|
|
1136
|
-
rect: node.getBoundingClientRect()
|
|
1137
|
-
} : null);
|
|
1138
|
-
},
|
|
1139
|
-
onBlurCapture: (event) => {
|
|
1140
|
-
if (!event.currentTarget.contains(event.relatedTarget) && event.relatedTarget?.id !== detailsId) hideDetails();
|
|
1141
|
-
},
|
|
1142
|
-
"data-layout": narrow ? "list" : layout && shown ? "ready" : "pending",
|
|
1143
|
-
"data-columns": layout?.columns,
|
|
1144
|
-
children: [
|
|
1145
|
-
caption,
|
|
1146
|
-
showLatestEvents && live && /* @__PURE__ */ jsx(LatestEvents, {
|
|
1147
|
-
agents: nodes,
|
|
1148
|
-
now,
|
|
1149
|
-
live,
|
|
1150
|
-
selectedId,
|
|
1151
|
-
onSelect: onSelect ? select : void 0
|
|
1152
|
-
}),
|
|
1153
|
-
narrow ? /* @__PURE__ */ jsxs("ul", {
|
|
1154
|
-
className: "ag-graph-viewport ag-phone-list overflow-auto",
|
|
1155
|
-
"aria-label": "Agents by attention",
|
|
1156
|
-
children: [phoneNodes.slice(first, first + PHONE_PAGE).map((agent) => /* @__PURE__ */ jsx("li", {
|
|
1157
|
-
style: { ["--depth"]: depthOf(agent.id) },
|
|
1158
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
1159
|
-
role: "button",
|
|
1160
|
-
tabIndex: 0,
|
|
1161
|
-
"aria-pressed": selectedId === agent.id,
|
|
1162
|
-
"aria-label": agentSummary(agent),
|
|
1163
|
-
"data-agent-id": agent.id,
|
|
1164
|
-
className: "ag-phone-select",
|
|
1165
|
-
onClick: () => select(agent.id),
|
|
1166
|
-
onKeyDown: (e) => {
|
|
1167
|
-
if ((e.key === "Enter" || e.key === " ") && e.target === e.currentTarget) {
|
|
1168
|
-
e.preventDefault();
|
|
1169
|
-
select(agent.id);
|
|
1170
|
-
}
|
|
1171
|
-
},
|
|
1172
|
-
children: live || agent.status === "succeeded" || agent.status === "stopped" ? /* @__PURE__ */ jsx(LiveAgentCardView, {
|
|
1173
|
-
agent,
|
|
1174
|
-
parentLabel: byId.get(parentOf.get(agent.id) ?? "")?.label,
|
|
1175
|
-
selected: selectedId === agent.id,
|
|
1176
|
-
now: clock,
|
|
1177
|
-
renderEventIcon,
|
|
1178
|
-
onOpen: onSelect ? () => select(agent.id) : void 0
|
|
1179
|
-
}) : /* @__PURE__ */ jsx(AgentCardView, {
|
|
1180
|
-
agent,
|
|
1181
|
-
parentLabel: byId.get(parentOf.get(agent.id) ?? "")?.label,
|
|
1182
|
-
selected: selectedId === agent.id,
|
|
1183
|
-
now: clock,
|
|
1184
|
-
direction,
|
|
1185
|
-
onOpen: onSelect ? () => select(agent.id) : void 0
|
|
1186
|
-
})
|
|
1187
|
-
})
|
|
1188
|
-
}, agent.id)), nodes.length > PHONE_PAGE && /* @__PURE__ */ jsxs("li", {
|
|
1189
|
-
className: "flex items-center justify-between gap-2 border-t border-border pt-3 text-sm",
|
|
1190
|
-
"aria-label": "Agent pages",
|
|
1191
|
-
children: [
|
|
1192
|
-
/* @__PURE__ */ jsx("button", {
|
|
1193
|
-
type: "button",
|
|
1194
|
-
className: "rounded px-2 py-1 hover:bg-muted disabled:opacity-40 focus-visible:outline",
|
|
1195
|
-
disabled: !first,
|
|
1196
|
-
onClick: () => {
|
|
1197
|
-
setPhonePage((page) => page - 1);
|
|
1198
|
-
wrapperRef.current?.querySelector(".ag-phone-list")?.scrollTo({ top: 0 });
|
|
1199
|
-
},
|
|
1200
|
-
children: "Previous"
|
|
1201
|
-
}),
|
|
1202
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
1203
|
-
first + 1,
|
|
1204
|
-
"–",
|
|
1205
|
-
Math.min(nodes.length, first + PHONE_PAGE),
|
|
1206
|
-
" of ",
|
|
1207
|
-
nodes.length
|
|
1208
|
-
] }),
|
|
1209
|
-
/* @__PURE__ */ jsx("button", {
|
|
1210
|
-
type: "button",
|
|
1211
|
-
className: "rounded px-2 py-1 hover:bg-muted disabled:opacity-40 focus-visible:outline",
|
|
1212
|
-
disabled: first + PHONE_PAGE >= nodes.length,
|
|
1213
|
-
onClick: () => {
|
|
1214
|
-
setPhonePage((page) => page + 1);
|
|
1215
|
-
wrapperRef.current?.querySelector(".ag-phone-list")?.scrollTo({ top: 0 });
|
|
1216
|
-
},
|
|
1217
|
-
children: "Next"
|
|
1218
|
-
})
|
|
1219
|
-
]
|
|
1220
|
-
})]
|
|
1221
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
1222
|
-
className: "ag-graph-viewport min-h-0 flex-1",
|
|
1223
|
-
style: { opacity: shown ? 1 : 0 },
|
|
1224
|
-
children: /* @__PURE__ */ jsxs(ReactFlow, {
|
|
1225
|
-
nodes: flowNodes,
|
|
1226
|
-
edges: flowEdges,
|
|
1227
|
-
nodeTypes: NODE_TYPES,
|
|
1228
|
-
edgeTypes: EDGE_TYPES,
|
|
1229
|
-
colorMode,
|
|
1230
|
-
onInit: (inst) => {
|
|
1231
|
-
rfRef.current = inst;
|
|
1232
|
-
setReady(true);
|
|
1233
|
-
},
|
|
1234
|
-
onNodeClick: handleNodeClick,
|
|
1235
|
-
onNodeMouseEnter: (event, node) => {
|
|
1236
|
-
keepDetails();
|
|
1237
|
-
hoverTarget.current = event.target.closest(".react-flow__node");
|
|
1238
|
-
if (hoverTarget.current) setHovered({
|
|
1239
|
-
id: node.id,
|
|
1240
|
-
rect: hoverTarget.current.getBoundingClientRect()
|
|
1241
|
-
});
|
|
1242
|
-
},
|
|
1243
|
-
onNodeMouseLeave: leaveDetails,
|
|
1244
|
-
proOptions: { hideAttribution: true },
|
|
1245
|
-
onlyRenderVisibleElements: true,
|
|
1246
|
-
nodesDraggable: false,
|
|
1247
|
-
nodesConnectable: false,
|
|
1248
|
-
elementsSelectable: false,
|
|
1249
|
-
nodesFocusable: true,
|
|
1250
|
-
edgesFocusable: false,
|
|
1251
|
-
deleteKeyCode: null,
|
|
1252
|
-
panOnScroll: true,
|
|
1253
|
-
zoomOnScroll: false,
|
|
1254
|
-
zoomOnPinch: true,
|
|
1255
|
-
zoomOnDoubleClick: false,
|
|
1256
|
-
panOnDrag: true,
|
|
1257
|
-
minZoom,
|
|
1258
|
-
maxZoom: 1.75,
|
|
1259
|
-
"aria-label": "Agent topology",
|
|
1260
|
-
children: [
|
|
1261
|
-
/* @__PURE__ */ jsx(Background, {
|
|
1262
|
-
bgColor: "var(--bg-root)",
|
|
1263
|
-
gap: 16,
|
|
1264
|
-
size: 1,
|
|
1265
|
-
color: "hsl(var(--foreground) / 0.06)"
|
|
1266
|
-
}),
|
|
1267
|
-
layout && canvas && (layout.width > canvas.width || layout.height > canvas.height) && /* @__PURE__ */ jsx(MiniMap, {
|
|
1268
|
-
pannable: true,
|
|
1269
|
-
zoomable: true,
|
|
1270
|
-
position: "bottom-left",
|
|
1271
|
-
style: {
|
|
1272
|
-
width: 168,
|
|
1273
|
-
height: 104
|
|
1274
|
-
},
|
|
1275
|
-
className: "!hidden sm:!block",
|
|
1276
|
-
ariaLabel: "Agent topology overview"
|
|
1277
|
-
}),
|
|
1278
|
-
/* @__PURE__ */ jsx(Controls, {
|
|
1279
|
-
showInteractive: false,
|
|
1280
|
-
position: "bottom-right",
|
|
1281
|
-
fitViewOptions: {
|
|
1282
|
-
padding: `${FIT_PADDING}px`,
|
|
1283
|
-
maxZoom: 1
|
|
1284
|
-
}
|
|
1285
|
-
})
|
|
1286
|
-
]
|
|
1287
|
-
})
|
|
1288
|
-
}),
|
|
1289
|
-
hovered && byId.has(hovered.id) && /* @__PURE__ */ jsx(AgentMetrics, {
|
|
1290
|
-
anchor: wrapperRef.current,
|
|
1291
|
-
mode: colorMode,
|
|
1292
|
-
id: detailsId,
|
|
1293
|
-
agent: byId.get(hovered.id),
|
|
1294
|
-
rect: hovered.rect,
|
|
1295
|
-
onEnter: keepDetails,
|
|
1296
|
-
onLeave: leaveDetails,
|
|
1297
|
-
onClose: () => {
|
|
1298
|
-
hideDetails();
|
|
1299
|
-
returningFocus.current = true;
|
|
1300
|
-
hoverTarget.current?.focus({ preventScroll: true });
|
|
1301
|
-
returningFocus.current = false;
|
|
1302
|
-
}
|
|
1303
|
-
})
|
|
1304
|
-
]
|
|
1305
|
-
});
|
|
1306
|
-
}
|
|
1307
|
-
/** Metrics stay at reading size outside the zoomed canvas and its clipping container. */
|
|
1308
|
-
function AgentMetrics({ anchor, mode, id, agent, rect, onEnter, onLeave, onClose }) {
|
|
1309
|
-
const [theme, setTheme] = useState({});
|
|
1310
|
-
useLayoutEffect(() => {
|
|
1311
|
-
if (!anchor) return;
|
|
1312
|
-
const read = () => {
|
|
1313
|
-
const css = getComputedStyle(anchor);
|
|
1314
|
-
setTheme(Object.fromEntries(Array.from(css).filter((name) => name.startsWith("--") && !name.startsWith("--tw-")).map((name) => [name, css.getPropertyValue(name)])));
|
|
1315
|
-
};
|
|
1316
|
-
read();
|
|
1317
|
-
const observer = new MutationObserver(read);
|
|
1318
|
-
for (let el = anchor; el; el = el.parentElement) observer.observe(el, {
|
|
1319
|
-
attributes: true,
|
|
1320
|
-
attributeFilter: [
|
|
1321
|
-
"class",
|
|
1322
|
-
"style",
|
|
1323
|
-
"data-theme",
|
|
1324
|
-
"data-sandbox-theme"
|
|
1325
|
-
]
|
|
1326
|
-
});
|
|
1327
|
-
return () => observer.disconnect();
|
|
1328
|
-
}, [anchor]);
|
|
1329
|
-
const facts = new Map([
|
|
1330
|
-
{
|
|
1331
|
-
label: "Tokens",
|
|
1332
|
-
value: fmtTokenTotal(agent.tokens) ?? "Not recorded"
|
|
1333
|
-
},
|
|
1334
|
-
{
|
|
1335
|
-
label: "Cost",
|
|
1336
|
-
value: fmtCost(agent.costUsd ?? void 0) ?? "Not recorded"
|
|
1337
|
-
},
|
|
1338
|
-
...agent.facts ?? []
|
|
1339
|
-
].map((fact) => [fact.label.toLowerCase(), fact]));
|
|
1340
|
-
const width = Math.min(420, window.innerWidth - 24), height = Math.min(560, window.innerHeight - 24);
|
|
1341
|
-
const left = Math.max(12, Math.min(rect.right + 12 + width <= window.innerWidth ? rect.right + 12 : rect.left - width - 12, window.innerWidth - width - 12));
|
|
1342
|
-
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
1343
|
-
id,
|
|
1344
|
-
"data-agent-metrics": true,
|
|
1345
|
-
"data-theme": anchor?.closest("[data-theme]")?.getAttribute("data-theme") ?? mode,
|
|
1346
|
-
role: "tooltip",
|
|
1347
|
-
tabIndex: 0,
|
|
1348
|
-
onMouseEnter: onEnter,
|
|
1349
|
-
onMouseLeave: onLeave,
|
|
1350
|
-
onWheel: (event) => event.stopPropagation(),
|
|
1351
|
-
onBlur: onLeave,
|
|
1352
|
-
onKeyDown: (event) => {
|
|
1353
|
-
if (event.key === "Escape") {
|
|
1354
|
-
event.preventDefault();
|
|
1355
|
-
onClose();
|
|
1356
|
-
}
|
|
1357
|
-
},
|
|
1358
|
-
className: `${mode} rounded-xl border border-border bg-card p-4 text-sm text-foreground shadow-xl`,
|
|
1359
|
-
style: {
|
|
1360
|
-
...theme,
|
|
1361
|
-
colorScheme: mode,
|
|
1362
|
-
position: "fixed",
|
|
1363
|
-
zIndex: 1e3,
|
|
1364
|
-
width,
|
|
1365
|
-
maxHeight: height,
|
|
1366
|
-
overflow: "auto",
|
|
1367
|
-
left,
|
|
1368
|
-
top: Math.max(12, Math.min(rect.top, window.innerHeight - height - 12))
|
|
1369
|
-
},
|
|
1370
|
-
children: [
|
|
1371
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1372
|
-
className: "mb-2 flex items-center justify-between gap-3",
|
|
1373
|
-
children: [/* @__PURE__ */ jsx("strong", { children: agent.label }), /* @__PURE__ */ jsx("span", { children: agentStatus(agent.status).label })]
|
|
1374
|
-
}),
|
|
1375
|
-
/* @__PURE__ */ jsx("p", {
|
|
1376
|
-
className: "mb-3 text-muted-foreground",
|
|
1377
|
-
children: [
|
|
1378
|
-
agent.model,
|
|
1379
|
-
agent.harness,
|
|
1380
|
-
agent.role
|
|
1381
|
-
].filter(Boolean).join(" · ")
|
|
1382
|
-
}),
|
|
1383
|
-
/* @__PURE__ */ jsx("dl", {
|
|
1384
|
-
className: "grid grid-cols-[minmax(0,1fr)_auto] gap-x-5 gap-y-1.5",
|
|
1385
|
-
children: [...facts.values()].map((fact) => /* @__PURE__ */ jsxs("div", {
|
|
1386
|
-
className: "contents",
|
|
1387
|
-
children: [/* @__PURE__ */ jsx("dt", {
|
|
1388
|
-
className: "min-w-0 break-words text-muted-foreground",
|
|
1389
|
-
children: fact.label
|
|
1390
|
-
}), /* @__PURE__ */ jsx("dd", {
|
|
1391
|
-
className: "m-0 min-w-0 max-w-64 whitespace-normal break-words text-right tabular-nums",
|
|
1392
|
-
children: fact.value
|
|
1393
|
-
})]
|
|
1394
|
-
}, fact.label))
|
|
1395
|
-
}),
|
|
1396
|
-
agent.status === "failed" && /* @__PURE__ */ jsx("p", {
|
|
1397
|
-
className: "mt-3 whitespace-pre-wrap break-words text-[var(--surface-danger-text)]",
|
|
1398
|
-
children: agent.result ?? agent.turn ?? "Failure cause not recorded"
|
|
1399
|
-
})
|
|
1400
|
-
]
|
|
1401
|
-
}), document.body);
|
|
1402
|
-
}
|
|
1403
|
-
export { AgentGraphCanvas };
|