create-claude-flow-codex-ui 0.1.0-alpha.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.
@@ -0,0 +1,1004 @@
1
+ const STATE_URL = "./state/swarm-state.json";
2
+ const WS_PATH = "/__swarm_ws";
3
+ const POLL_INTERVAL_MS = 4000;
4
+ const WS_RECONNECT_BASE_MS = 800;
5
+ const WS_RECONNECT_MAX_MS = 10000;
6
+ const SIMULATION_TICK_MS = 1150;
7
+ const CLOCK_TICK_MS = 1000;
8
+ const MAX_EVENTS = 16;
9
+ const FILTER_SEQUENCE = ["all", "active", "warn", "error", "idle"];
10
+
11
+ const elements = {
12
+ kpiGrid: document.getElementById("kpi-grid"),
13
+ swarmMeta: document.getElementById("swarm-meta"),
14
+ livePill: document.getElementById("live-pill"),
15
+ liveLabel: document.getElementById("live-label"),
16
+ topology: document.getElementById("topology"),
17
+ eventStream: document.getElementById("event-stream"),
18
+ agentGrid: document.getElementById("agent-grid"),
19
+ taskBody: document.getElementById("task-body"),
20
+ agentSummary: document.getElementById("agent-summary"),
21
+ taskSummary: document.getElementById("task-summary"),
22
+ signalLane: document.getElementById("signal-lane"),
23
+ signalSummary: document.getElementById("signal-summary"),
24
+ simulateToggle: document.getElementById("simulate-toggle"),
25
+ statusFilter: document.getElementById("status-filter"),
26
+ };
27
+
28
+ const ns = "http://www.w3.org/2000/svg";
29
+ const byId = new Map();
30
+
31
+ let state = null;
32
+ let simulationEnabled = false;
33
+ let ws = null;
34
+ let wsConnected = false;
35
+ let pollTimer = 0;
36
+ let simulationTimer = 0;
37
+ let clockTimer = 0;
38
+ let reconnectTimer = 0;
39
+ let reconnectAttempt = 0;
40
+ let lastSyncOk = true;
41
+ let consecutiveFetchFailures = 0;
42
+ let filterIndex = 0;
43
+ let firstRender = true;
44
+ let lastDataSignature = "";
45
+ let lastEventsSignature = "";
46
+
47
+ bootstrap();
48
+
49
+ function bootstrap() {
50
+ const params = new URLSearchParams(window.location.search);
51
+ simulationEnabled = params.get("simulate") === "1";
52
+
53
+ const filterFromUrl = (params.get("filter") || "all").toLowerCase();
54
+ const nextFilterIndex = FILTER_SEQUENCE.indexOf(filterFromUrl);
55
+ filterIndex = nextFilterIndex >= 0 ? nextFilterIndex : 0;
56
+
57
+ elements.simulateToggle?.addEventListener("click", toggleSimulation);
58
+ elements.statusFilter?.addEventListener("click", cycleFilter);
59
+
60
+ syncSimulationButton();
61
+ syncFilterButton();
62
+
63
+ ensurePolling(true, true);
64
+ connectWebSocket();
65
+
66
+ simulationTimer = window.setInterval(() => {
67
+ if (simulationEnabled) {
68
+ runSimulationTick();
69
+ render(false);
70
+ }
71
+ }, SIMULATION_TICK_MS);
72
+
73
+ clockTimer = window.setInterval(() => {
74
+ if (!state) {
75
+ return;
76
+ }
77
+ const filteredCount = filterAgents(state.agents, currentFilter()).length;
78
+ renderHeader(filteredCount);
79
+ }, CLOCK_TICK_MS);
80
+
81
+ window.addEventListener("beforeunload", teardown);
82
+ }
83
+
84
+ function teardown() {
85
+ if (pollTimer) {
86
+ window.clearInterval(pollTimer);
87
+ pollTimer = 0;
88
+ }
89
+ if (simulationTimer) {
90
+ window.clearInterval(simulationTimer);
91
+ simulationTimer = 0;
92
+ }
93
+ if (clockTimer) {
94
+ window.clearInterval(clockTimer);
95
+ clockTimer = 0;
96
+ }
97
+ if (reconnectTimer) {
98
+ window.clearTimeout(reconnectTimer);
99
+ reconnectTimer = 0;
100
+ }
101
+ if (ws) {
102
+ ws.close();
103
+ ws = null;
104
+ }
105
+ }
106
+
107
+ function toggleSimulation() {
108
+ simulationEnabled = !simulationEnabled;
109
+ syncSimulationButton();
110
+
111
+ if (simulationEnabled) {
112
+ addEvent("warn", "Simulation enabled for cinematic playback.");
113
+ } else {
114
+ addEvent("info", "Simulation disabled. Showing only live source telemetry.");
115
+ ensurePolling(true, true);
116
+ }
117
+
118
+ lastDataSignature = "";
119
+ render(false);
120
+ }
121
+
122
+ function cycleFilter() {
123
+ filterIndex = (filterIndex + 1) % FILTER_SEQUENCE.length;
124
+ syncFilterButton();
125
+ addEvent("info", `Agent filter set to ${currentFilterLabel()}.`);
126
+ lastDataSignature = "";
127
+ render(false);
128
+ }
129
+
130
+ function currentFilter() {
131
+ return FILTER_SEQUENCE[filterIndex];
132
+ }
133
+
134
+ function currentFilterLabel() {
135
+ const filter = currentFilter();
136
+ if (filter === "active") {
137
+ return "Active";
138
+ }
139
+ if (filter === "warn") {
140
+ return "Attention";
141
+ }
142
+ if (filter === "error") {
143
+ return "Critical";
144
+ }
145
+ if (filter === "idle") {
146
+ return "Idle";
147
+ }
148
+ return "All";
149
+ }
150
+
151
+ function syncSimulationButton() {
152
+ elements.simulateToggle?.classList.toggle("on", simulationEnabled);
153
+ if (elements.simulateToggle) {
154
+ elements.simulateToggle.textContent = simulationEnabled
155
+ ? "Simulation: On"
156
+ : "Simulation: Off";
157
+ }
158
+ }
159
+
160
+ function syncFilterButton() {
161
+ if (elements.statusFilter) {
162
+ elements.statusFilter.textContent = `Filter: ${currentFilterLabel()}`;
163
+ }
164
+ }
165
+
166
+ function connectWebSocket() {
167
+ if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
168
+ return;
169
+ }
170
+
171
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
172
+ const url = `${protocol}//${window.location.host}${WS_PATH}`;
173
+
174
+ try {
175
+ ws = new WebSocket(url);
176
+ } catch {
177
+ scheduleReconnect();
178
+ return;
179
+ }
180
+
181
+ ws.addEventListener("open", () => {
182
+ wsConnected = true;
183
+ reconnectAttempt = 0;
184
+ ensurePolling(false);
185
+ renderHeader(state ? filterAgents(state.agents, currentFilter()).length : 0);
186
+ });
187
+
188
+ ws.addEventListener("message", (event) => {
189
+ let payload;
190
+ try {
191
+ payload = JSON.parse(String(event.data));
192
+ } catch {
193
+ return;
194
+ }
195
+
196
+ if (!payload || payload.type !== "state") {
197
+ return;
198
+ }
199
+
200
+ applyIncomingState(payload.state, false, "ws");
201
+ });
202
+
203
+ ws.addEventListener("close", () => {
204
+ handleSocketDisconnect();
205
+ });
206
+
207
+ ws.addEventListener("error", () => {
208
+ handleSocketDisconnect();
209
+ });
210
+ }
211
+
212
+ function handleSocketDisconnect() {
213
+ if (wsConnected) {
214
+ addEvent("warn", "WebSocket disconnected, switching to HTTP polling.");
215
+ }
216
+
217
+ wsConnected = false;
218
+ ws = null;
219
+ ensurePolling(true);
220
+ scheduleReconnect();
221
+
222
+ if (!state) {
223
+ return;
224
+ }
225
+ renderHeader(filterAgents(state.agents, currentFilter()).length);
226
+ }
227
+
228
+ function scheduleReconnect() {
229
+ if (reconnectTimer) {
230
+ return;
231
+ }
232
+
233
+ const delay = Math.min(
234
+ WS_RECONNECT_BASE_MS * Math.pow(2, reconnectAttempt),
235
+ WS_RECONNECT_MAX_MS
236
+ );
237
+ reconnectAttempt += 1;
238
+
239
+ reconnectTimer = window.setTimeout(() => {
240
+ reconnectTimer = 0;
241
+ connectWebSocket();
242
+ }, delay);
243
+ }
244
+
245
+ function ensurePolling(enabled, triggerImmediate = false) {
246
+ if (enabled) {
247
+ if (!pollTimer) {
248
+ pollTimer = window.setInterval(() => refresh(false), POLL_INTERVAL_MS);
249
+ }
250
+ if (triggerImmediate) {
251
+ refresh(true);
252
+ }
253
+ return;
254
+ }
255
+
256
+ if (pollTimer) {
257
+ window.clearInterval(pollTimer);
258
+ pollTimer = 0;
259
+ }
260
+ }
261
+
262
+ async function refresh(initial) {
263
+ try {
264
+ const response = await fetch(`${STATE_URL}?t=${Date.now()}`, {
265
+ cache: "no-store",
266
+ });
267
+
268
+ if (!response.ok) {
269
+ throw new Error(`HTTP ${response.status}`);
270
+ }
271
+
272
+ const incoming = await response.json();
273
+ applyIncomingState(incoming, initial, "poll");
274
+ } catch (error) {
275
+ if (!state) {
276
+ state = normalizeState({});
277
+ }
278
+
279
+ lastSyncOk = false;
280
+ consecutiveFetchFailures += 1;
281
+
282
+ if (consecutiveFetchFailures === 1 || consecutiveFetchFailures % 5 === 0) {
283
+ addEvent("error", `Source refresh failed (${error.message}).`);
284
+ }
285
+
286
+ render(initial);
287
+ }
288
+ }
289
+
290
+ function applyIncomingState(raw, initial, source) {
291
+ const incoming = normalizeState(raw);
292
+
293
+ if (!state || !simulationEnabled || initial) {
294
+ state = incoming;
295
+ }
296
+
297
+ lastSyncOk = true;
298
+ consecutiveFetchFailures = 0;
299
+
300
+ if (source === "ws") {
301
+ wsConnected = true;
302
+ }
303
+
304
+ render(initial);
305
+ }
306
+
307
+ function normalizeState(raw) {
308
+ const normalized = {
309
+ swarmId: raw?.swarmId || "unknown",
310
+ orchestrationId: raw?.orchestrationId || "n/a",
311
+ lastUpdated: raw?.lastUpdated || new Date().toISOString(),
312
+ agents: Array.isArray(raw?.agents) ? raw.agents : [],
313
+ tasks: Array.isArray(raw?.tasks) ? raw.tasks : [],
314
+ events: Array.isArray(raw?.events) ? raw.events : [],
315
+ };
316
+
317
+ byId.clear();
318
+ normalized.agents.forEach((agent) => {
319
+ byId.set(agent.id, agent);
320
+ });
321
+
322
+ return normalized;
323
+ }
324
+
325
+ function runSimulationTick() {
326
+ if (!state || !state.tasks.length) {
327
+ return;
328
+ }
329
+
330
+ const touched = [];
331
+ const inProgress = state.tasks.filter((task) => task.status === "in_progress");
332
+ const pending = state.tasks.filter((task) => task.status === "pending");
333
+
334
+ if (pending.length && Math.random() < 0.2) {
335
+ const promote = pending[rand(0, pending.length - 1)];
336
+ promote.status = "in_progress";
337
+ promote.progress = Math.max(1, promote.progress || 0);
338
+ touched.push(`${promote.id} moved to in_progress.`);
339
+ }
340
+
341
+ for (const task of inProgress) {
342
+ const delta = rand(2, 8);
343
+ const next = clamp((task.progress || 0) + delta, 0, 100);
344
+ task.progress = next;
345
+
346
+ if (next >= 100) {
347
+ task.status = "completed";
348
+ touched.push(`${task.id} completed by ${task.assignedTo}.`);
349
+ }
350
+
351
+ const agent = byId.get(task.assignedTo);
352
+ if (!agent) {
353
+ continue;
354
+ }
355
+
356
+ agent.progress = next;
357
+ agent.status = next >= 100 ? "idle" : "working";
358
+ agent.health = clamp((agent.health || 1) - 0.006 + Math.random() * 0.016, 0.86, 1);
359
+ }
360
+
361
+ state.lastUpdated = new Date().toISOString();
362
+ touched.slice(0, 3).forEach((message) => addEvent("success", message));
363
+ }
364
+
365
+ function render(initial) {
366
+ if (!state) {
367
+ return;
368
+ }
369
+
370
+ const filteredAgents = filterAgents(state.agents, currentFilter());
371
+ renderHeader(filteredAgents.length);
372
+
373
+ const dataSignature = buildDataSignature(state, currentFilter());
374
+ const shouldRenderBody = initial || simulationEnabled || dataSignature !== lastDataSignature;
375
+ if (!shouldRenderBody) {
376
+ return;
377
+ }
378
+
379
+ renderKpis();
380
+ renderTopology(filteredAgents);
381
+ renderEvents();
382
+ renderSignalLane();
383
+ renderAgents(filteredAgents);
384
+ renderTasks();
385
+
386
+ lastDataSignature = dataSignature;
387
+
388
+ if (initial && firstRender) {
389
+ window.requestAnimationFrame(() => {
390
+ document.body.classList.add("ready");
391
+ });
392
+ firstRender = false;
393
+ }
394
+ }
395
+
396
+ function renderHeader(filteredCount) {
397
+ const ageSeconds = Math.floor((Date.now() - Date.parse(state.lastUpdated)) / 1000);
398
+
399
+ const base = `swarm ${state.swarmId} | orchestration ${state.orchestrationId} | ${ageSeconds}s ago`;
400
+ const scope = currentFilter() === "all" ? "view all agents" : `view ${currentFilterLabel().toLowerCase()}`;
401
+
402
+ elements.swarmMeta.textContent = `${base} | ${scope} (${filteredCount})`;
403
+
404
+ elements.livePill.classList.toggle("offline", !lastSyncOk);
405
+ if (!lastSyncOk) {
406
+ elements.liveLabel.textContent = "OFFLINE CACHE";
407
+ return;
408
+ }
409
+ elements.liveLabel.textContent = wsConnected ? "LIVE WS" : "LIVE POLL";
410
+ }
411
+
412
+ function renderKpis() {
413
+ const activeAgents = state.agents.filter((agent) => isLiveStatus(agent.status)).length;
414
+ const totalAgents = state.agents.length;
415
+ const completed = state.tasks.filter((task) => task.status === "completed").length;
416
+ const totalTasks = state.tasks.length;
417
+ const avgProgress = average(state.tasks.map((task) => Number(task.progress || 0)));
418
+ const avgHealth = average(state.agents.map((agent) => Number(agent.health || 0) * 100));
419
+
420
+ const fiveMinAgo = Date.now() - 5 * 60 * 1000;
421
+ const eventRate = state.events.filter((event) => Date.parse(event.time) >= fiveMinAgo).length;
422
+
423
+ const kpis = [
424
+ {
425
+ label: "Live Agents",
426
+ value: `${activeAgents}/${totalAgents}`,
427
+ sub: `${totalAgents - activeAgents} idle or non-active`,
428
+ },
429
+ {
430
+ label: "Completion",
431
+ value: `${completed}/${totalTasks}`,
432
+ sub: `${Math.round((completed / Math.max(totalTasks, 1)) * 100)}% delivered`,
433
+ },
434
+ {
435
+ label: "Mean Progress",
436
+ value: `${Math.round(avgProgress)}%`,
437
+ sub: "across all tracked tasks",
438
+ },
439
+ {
440
+ label: "Swarm Health",
441
+ value: `${Math.round(avgHealth)}%`,
442
+ sub: "average agent health",
443
+ },
444
+ {
445
+ label: "Event Velocity",
446
+ value: `${eventRate}`,
447
+ sub: "events in last 5 minutes",
448
+ },
449
+ ];
450
+
451
+ elements.kpiGrid.innerHTML = "";
452
+ kpis.forEach((kpi) => {
453
+ const card = document.createElement("article");
454
+ card.className = "kpi";
455
+
456
+ const label = document.createElement("div");
457
+ label.className = "label";
458
+ label.textContent = kpi.label;
459
+
460
+ const value = document.createElement("p");
461
+ value.className = "value";
462
+ value.textContent = kpi.value;
463
+
464
+ const sub = document.createElement("div");
465
+ sub.className = "sub";
466
+ sub.textContent = kpi.sub;
467
+
468
+ card.append(label, value, sub);
469
+ elements.kpiGrid.appendChild(card);
470
+ });
471
+ }
472
+
473
+ function renderTopology(agents) {
474
+ const svg = elements.topology;
475
+ svg.innerHTML = "";
476
+
477
+ const width = 980;
478
+ const height = 460;
479
+ const center = { x: width / 2, y: height / 2 };
480
+ const radiusX = width * 0.34;
481
+ const radiusY = height * 0.32;
482
+
483
+ const defs = s("defs");
484
+ const queenGradient = s("radialGradient", {
485
+ id: "queenGradient",
486
+ cx: "50%",
487
+ cy: "42%",
488
+ r: "65%",
489
+ });
490
+ queenGradient.append(
491
+ s("stop", { offset: "0%", "stop-color": "#c9fbff", "stop-opacity": "1" }),
492
+ s("stop", { offset: "58%", "stop-color": "#40e9ff", "stop-opacity": "0.95" }),
493
+ s("stop", { offset: "100%", "stop-color": "#45a6ff", "stop-opacity": "0.86" })
494
+ );
495
+ defs.appendChild(queenGradient);
496
+ svg.appendChild(defs);
497
+
498
+ svg.appendChild(
499
+ s("circle", {
500
+ cx: center.x,
501
+ cy: center.y,
502
+ r: 152,
503
+ fill: "none",
504
+ stroke: "rgba(127, 155, 169, 0.26)",
505
+ "stroke-width": "1",
506
+ "stroke-dasharray": "3 9",
507
+ })
508
+ );
509
+
510
+ const queenRing = s("circle", {
511
+ cx: center.x,
512
+ cy: center.y,
513
+ r: 34,
514
+ fill: "none",
515
+ stroke: "rgba(64, 233, 255, 0.42)",
516
+ "stroke-width": "2",
517
+ });
518
+ svg.appendChild(queenRing);
519
+
520
+ const queen = s("circle", {
521
+ cx: center.x,
522
+ cy: center.y,
523
+ r: 23,
524
+ fill: "url(#queenGradient)",
525
+ });
526
+ queen.classList.add("node");
527
+ svg.appendChild(queen);
528
+
529
+ svg.appendChild(
530
+ s("text", {
531
+ x: center.x,
532
+ y: center.y + 56,
533
+ "text-anchor": "middle",
534
+ "font-family": "JetBrains Mono",
535
+ "font-size": "12",
536
+ "letter-spacing": "0.08em",
537
+ fill: "#bde2eb",
538
+ }, "QUEEN")
539
+ );
540
+
541
+ if (!agents.length) {
542
+ svg.appendChild(
543
+ s("text", {
544
+ x: center.x,
545
+ y: center.y + 8,
546
+ "text-anchor": "middle",
547
+ "font-family": "JetBrains Mono",
548
+ "font-size": "12",
549
+ fill: "#9bbec7",
550
+ }, "No agents in current filter")
551
+ );
552
+ return;
553
+ }
554
+
555
+ agents.forEach((agent, index) => {
556
+ const angle = (Math.PI * 2 * index) / Math.max(agents.length, 1) - Math.PI / 2;
557
+ const x = center.x + Math.cos(angle) * radiusX;
558
+ const y = center.y + Math.sin(angle) * radiusY;
559
+
560
+ const color = statusColor(agent.status);
561
+ const live = isLiveStatus(agent.status);
562
+
563
+ const edge = s("line", {
564
+ x1: center.x,
565
+ y1: center.y,
566
+ x2: x,
567
+ y2: y,
568
+ stroke: color,
569
+ "stroke-opacity": live ? "0.62" : "0.36",
570
+ "stroke-width": live ? "2.1" : "1.4",
571
+ });
572
+ edge.classList.add("edge");
573
+ svg.appendChild(edge);
574
+
575
+ const halo = s("circle", {
576
+ cx: x,
577
+ cy: y,
578
+ r: live ? 17 : 14,
579
+ fill: color,
580
+ "fill-opacity": live ? "0.2" : "0.12",
581
+ });
582
+ svg.appendChild(halo);
583
+
584
+ const node = s("circle", {
585
+ cx: x,
586
+ cy: y,
587
+ r: live ? 11 : 9,
588
+ fill: color,
589
+ "fill-opacity": live ? "0.95" : "0.72",
590
+ });
591
+ node.classList.add("node");
592
+ svg.appendChild(node);
593
+
594
+ svg.appendChild(
595
+ s("text", {
596
+ x,
597
+ y: y + 26,
598
+ "text-anchor": "middle",
599
+ "font-family": "JetBrains Mono",
600
+ "font-size": "10",
601
+ fill: "#c1e5ee",
602
+ }, shortLabel(agent.id))
603
+ );
604
+ });
605
+ }
606
+
607
+ function renderEvents() {
608
+ const sorted = sortedEvents(state.events);
609
+ const signature = buildEventsSignature(sorted);
610
+ if (signature === lastEventsSignature) {
611
+ return;
612
+ }
613
+
614
+ lastEventsSignature = signature;
615
+ elements.eventStream.innerHTML = "";
616
+
617
+ if (!sorted.length) {
618
+ const empty = document.createElement("li");
619
+ empty.className = "empty-state";
620
+ empty.textContent = "No events yet.";
621
+ elements.eventStream.appendChild(empty);
622
+ return;
623
+ }
624
+
625
+ sorted.forEach((event) => {
626
+ const level = sanitizeLevel(event.level);
627
+
628
+ const item = document.createElement("li");
629
+ item.className = `event ${level}`;
630
+
631
+ const line = document.createElement("div");
632
+ line.className = "event-line";
633
+
634
+ const left = document.createElement("span");
635
+ left.textContent = level.toUpperCase();
636
+
637
+ const right = document.createElement("span");
638
+ right.textContent = formatClock(event.time);
639
+
640
+ line.append(left, right);
641
+
642
+ const msg = document.createElement("div");
643
+ msg.className = "event-msg";
644
+ msg.textContent = String(event.message || "No message");
645
+
646
+ item.append(line, msg);
647
+ elements.eventStream.appendChild(item);
648
+ });
649
+ }
650
+
651
+ function renderSignalLane() {
652
+ const tasks = [...state.tasks]
653
+ .sort((a, b) => {
654
+ const aBoost = a.status === "in_progress" ? 1000 : 0;
655
+ const bBoost = b.status === "in_progress" ? 1000 : 0;
656
+ return bBoost + (b.progress || 0) - (aBoost + (a.progress || 0));
657
+ })
658
+ .slice(0, 5);
659
+
660
+ elements.signalLane.innerHTML = "";
661
+
662
+ if (!tasks.length) {
663
+ elements.signalSummary.textContent = "No active tasks";
664
+ const empty = document.createElement("div");
665
+ empty.className = "empty-state";
666
+ empty.textContent = "Task signals will appear when telemetry includes tasks.";
667
+ elements.signalLane.appendChild(empty);
668
+ return;
669
+ }
670
+
671
+ elements.signalSummary.textContent = `${tasks.length} highlighted task signals`;
672
+
673
+ tasks.forEach((task) => {
674
+ const progress = clamp(Number(task.progress || 0), 0, 100);
675
+
676
+ const item = document.createElement("article");
677
+ item.className = "signal-item";
678
+
679
+ const top = document.createElement("div");
680
+ top.className = "signal-top";
681
+
682
+ const id = document.createElement("span");
683
+ id.textContent = shortLabel(task.id);
684
+
685
+ const pct = document.createElement("span");
686
+ pct.textContent = `${progress}%`;
687
+
688
+ top.append(id, pct);
689
+
690
+ const track = document.createElement("div");
691
+ track.className = "signal-track";
692
+
693
+ const bar = document.createElement("div");
694
+ bar.className = "signal-bar";
695
+ bar.style.width = `${progress}%`;
696
+
697
+ track.appendChild(bar);
698
+
699
+ item.append(top, track);
700
+ elements.signalLane.appendChild(item);
701
+ });
702
+ }
703
+
704
+ function renderAgents(agents) {
705
+ elements.agentGrid.innerHTML = "";
706
+ const active = agents.filter((agent) => isLiveStatus(agent.status)).length;
707
+ const label = currentFilter() === "all" ? "all" : currentFilterLabel().toLowerCase();
708
+ elements.agentSummary.textContent = `${agents.length}/${state.agents.length} shown (${label}) | ${active} active`;
709
+
710
+ if (!agents.length) {
711
+ const empty = document.createElement("div");
712
+ empty.className = "empty-state";
713
+ empty.textContent = `No agents match '${currentFilterLabel()}'.`;
714
+ elements.agentGrid.appendChild(empty);
715
+ return;
716
+ }
717
+
718
+ agents.forEach((agent) => {
719
+ const tone = statusTone(agent.status);
720
+ const progress = clamp(Number(agent.progress || 0), 0, 100);
721
+
722
+ const card = document.createElement("article");
723
+ card.className = `agent-card ${tone}`;
724
+
725
+ const top = document.createElement("div");
726
+ top.className = "agent-top";
727
+
728
+ const id = document.createElement("p");
729
+ id.className = "agent-id";
730
+ id.textContent = String(agent.id || "unknown-agent");
731
+
732
+ const badge = document.createElement("span");
733
+ badge.className = `badge ${badgeTone(agent.status)}`;
734
+ badge.textContent = String(agent.status || "idle");
735
+
736
+ top.append(id, badge);
737
+
738
+ const type = document.createElement("div");
739
+ type.className = "agent-detail";
740
+ type.textContent = `${agent.type || "unknown"} / ${agent.domain || "general"}`;
741
+
742
+ const task = document.createElement("div");
743
+ task.className = "agent-detail";
744
+ task.textContent = `Task: ${agent.taskTitle || "none"}`;
745
+
746
+ const health = document.createElement("div");
747
+ health.className = "agent-detail";
748
+ health.textContent = `Health: ${Math.round(clamp(Number(agent.health || 0), 0, 1) * 100)}%`;
749
+
750
+ const track = document.createElement("div");
751
+ track.className = "progress-track";
752
+
753
+ const bar = document.createElement("div");
754
+ bar.className = "progress-bar";
755
+ bar.style.width = `${progress}%`;
756
+
757
+ track.appendChild(bar);
758
+
759
+ card.append(top, type, task, health, track);
760
+ elements.agentGrid.appendChild(card);
761
+ });
762
+ }
763
+
764
+ function renderTasks() {
765
+ elements.taskBody.innerHTML = "";
766
+ elements.taskSummary.textContent = `${state.tasks.length} tasks tracked`;
767
+
768
+ if (!state.tasks.length) {
769
+ const tr = document.createElement("tr");
770
+ const td = document.createElement("td");
771
+ td.colSpan = 6;
772
+ td.className = "empty-state";
773
+ td.textContent = "No tasks available.";
774
+ tr.appendChild(td);
775
+ elements.taskBody.appendChild(tr);
776
+ return;
777
+ }
778
+
779
+ state.tasks.forEach((task) => {
780
+ const tr = document.createElement("tr");
781
+
782
+ const id = document.createElement("td");
783
+ id.className = "mono";
784
+ id.textContent = String(task.id || "-");
785
+
786
+ const assigned = document.createElement("td");
787
+ assigned.textContent = String(task.assignedTo || "unassigned");
788
+
789
+ const type = document.createElement("td");
790
+ type.textContent = String(task.type || "feature");
791
+
792
+ const priority = document.createElement("td");
793
+ priority.className = "mono";
794
+ priority.textContent = String(task.priority || "normal");
795
+
796
+ const status = document.createElement("td");
797
+ const statusLabel = String(task.status || "pending");
798
+ status.className = `task-status status-${sanitizeClass(statusLabel)}`;
799
+ status.textContent = statusLabel;
800
+
801
+ const progressCell = document.createElement("td");
802
+ progressCell.className = "progress-cell";
803
+
804
+ const progressWrap = document.createElement("div");
805
+ progressWrap.className = "task-progress";
806
+
807
+ const pct = document.createElement("span");
808
+ pct.className = "pct";
809
+ const progress = clamp(Number(task.progress || 0), 0, 100);
810
+ pct.textContent = `${progress}%`;
811
+
812
+ const miniTrack = document.createElement("div");
813
+ miniTrack.className = "mini-track";
814
+
815
+ const miniBar = document.createElement("div");
816
+ miniBar.className = "mini-bar";
817
+ miniBar.style.width = `${progress}%`;
818
+
819
+ miniTrack.appendChild(miniBar);
820
+ progressWrap.append(pct, miniTrack);
821
+ progressCell.appendChild(progressWrap);
822
+
823
+ tr.append(id, assigned, type, priority, status, progressCell);
824
+ elements.taskBody.appendChild(tr);
825
+ });
826
+ }
827
+
828
+ function addEvent(level, message) {
829
+ if (!state) {
830
+ return;
831
+ }
832
+ state.events.unshift({
833
+ time: new Date().toISOString(),
834
+ level,
835
+ message,
836
+ });
837
+
838
+ if (state.events.length > 80) {
839
+ state.events.length = 80;
840
+ }
841
+ }
842
+
843
+ function filterAgents(agents, filter) {
844
+ if (filter === "all") {
845
+ return agents;
846
+ }
847
+
848
+ if (filter === "active") {
849
+ return agents.filter((agent) => isLiveStatus(agent.status));
850
+ }
851
+
852
+ if (filter === "warn") {
853
+ return agents.filter((agent) => ["reviewing", "pending", "stalled"].includes(agent.status));
854
+ }
855
+
856
+ if (filter === "error") {
857
+ return agents.filter((agent) => ["blocked", "error", "failed"].includes(agent.status));
858
+ }
859
+
860
+ if (filter === "idle") {
861
+ return agents.filter((agent) => statusTone(agent.status) === "idle");
862
+ }
863
+
864
+ return agents;
865
+ }
866
+
867
+ function isLiveStatus(status) {
868
+ return ["working", "in_progress", "reviewing"].includes(status);
869
+ }
870
+
871
+ function statusTone(status) {
872
+ if (["blocked", "error", "failed"].includes(status)) {
873
+ return "error";
874
+ }
875
+ if (["reviewing", "pending", "stalled"].includes(status)) {
876
+ return "warn";
877
+ }
878
+ if (isLiveStatus(status)) {
879
+ return "live";
880
+ }
881
+ return "idle";
882
+ }
883
+
884
+ function badgeTone(status) {
885
+ return statusTone(status);
886
+ }
887
+
888
+ function statusColor(status) {
889
+ const tone = statusTone(status);
890
+ if (tone === "error") {
891
+ return "#ff5f73";
892
+ }
893
+ if (tone === "warn") {
894
+ return "#ffba4a";
895
+ }
896
+ if (tone === "live") {
897
+ return "#3ee6bb";
898
+ }
899
+ return "#8fa9b5";
900
+ }
901
+
902
+ function sanitizeLevel(value) {
903
+ const level = String(value || "info").toLowerCase();
904
+ if (["warn", "error", "success", "info"].includes(level)) {
905
+ return level;
906
+ }
907
+ return "info";
908
+ }
909
+
910
+ function sanitizeClass(value) {
911
+ return String(value || "").toLowerCase().replace(/[^a-z0-9_-]/g, "_");
912
+ }
913
+
914
+ function shortLabel(text) {
915
+ const raw = String(text || "unknown");
916
+ const tokens = raw.split("-");
917
+ const last = tokens[tokens.length - 1] || raw;
918
+ if (last.length <= 8) {
919
+ return last.toUpperCase();
920
+ }
921
+ return `${last.slice(0, 8).toUpperCase()}..`;
922
+ }
923
+
924
+ function formatClock(value) {
925
+ const date = new Date(value);
926
+ if (Number.isNaN(date.getTime())) {
927
+ return "--:--:--";
928
+ }
929
+ return date.toLocaleTimeString([], {
930
+ hour12: false,
931
+ hour: "2-digit",
932
+ minute: "2-digit",
933
+ second: "2-digit",
934
+ });
935
+ }
936
+
937
+ function sortedEvents(events) {
938
+ return [...events]
939
+ .sort((a, b) => Date.parse(b.time) - Date.parse(a.time))
940
+ .slice(0, MAX_EVENTS);
941
+ }
942
+
943
+ function buildEventsSignature(events) {
944
+ return events.map((event) => `${event.time}|${event.level}|${event.message}`).join("\n");
945
+ }
946
+
947
+ function buildDataSignature(snapshot, filter) {
948
+ const agents = snapshot.agents.map((agent) => [
949
+ agent.id,
950
+ agent.status,
951
+ Number(agent.progress || 0),
952
+ Math.round(Number(agent.health || 0) * 100),
953
+ agent.taskId || "",
954
+ agent.taskTitle || "",
955
+ ]);
956
+
957
+ const tasks = snapshot.tasks.map((task) => [
958
+ task.id,
959
+ task.assignedTo,
960
+ task.type,
961
+ task.priority,
962
+ task.status,
963
+ Number(task.progress || 0),
964
+ ]);
965
+
966
+ const events = sortedEvents(snapshot.events).map((event) => [
967
+ event.time,
968
+ event.level,
969
+ event.message,
970
+ ]);
971
+
972
+ return JSON.stringify({
973
+ filter,
974
+ agents,
975
+ tasks,
976
+ events,
977
+ });
978
+ }
979
+
980
+ function average(values) {
981
+ if (!values.length) {
982
+ return 0;
983
+ }
984
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
985
+ }
986
+
987
+ function clamp(value, min, max) {
988
+ return Math.max(min, Math.min(max, value));
989
+ }
990
+
991
+ function rand(min, max) {
992
+ return Math.floor(Math.random() * (max - min + 1)) + min;
993
+ }
994
+
995
+ function s(tag, attrs = {}, text = "") {
996
+ const el = document.createElementNS(ns, tag);
997
+ Object.entries(attrs).forEach(([key, value]) => {
998
+ el.setAttribute(key, String(value));
999
+ });
1000
+ if (text) {
1001
+ el.textContent = text;
1002
+ }
1003
+ return el;
1004
+ }