agentflow-dashboard 0.5.0 → 0.7.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.
@@ -54,7 +54,7 @@ class AgentFlowDashboard {
54
54
  // ---------------------------------------------------------------------------
55
55
  connectWebSocket() {
56
56
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
57
- const wsUrl = protocol + '//' + window.location.host;
57
+ const wsUrl = `${protocol}//${window.location.host}`;
58
58
 
59
59
  try {
60
60
  this.ws = new WebSocket(wsUrl);
@@ -92,15 +92,15 @@ class AgentFlowDashboard {
92
92
  attemptReconnect() {
93
93
  if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
94
94
  this.reconnectAttempts++;
95
- var delay = this.reconnectDelay * Math.min(Math.pow(1.5, this.reconnectAttempts - 1), 30);
95
+ var delay = this.reconnectDelay * Math.min(1.5 ** (this.reconnectAttempts - 1), 30);
96
96
  setTimeout(() => this.connectWebSocket(), delay);
97
97
  }
98
98
 
99
99
  handleWebSocketMessage(msg) {
100
100
  switch (msg.type) {
101
101
  case 'init':
102
- if (msg.data && msg.data.traces) this.traces = msg.data.traces;
103
- if (msg.data && msg.data.stats) this.stats = msg.data.stats;
102
+ if (msg.data?.traces) this.traces = msg.data.traces;
103
+ if (msg.data?.stats) this.stats = msg.data.stats;
104
104
  this.renderTraceList();
105
105
  this.renderStatsOverview();
106
106
  if (this.traces.length > 0 && !this.selectedTrace) {
@@ -115,7 +115,7 @@ class AgentFlowDashboard {
115
115
  }
116
116
  break;
117
117
  case 'trace-updated': {
118
- var idx = this.traces.findIndex(function(t) { return t.filename === msg.data.filename; });
118
+ var idx = this.traces.findIndex((t) => t.filename === msg.data.filename);
119
119
  if (idx >= 0) this.traces[idx] = msg.data;
120
120
  this.renderTraceList();
121
121
  if (this.selectedTrace && this.selectedTrace.filename === msg.data.filename) {
@@ -152,10 +152,7 @@ class AgentFlowDashboard {
152
152
  // ---------------------------------------------------------------------------
153
153
  async loadInitialData() {
154
154
  try {
155
- var results = await Promise.all([
156
- fetch('/api/traces'),
157
- fetch('/api/stats')
158
- ]);
155
+ var results = await Promise.all([fetch('/api/traces'), fetch('/api/stats')]);
159
156
  if (results[0].ok) this.traces = await results[0].json();
160
157
  if (results[1].ok) this.stats = await results[1].json();
161
158
  this.renderTraceList();
@@ -183,7 +180,7 @@ class AgentFlowDashboard {
183
180
 
184
181
  async loadTraceDetail(filename) {
185
182
  try {
186
- var res = await fetch('/api/traces/' + encodeURIComponent(filename));
183
+ var res = await fetch(`/api/traces/${encodeURIComponent(filename)}`);
187
184
  if (res.ok) {
188
185
  this.selectedTraceData = await res.json();
189
186
  this.renderActiveTab();
@@ -199,7 +196,7 @@ class AgentFlowDashboard {
199
196
  if (!res.ok) return;
200
197
  this.processHealth = await res.json();
201
198
  this.renderProcessHealth();
202
- } catch (e) {
199
+ } catch (_e) {
203
200
  // silent — endpoint may not always be available
204
201
  }
205
202
  }
@@ -208,68 +205,70 @@ class AgentFlowDashboard {
208
205
  // Event listeners
209
206
  // ---------------------------------------------------------------------------
210
207
  setupEventListeners() {
211
- var self = this;
212
-
213
208
  // Tab switching
214
- document.querySelectorAll('.tab').forEach(function(tab) {
215
- tab.addEventListener('click', function() {
216
- self.activeTab = tab.dataset.tab;
217
- document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
209
+ document.querySelectorAll('.tab').forEach((tab) => {
210
+ tab.addEventListener('click', () => {
211
+ this.activeTab = tab.dataset.tab;
212
+ document.querySelectorAll('.tab').forEach((t) => {
213
+ t.classList.remove('active');
214
+ });
218
215
  tab.classList.add('active');
219
- document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
220
- document.getElementById('panel-' + self.activeTab).classList.add('active');
221
- self.renderActiveTab();
216
+ document.querySelectorAll('.tab-panel').forEach((p) => {
217
+ p.classList.remove('active');
218
+ });
219
+ document.getElementById(`panel-${this.activeTab}`).classList.add('active');
220
+ this.renderActiveTab();
222
221
  });
223
222
  });
224
223
 
225
224
  // Search
226
- document.getElementById('traceSearch').addEventListener('input', function(e) {
227
- self.searchFilter = e.target.value.toLowerCase();
228
- self.renderTraceList();
225
+ document.getElementById('traceSearch').addEventListener('input', (e) => {
226
+ this.searchFilter = e.target.value.toLowerCase();
227
+ this.renderTraceList();
229
228
  });
230
229
 
231
230
  // Status filter dropdown
232
- document.getElementById('statusFilter').addEventListener('change', function(e) {
233
- self.statusFilter = e.target.value;
234
- self.renderTraceList();
231
+ document.getElementById('statusFilter').addEventListener('change', (e) => {
232
+ this.statusFilter = e.target.value;
233
+ this.renderTraceList();
235
234
  });
236
235
 
237
236
  // Time range filter dropdown
238
- document.getElementById('timeRangeFilter').addEventListener('change', function(e) {
239
- self.timeRangeFilter = e.target.value;
240
- self.renderTraceList();
237
+ document.getElementById('timeRangeFilter').addEventListener('change', (e) => {
238
+ this.timeRangeFilter = e.target.value;
239
+ this.renderTraceList();
241
240
  });
242
241
 
243
242
  // Activity filter dropdown (if exists)
244
243
  var activityFilter = document.getElementById('activityFilter');
245
244
  if (activityFilter) {
246
- activityFilter.addEventListener('change', function(e) {
247
- self.activityFilter = e.target.value;
248
- self.renderTraceList();
245
+ activityFilter.addEventListener('change', (e) => {
246
+ this.activityFilter = e.target.value;
247
+ this.renderTraceList();
249
248
  });
250
249
  }
251
250
 
252
251
  // Toolbar buttons
253
- document.getElementById('btnFit').addEventListener('click', function() {
254
- if (self.cy) self.cy.fit(50);
252
+ document.getElementById('btnFit').addEventListener('click', () => {
253
+ if (this.cy) this.cy.fit(50);
255
254
  });
256
- document.getElementById('btnLayout').addEventListener('click', function() {
257
- self.runCytoscapeLayout();
255
+ document.getElementById('btnLayout').addEventListener('click', () => {
256
+ this.runCytoscapeLayout();
258
257
  });
259
- document.getElementById('btnExportPng').addEventListener('click', function() {
260
- self.exportGraphPNG();
258
+ document.getElementById('btnExportPng').addEventListener('click', () => {
259
+ this.exportGraphPNG();
261
260
  });
262
- document.getElementById('btnRefresh').addEventListener('click', function() {
263
- self.loadInitialData();
264
- self.loadProcessHealth();
261
+ document.getElementById('btnRefresh').addEventListener('click', () => {
262
+ this.loadInitialData();
263
+ this.loadProcessHealth();
265
264
  });
266
- document.getElementById('btnPlayPause').addEventListener('click', function() {
267
- self.isLive = !self.isLive;
265
+ document.getElementById('btnPlayPause').addEventListener('click', () => {
266
+ this.isLive = !this.isLive;
268
267
  var btn = document.getElementById('btnPlayPause');
269
- btn.innerHTML = self.isLive ? '⏸' : '▶';
270
- btn.title = self.isLive ? 'Pause live tail' : 'Resume live tail';
268
+ btn.innerHTML = this.isLive ? '⏸' : '▶';
269
+ btn.title = this.isLive ? 'Pause live tail' : 'Resume live tail';
271
270
  var liveInd = document.getElementById('liveIndicator');
272
- if (self.isLive && self.ws && self.ws.readyState === WebSocket.OPEN) {
271
+ if (this.isLive && this.ws && this.ws.readyState === WebSocket.OPEN) {
273
272
  liveInd.className = 'live-indicator active';
274
273
  } else {
275
274
  liveInd.className = 'live-indicator';
@@ -277,22 +276,22 @@ class AgentFlowDashboard {
277
276
  });
278
277
 
279
278
  // Node detail close
280
- document.getElementById('nodeDetailClose').addEventListener('click', function() {
279
+ document.getElementById('nodeDetailClose').addEventListener('click', () => {
281
280
  document.getElementById('nodeDetailPanel').classList.remove('active');
282
281
  });
283
282
 
284
283
  // Trace list click delegation
285
- document.getElementById('traceList').addEventListener('click', function(e) {
284
+ document.getElementById('traceList').addEventListener('click', (e) => {
286
285
  var item = e.target.closest('.session-item');
287
286
  if (!item) return;
288
287
  var filename = item.dataset.filename;
289
- self.selectTrace(filename);
288
+ this.selectTrace(filename);
290
289
  });
291
290
 
292
291
  // Auto-refresh stats every 30s
293
- setInterval(function() {
294
- if (self.ws && self.ws.readyState === WebSocket.OPEN) {
295
- self.refreshStats();
292
+ setInterval(() => {
293
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
294
+ this.refreshStats();
296
295
  }
297
296
  }, 30000);
298
297
  }
@@ -301,7 +300,7 @@ class AgentFlowDashboard {
301
300
  // Trace selection
302
301
  // ---------------------------------------------------------------------------
303
302
  selectTrace(filename) {
304
- var trace = this.traces.find(function(t) { return t.filename === filename; });
303
+ var trace = this.traces.find((t) => t.filename === filename);
305
304
  if (!trace) return;
306
305
 
307
306
  this.selectedTrace = trace;
@@ -310,7 +309,10 @@ class AgentFlowDashboard {
310
309
  // Reset agent-level caches when agent changes
311
310
  if (this._processMapAgent !== trace.agentId) {
312
311
  this._processMapAgent = null;
313
- if (this._cyProcessMap) { this._cyProcessMap.destroy(); this._cyProcessMap = null; }
312
+ if (this._cyProcessMap) {
313
+ this._cyProcessMap.destroy();
314
+ this._cyProcessMap = null;
315
+ }
314
316
  }
315
317
  if (this._agentTimelineAgent !== trace.agentId) {
316
318
  this._agentTimelineAgent = null;
@@ -318,8 +320,10 @@ class AgentFlowDashboard {
318
320
  }
319
321
 
320
322
  // Update sidebar selection
321
- document.querySelectorAll('.session-item').forEach(function(el) { el.classList.remove('active'); });
322
- var activeEl = document.querySelector('.session-item[data-filename="' + CSS.escape(filename) + '"]');
323
+ document.querySelectorAll('.session-item').forEach((el) => {
324
+ el.classList.remove('active');
325
+ });
326
+ var activeEl = document.querySelector(`.session-item[data-filename="${CSS.escape(filename)}"]`);
323
327
  if (activeEl) {
324
328
  activeEl.classList.add('active');
325
329
  // Scroll into view if needed
@@ -340,11 +344,13 @@ class AgentFlowDashboard {
340
344
  if (!this.stats) return;
341
345
  var s = this.stats;
342
346
  document.getElementById('statAgents').textContent = s.totalAgents || 0;
343
- document.getElementById('statExecutions').textContent = (s.totalExecutions || 0).toLocaleString();
347
+ document.getElementById('statExecutions').textContent = (
348
+ s.totalExecutions || 0
349
+ ).toLocaleString();
344
350
  var rate = Math.round((s.globalSuccessRate || 0) * 10) / 10;
345
351
  var rateEl = document.getElementById('statSuccessRate');
346
- rateEl.textContent = rate + '%';
347
- rateEl.className = 'metric-value ' + (rate >= 90 ? 'success' : rate >= 70 ? 'warning' : 'error');
352
+ rateEl.textContent = `${rate}%`;
353
+ rateEl.className = `metric-value ${rate >= 90 ? 'success' : rate >= 70 ? 'warning' : 'error'}`;
348
354
  document.getElementById('statActive').textContent = s.activeAgents || 0;
349
355
  }
350
356
 
@@ -359,8 +365,13 @@ class AgentFlowDashboard {
359
365
  }
360
366
 
361
367
  var r = this.processHealth;
362
- var hasContent = r.pidFile || r.systemd || r.workers || (r.orphans && r.orphans.length > 0) ||
363
- (r.osProcesses && r.osProcesses.length > 0) || (r.problems && r.problems.length > 0);
368
+ var hasContent =
369
+ r.pidFile ||
370
+ r.systemd ||
371
+ r.workers ||
372
+ (r.orphans && r.orphans.length > 0) ||
373
+ (r.osProcesses && r.osProcesses.length > 0) ||
374
+ (r.problems && r.problems.length > 0);
364
375
  if (!hasContent) {
365
376
  section.style.display = 'none';
366
377
  return;
@@ -369,45 +380,83 @@ class AgentFlowDashboard {
369
380
  section.style.display = '';
370
381
  var html = '<h4>Process Health</h4>';
371
382
 
372
- // PID File section
373
- if (r.pidFile) {
374
- var pf = r.pidFile;
375
- var cls = pf.alive && pf.matchesProcess ? 'ok' : pf.stale ? 'bad' : 'warn';
376
- html += '<div class="ph-row">';
377
- html += '<span class="ph-label">PID File</span>';
378
- html += '<span class="ph-value ' + cls + '">';
379
- html += pf.pid ? ('PID ' + pf.pid + (pf.alive ? ' (alive)' : ' (dead)')) : 'No PID';
380
- html += '</span></div>';
381
- }
383
+ // Render all discovered services (new multi-service format)
384
+ var services = r.services || [];
385
+ if (services.length > 0) {
386
+ for (var si = 0; si < services.length; si++) {
387
+ var svc = services[si];
388
+ html += '<div class="ph-service">';
389
+ html += `<div class="ph-service-name">${escapeHtml(svc.name)}</div>`;
390
+
391
+ // PID File for this service
392
+ if (svc.pidFile) {
393
+ var pf = svc.pidFile;
394
+ var cls = pf.alive && pf.matchesProcess ? 'ok' : pf.stale ? 'bad' : 'warn';
395
+ html += '<div class="ph-row">';
396
+ html += '<span class="ph-label">PID File</span>';
397
+ html += `<span class="ph-value ${cls}">`;
398
+ html += pf.pid ? `PID ${pf.pid}${pf.alive ? ' (alive)' : ' (dead)'}` : 'No PID';
399
+ html += '</span></div>';
400
+ }
382
401
 
383
- // Systemd section
384
- if (r.systemd) {
385
- var sd = r.systemd;
386
- var sdCls = sd.activeState === 'active' ? 'ok' : sd.failed ? 'bad' : 'warn';
387
- html += '<div class="ph-row">';
388
- html += '<span class="ph-label">Systemd</span>';
389
- html += '<span class="ph-value ' + sdCls + '">';
390
- html += escapeHtml(sd.unit) + ' \u2014 ' + escapeHtml(sd.activeState) + ' (' + escapeHtml(sd.subState) + ')';
391
- if (sd.restarts > 0) html += ' [' + sd.restarts + ' restarts]';
392
- html += '</span></div>';
393
- }
402
+ // Systemd for this service
403
+ if (svc.systemd) {
404
+ var sd = svc.systemd;
405
+ var sdCls = sd.activeState === 'active' ? 'ok' : sd.failed ? 'bad' : 'warn';
406
+ html += '<div class="ph-row">';
407
+ html += '<span class="ph-label">Systemd</span>';
408
+ html += `<span class="ph-value ${sdCls}">`;
409
+ html +=
410
+ escapeHtml(sd.unit) +
411
+ ' \u2014 ' +
412
+ escapeHtml(sd.activeState) +
413
+ ' (' +
414
+ escapeHtml(sd.subState) +
415
+ ')';
416
+ if (sd.restarts > 0) html += ` [${sd.restarts} restarts]`;
417
+ html += '</span></div>';
418
+ }
394
419
 
395
- // Workers detailed view
396
- if (r.workers && r.workers.workers) {
397
- html += '<div class="ph-section">';
398
- html += '<span class="ph-label">Workers</span>';
399
- html += '<div class="process-grid">';
400
- for (var i = 0; i < r.workers.workers.length; i++) {
401
- var worker = r.workers.workers[i];
402
- var statusCls = worker.alive ? 'ok' : worker.stale ? 'bad' : 'warn';
403
- html += '<div class="worker-card ' + statusCls + '">';
404
- html += '<div class="worker-name">' + escapeHtml(worker.name) + '</div>';
405
- html += '<div class="worker-details">';
406
- html += '<span>PID: ' + (worker.pid || '-') + '</span>';
407
- html += '<span>' + escapeHtml(worker.declaredStatus) + '</span>';
408
- html += '</div></div>';
420
+ // Workers for this service
421
+ if (svc.workers?.workers) {
422
+ html += '<div class="ph-section">';
423
+ html += '<span class="ph-label">Workers</span>';
424
+ html += '<div class="process-grid">';
425
+ for (var i = 0; i < svc.workers.workers.length; i++) {
426
+ var worker = svc.workers.workers[i];
427
+ var statusCls = worker.alive ? 'ok' : worker.stale ? 'bad' : 'warn';
428
+ html += `<div class="worker-card ${statusCls}">`;
429
+ html += `<div class="worker-name">${escapeHtml(worker.name)}</div>`;
430
+ html += '<div class="worker-details">';
431
+ html += `<span>PID: ${worker.pid || '-'}</span>`;
432
+ html += `<span>${escapeHtml(worker.declaredStatus)}</span>`;
433
+ html += '</div></div>';
434
+ }
435
+ html += '</div></div>';
436
+ }
437
+
438
+ html += '</div>'; // .ph-service
439
+ }
440
+ } else {
441
+ // Fallback: legacy single-service format
442
+ if (r.pidFile) {
443
+ var pf2 = r.pidFile;
444
+ var cls2 = pf2.alive && pf2.matchesProcess ? 'ok' : pf2.stale ? 'bad' : 'warn';
445
+ html += '<div class="ph-row">';
446
+ html += '<span class="ph-label">PID File</span>';
447
+ html += `<span class="ph-value ${cls2}">`;
448
+ html += pf2.pid ? `PID ${pf2.pid}${pf2.alive ? ' (alive)' : ' (dead)'}` : 'No PID';
449
+ html += '</span></div>';
450
+ }
451
+ if (r.systemd) {
452
+ var sd2 = r.systemd;
453
+ var sdCls2 = sd2.activeState === 'active' ? 'ok' : sd2.failed ? 'bad' : 'warn';
454
+ html += '<div class="ph-row">';
455
+ html += '<span class="ph-label">Systemd</span>';
456
+ html += `<span class="ph-value ${sdCls2}">`;
457
+ html += escapeHtml(sd2.unit) + ' \u2014 ' + escapeHtml(sd2.activeState) + ' (' + escapeHtml(sd2.subState) + ')';
458
+ html += '</span></div>';
409
459
  }
410
- html += '</div></div>';
411
460
  }
412
461
 
413
462
  // Agent Services - categorize processes and build tree
@@ -415,7 +464,7 @@ class AgentFlowDashboard {
415
464
 
416
465
  if (categorized.agents.length > 0) {
417
466
  html += '<div class="ph-section">';
418
- html += '<span class="ph-label">Agent Services (' + categorized.agents.length + ')</span>';
467
+ html += `<span class="ph-label">Agent Services (${categorized.agents.length})</span>`;
419
468
 
420
469
  // Build process tree for agents
421
470
  var agentTree = this.buildProcessTree(categorized.agents);
@@ -426,7 +475,7 @@ class AgentFlowDashboard {
426
475
  // Infrastructure processes
427
476
  if (categorized.infrastructure.length > 0) {
428
477
  html += '<div class="ph-section">';
429
- html += '<span class="ph-label">Infrastructure (' + categorized.infrastructure.length + ')</span>';
478
+ html += `<span class="ph-label">Infrastructure (${categorized.infrastructure.length})</span>`;
430
479
 
431
480
  // Build process tree for infrastructure
432
481
  var infraTree = this.buildProcessTree(categorized.infrastructure);
@@ -438,15 +487,25 @@ class AgentFlowDashboard {
438
487
  var uncategorized = this.getUncategorizedOrphans(r.orphans || [], categorized);
439
488
  if (uncategorized.length > 0) {
440
489
  html += '<div class="ph-section">';
441
- html += '<span class="ph-label">Orphans (' + uncategorized.length + ')</span>';
490
+ html += `<span class="ph-label">Orphans (${uncategorized.length})</span>`;
442
491
  html += '<div class="orphan-list">';
443
492
  for (var j = 0; j < uncategorized.length; j++) {
444
493
  var o = uncategorized[j];
445
494
  html += '<div class="orphan-row">';
446
- html += '<span class="orphan-pid">PID ' + o.pid + '</span>';
447
- html += '<span class="orphan-resources">CPU: ' + escapeHtml(o.cpu) + '% | MEM: ' + escapeHtml(o.mem) + '%</span>';
448
- html += '<span class="orphan-cmd" title="' + escapeHtml(o.cmdline || o.command) + '">' +
449
- escapeHtml((o.command || '').substring(0, 60)) + (o.command && o.command.length > 60 ? '...' : '') + '</span>';
495
+ html += `<span class="orphan-pid">PID ${o.pid}</span>`;
496
+ html +=
497
+ '<span class="orphan-resources">CPU: ' +
498
+ escapeHtml(o.cpu) +
499
+ '% | MEM: ' +
500
+ escapeHtml(o.mem) +
501
+ '%</span>';
502
+ html +=
503
+ '<span class="orphan-cmd" title="' +
504
+ escapeHtml(o.cmdline || o.command) +
505
+ '">' +
506
+ escapeHtml((o.command || '').substring(0, 60)) +
507
+ (o.command && o.command.length > 60 ? '...' : '') +
508
+ '</span>';
450
509
  html += '</div>';
451
510
  }
452
511
  html += '</div></div>';
@@ -458,7 +517,7 @@ class AgentFlowDashboard {
458
517
  html += '<span class="ph-label problems">Issues</span>';
459
518
  html += '<div class="problems-list">';
460
519
  for (var k = 0; k < r.problems.length; k++) {
461
- html += '<div class="problem-item">⚠️ ' + escapeHtml(r.problems[k]) + '</div>';
520
+ html += `<div class="problem-item">⚠️ ${escapeHtml(r.problems[k])}</div>`;
462
521
  }
463
522
  html += '</div></div>';
464
523
  }
@@ -490,11 +549,10 @@ class AgentFlowDashboard {
490
549
  elapsed: proc.elapsed,
491
550
  ppid: proc.ppid,
492
551
  cmdline: proc.cmdline || proc.command,
493
- tag: activityTag
552
+ tag: activityTag,
494
553
  });
495
554
  console.log('Detected infrastructure:', proc.pid, component, 'tag:', activityTag);
496
- }
497
- else if (service) {
555
+ } else if (service) {
498
556
  agents.push({
499
557
  service: service,
500
558
  pid: proc.pid,
@@ -503,13 +561,16 @@ class AgentFlowDashboard {
503
561
  elapsed: proc.elapsed,
504
562
  ppid: proc.ppid,
505
563
  cmdline: proc.cmdline || proc.command,
506
- tag: activityTag
564
+ tag: activityTag,
507
565
  });
508
566
  console.log('Detected agent:', proc.pid, service, 'tag:', activityTag);
509
567
  }
510
568
  }
511
569
 
512
- console.log('Categorization result:', {agents: agents.length, infrastructure: infrastructure.length});
570
+ console.log('Categorization result:', {
571
+ agents: agents.length,
572
+ infrastructure: infrastructure.length,
573
+ });
513
574
  return { agents: agents, infrastructure: infrastructure };
514
575
  }
515
576
 
@@ -531,7 +592,8 @@ class AgentFlowDashboard {
531
592
  if (cmdline.includes('alfred') && cmdline.includes('janitor')) return 'Alfred Janitor';
532
593
  if (cmdline.includes('alfred') && cmdline.includes('distiller')) return 'Alfred Distiller';
533
594
  if (cmdline.includes('alfred') && cmdline.includes('surveyor')) return 'Alfred Surveyor';
534
- if (cmdline.includes('alfred') && (cmdline.includes('worker') || cmdline.includes('daemon'))) return 'Alfred Worker';
595
+ if (cmdline.includes('alfred') && (cmdline.includes('worker') || cmdline.includes('daemon')))
596
+ return 'Alfred Worker';
535
597
  if (cmdline.includes('.alfred')) return 'Alfred Process';
536
598
 
537
599
  // AI/ML agent frameworks
@@ -541,8 +603,10 @@ class AgentFlowDashboard {
541
603
  if (cmdline.includes('mastra')) return 'Mastra Agent';
542
604
 
543
605
  // Node.js/Python AI processes
544
- if ((cmd.includes('node') || cmd.includes('python')) &&
545
- (cmdline.includes('agent') || cmdline.includes('ai') || cmdline.includes('llm'))) {
606
+ if (
607
+ (cmd.includes('node') || cmd.includes('python')) &&
608
+ (cmdline.includes('agent') || cmdline.includes('ai') || cmdline.includes('llm'))
609
+ ) {
546
610
  return 'AI Agent Process';
547
611
  }
548
612
 
@@ -552,8 +616,10 @@ class AgentFlowDashboard {
552
616
  }
553
617
 
554
618
  // Generic agent indicators
555
- if (cmdline.includes('agent') &&
556
- (cmdline.includes('server') || cmdline.includes('worker') || cmdline.includes('daemon'))) {
619
+ if (
620
+ cmdline.includes('agent') &&
621
+ (cmdline.includes('server') || cmdline.includes('worker') || cmdline.includes('daemon'))
622
+ ) {
557
623
  return 'Agent Service';
558
624
  }
559
625
 
@@ -597,8 +663,10 @@ class AgentFlowDashboard {
597
663
 
598
664
  // Get orphans that weren't categorized
599
665
  getUncategorizedOrphans(orphans, categorized) {
600
- var allCategorizedPids = categorized.agents.concat(categorized.infrastructure).map(function(p) { return p.pid; });
601
- return orphans.filter(function(o) { return allCategorizedPids.indexOf(o.pid) === -1; });
666
+ var allCategorizedPids = categorized.agents
667
+ .concat(categorized.infrastructure)
668
+ .map((p) => p.pid);
669
+ return orphans.filter((o) => allCategorizedPids.indexOf(o.pid) === -1);
602
670
  }
603
671
 
604
672
  // Build hierarchical process tree from flat process list
@@ -611,7 +679,7 @@ class AgentFlowDashboard {
611
679
  var proc = processes[i];
612
680
  processMap[proc.pid] = {
613
681
  process: proc,
614
- children: []
682
+ children: [],
615
683
  };
616
684
  }
617
685
 
@@ -645,33 +713,47 @@ class AgentFlowDashboard {
645
713
  // Render individual process node recursively
646
714
  renderProcessNode(node, type, depth) {
647
715
  var proc = node.process;
648
- var indent = 'padding-left: ' + (depth * 20) + 'px;';
716
+ var indent = `padding-left: ${depth * 20}px;`;
649
717
  var cpuNum = parseFloat(proc.cpu) || 0;
650
- var cpuCls = type === 'agent'
651
- ? (cpuNum > 50 ? 'high' : cpuNum > 10 ? 'medium' : 'low')
652
- : (cpuNum > 20 ? 'high' : cpuNum > 5 ? 'medium' : 'low');
718
+ var cpuCls =
719
+ type === 'agent'
720
+ ? cpuNum > 50
721
+ ? 'high'
722
+ : cpuNum > 10
723
+ ? 'medium'
724
+ : 'low'
725
+ : cpuNum > 20
726
+ ? 'high'
727
+ : cpuNum > 5
728
+ ? 'medium'
729
+ : 'low';
653
730
 
654
731
  var serviceName = type === 'agent' ? proc.service : proc.component;
655
732
 
656
- var html = '<div class="process-node ' + type + '-node ' + cpuCls + '" style="' + indent + '">';
733
+ var html = `<div class="process-node ${type}-node ${cpuCls}" style="${indent}">`;
657
734
 
658
735
  // Process icon and name
659
736
  if (depth > 0) {
660
737
  html += '<span class="tree-connector">└─ </span>';
661
738
  }
662
739
  html += '<div class="process-main">';
663
- html += '<div class="process-name" title="' + escapeHtml(proc.cmdline) + '">' + escapeHtml(serviceName) + '</div>';
740
+ html +=
741
+ '<div class="process-name" title="' +
742
+ escapeHtml(proc.cmdline) +
743
+ '">' +
744
+ escapeHtml(serviceName) +
745
+ '</div>';
664
746
 
665
747
  // Add activity tag
666
748
  if (proc.tag && proc.tag !== 'other') {
667
- html += '<span class="activity-tag tag-' + proc.tag + '">' + proc.tag + '</span>';
749
+ html += `<span class="activity-tag tag-${proc.tag}">${proc.tag}</span>`;
668
750
  }
669
751
 
670
752
  html += '<div class="process-metrics">';
671
- html += '<span class="pid-badge">PID: ' + proc.pid + '</span>';
672
- html += '<span class="cpu-badge">CPU: ' + escapeHtml(proc.cpu) + '%</span>';
673
- html += '<span class="mem-badge">MEM: ' + escapeHtml(proc.mem) + '%</span>';
674
- html += '<span class="time-badge">Up: ' + escapeHtml(proc.elapsed) + '</span>';
753
+ html += `<span class="pid-badge">PID: ${proc.pid}</span>`;
754
+ html += `<span class="cpu-badge">CPU: ${escapeHtml(proc.cpu)}%</span>`;
755
+ html += `<span class="mem-badge">MEM: ${escapeHtml(proc.mem)}%</span>`;
756
+ html += `<span class="time-badge">Up: ${escapeHtml(proc.elapsed)}</span>`;
675
757
  html += '</div>';
676
758
  html += '</div>';
677
759
  html += '</div>';
@@ -690,18 +772,18 @@ class AgentFlowDashboard {
690
772
  renderTraceList() {
691
773
  var container = document.getElementById('traceList');
692
774
  var countEl = document.getElementById('traceCount');
693
- var self = this;
694
775
 
695
776
  var filtered = this.traces;
696
777
 
697
778
  // Search filter
698
779
  if (this.searchFilter) {
699
780
  var sf = this.searchFilter;
700
- filtered = filtered.filter(function(t) {
701
- return (t.agentId || '').toLowerCase().indexOf(sf) >= 0 ||
702
- (t.name || '').toLowerCase().indexOf(sf) >= 0 ||
703
- (t.filename || '').toLowerCase().indexOf(sf) >= 0;
704
- });
781
+ filtered = filtered.filter(
782
+ (t) =>
783
+ (t.agentId || '').toLowerCase().indexOf(sf) >= 0 ||
784
+ (t.name || '').toLowerCase().indexOf(sf) >= 0 ||
785
+ (t.filename || '').toLowerCase().indexOf(sf) >= 0,
786
+ );
705
787
  }
706
788
 
707
789
  // Time range filter
@@ -709,13 +791,20 @@ class AgentFlowDashboard {
709
791
  var now = Date.now();
710
792
  var cutoff;
711
793
  switch (this.timeRangeFilter) {
712
- case '1h': cutoff = now - 3600000; break;
713
- case '24h': cutoff = now - 86400000; break;
714
- case '7d': cutoff = now - 604800000; break;
715
- default: cutoff = 0;
794
+ case '1h':
795
+ cutoff = now - 3600000;
796
+ break;
797
+ case '24h':
798
+ cutoff = now - 86400000;
799
+ break;
800
+ case '7d':
801
+ cutoff = now - 604800000;
802
+ break;
803
+ default:
804
+ cutoff = 0;
716
805
  }
717
- filtered = filtered.filter(function(t) {
718
- var ts = t.timestamp ? new Date(t.timestamp).getTime() : (t.startTime || t.lastModified || 0);
806
+ filtered = filtered.filter((t) => {
807
+ var ts = t.timestamp ? new Date(t.timestamp).getTime() : t.startTime || t.lastModified || 0;
719
808
  return ts >= cutoff;
720
809
  });
721
810
  }
@@ -723,26 +812,23 @@ class AgentFlowDashboard {
723
812
  // Status filter
724
813
  if (this.statusFilter !== 'all') {
725
814
  var statusTarget = this.statusFilter;
726
- filtered = filtered.filter(function(t) {
727
- return self.getTraceStatus(t) === statusTarget;
728
- });
815
+ filtered = filtered.filter((t) => this.getTraceStatus(t) === statusTarget);
729
816
  }
730
817
 
731
818
  // Activity filter
732
819
  if (this.activityFilter && this.activityFilter !== 'all') {
733
820
  var activityTarget = this.activityFilter;
734
- filtered = filtered.filter(function(t) {
735
- return self.getTraceActivity(t) === activityTarget;
736
- });
821
+ filtered = filtered.filter((t) => this.getTraceActivity(t) === activityTarget);
737
822
  }
738
823
 
739
- countEl.textContent = filtered.length + ' of ' + this.traces.length + ' traces';
824
+ countEl.textContent = `${filtered.length} of ${this.traces.length} traces`;
740
825
 
741
826
  // Render max 100 items for performance
742
827
  var visible = filtered.slice(0, 100);
743
828
 
744
829
  if (visible.length === 0) {
745
- container.innerHTML = '<div class="empty-state" style="height:120px;"><div class="empty-state-text">No traces match the filter.</div></div>';
830
+ container.innerHTML =
831
+ '<div class="empty-state" style="height:120px;"><div class="empty-state-text">No traces match the filter.</div></div>';
746
832
  return;
747
833
  }
748
834
 
@@ -753,13 +839,30 @@ class AgentFlowDashboard {
753
839
  var isActive = this.selectedTrace && this.selectedTrace.filename === trace.filename;
754
840
  var name = trace.name || trace.agentId || trace.filename;
755
841
  var ts = this.formatTimestamp(trace.timestamp || trace.startTime || trace.lastModified);
756
- var badgeClass = status === 'success' ? 'badge-success' : status === 'failure' ? 'badge-error' : status === 'running' ? 'badge-running' : 'badge-unknown';
757
- var badgeText = status === 'success' ? 'OK' : status === 'failure' ? 'FAIL' : status === 'running' ? 'LIVE' : '?';
842
+ var badgeClass =
843
+ status === 'success'
844
+ ? 'badge-success'
845
+ : status === 'failure'
846
+ ? 'badge-error'
847
+ : status === 'running'
848
+ ? 'badge-running'
849
+ : 'badge-unknown';
850
+ var badgeText =
851
+ status === 'success'
852
+ ? 'OK'
853
+ : status === 'failure'
854
+ ? 'FAIL'
855
+ : status === 'running'
856
+ ? 'LIVE'
857
+ : '?';
758
858
 
759
859
  // Compute node stats for this trace
760
860
  var traceNodes = this.getNodesArray(trace);
761
861
  var nodeCount = traceNodes.length;
762
- var agentCount = 0, toolCount = 0, subagentCount = 0, otherCount = 0;
862
+ var agentCount = 0,
863
+ toolCount = 0,
864
+ subagentCount = 0,
865
+ otherCount = 0;
763
866
  for (var j = 0; j < traceNodes.length; j++) {
764
867
  var nt = traceNodes[j].type;
765
868
  if (nt === 'agent') agentCount++;
@@ -767,28 +870,67 @@ class AgentFlowDashboard {
767
870
  else if (nt === 'subagent') subagentCount++;
768
871
  else otherCount++;
769
872
  }
770
- var traceDuration = this.computeDuration(trace.startTime, traceNodes.length > 0 ? Math.max.apply(null, traceNodes.map(function(n) { return n.endTime ? new Date(n.endTime).getTime() : 0; }).filter(function(v) { return v > 0; })) || null : null);
771
- var sourceLabel = trace.sourceType === 'session' ? 'session' : 'trace';
772
-
773
- html += '<div class="session-item' + (isActive ? ' active' : '') + '" data-filename="' + escapeHtml(trace.filename) + '">';
774
- html += '<div class="session-id" title="' + escapeHtml(trace.filename) + '">' + escapeHtml(name.length > 45 ? name.substring(0, 42) + '...' : name) + '</div>';
873
+ var traceDuration = this.computeDuration(
874
+ trace.startTime,
875
+ traceNodes.length > 0
876
+ ? Math.max.apply(
877
+ null,
878
+ traceNodes
879
+ .map((n) => (n.endTime ? new Date(n.endTime).getTime() : 0))
880
+ .filter((v) => v > 0),
881
+ ) || null
882
+ : null,
883
+ );
884
+ var _sourceLabel = trace.sourceType === 'session' ? 'session' : 'trace';
885
+
886
+ html +=
887
+ '<div class="session-item' +
888
+ (isActive ? ' active' : '') +
889
+ '" data-filename="' +
890
+ escapeHtml(trace.filename) +
891
+ '">';
892
+ html +=
893
+ '<div class="session-id" title="' +
894
+ escapeHtml(trace.filename) +
895
+ '">' +
896
+ escapeHtml(name.length > 45 ? `${name.substring(0, 42)}...` : name) +
897
+ '</div>';
775
898
  html += '<div class="session-meta">';
776
- html += '<span class="session-agent">' + escapeHtml(trace.agentId || '') + '</span>';
777
- html += '<span>' + escapeHtml(ts) + '</span>';
778
- html += '<span class="badge ' + badgeClass + '">' + badgeText + '</span>';
899
+ html += `<span class="session-agent">${escapeHtml(trace.agentId || '')}</span>`;
900
+ html += `<span>${escapeHtml(ts)}</span>`;
901
+ html += `<span class="badge ${badgeClass}">${badgeText}</span>`;
779
902
  html += '</div>';
780
903
  // Node type breakdown + duration
781
904
  html += '<div class="session-meta" style="margin-top:3px;">';
782
- html += '<span style="font-size:0.7rem;color:var(--accent-primary);">' + nodeCount + ' nodes</span>';
783
- if (agentCount > 0) html += '<span class="badge badge-type badge-agent">' + agentCount + ' agent</span>';
784
- if (toolCount > 0) html += '<span class="badge badge-type badge-tool">' + toolCount + ' tool</span>';
785
- if (subagentCount > 0) html += '<span class="badge badge-type badge-subagent">' + subagentCount + ' sub</span>';
786
- if (otherCount > 0) html += '<span class="badge badge-type badge-other">' + otherCount + ' other</span>';
787
- if (traceDuration !== '--') html += '<span style="font-size:0.7rem;color:var(--text-secondary);">' + escapeHtml(traceDuration) + '</span>';
905
+ html +=
906
+ '<span style="font-size:0.7rem;color:var(--accent-primary);">' +
907
+ nodeCount +
908
+ ' nodes</span>';
909
+ if (agentCount > 0)
910
+ html += `<span class="badge badge-type badge-agent">${agentCount} agent</span>`;
911
+ if (toolCount > 0)
912
+ html += `<span class="badge badge-type badge-tool">${toolCount} tool</span>`;
913
+ if (subagentCount > 0)
914
+ html += `<span class="badge badge-type badge-subagent">${subagentCount} sub</span>`;
915
+ if (otherCount > 0)
916
+ html += `<span class="badge badge-type badge-other">${otherCount} other</span>`;
917
+ if (traceDuration !== '--')
918
+ html +=
919
+ '<span style="font-size:0.7rem;color:var(--text-secondary);">' +
920
+ escapeHtml(traceDuration) +
921
+ '</span>';
788
922
  if (trace.tokenUsage && trace.tokenUsage.total > 0) {
789
- html += '<span style="font-size:0.7rem;color:#bc8cff;">' + (trace.tokenUsage.total > 1000 ? Math.round(trace.tokenUsage.total/1000) + 'k' : trace.tokenUsage.total) + ' tok</span>';
923
+ html +=
924
+ '<span style="font-size:0.7rem;color:#bc8cff;">' +
925
+ (trace.tokenUsage.total > 1000
926
+ ? `${Math.round(trace.tokenUsage.total / 1000)}k`
927
+ : trace.tokenUsage.total) +
928
+ ' tok</span>';
790
929
  if (trace.tokenUsage.cost > 0) {
791
- html += '<span style="font-size:0.7rem;color:#f0883e;">$' + trace.tokenUsage.cost.toFixed(4) + '</span>';
930
+ html +=
931
+ '<span style="font-size:0.7rem;color:#f0883e;">$' +
932
+ trace.tokenUsage.cost.toFixed(4) +
933
+ '</span>';
792
934
  }
793
935
  }
794
936
  html += '</div>';
@@ -802,11 +944,11 @@ class AgentFlowDashboard {
802
944
  if (!trace.nodes) return 'unknown';
803
945
  var nodes = this.getNodesArray(trace);
804
946
  if (nodes.length === 0) return 'unknown';
805
- var hasFailed = nodes.some(function(n) { return n.status === 'failed' || (n.metadata && n.metadata.error); });
947
+ var hasFailed = nodes.some((n) => n.status === 'failed' || n.metadata?.error);
806
948
  if (hasFailed) return 'failure';
807
- var hasRunning = nodes.some(function(n) { return n.status === 'running'; });
949
+ var hasRunning = nodes.some((n) => n.status === 'running');
808
950
  if (hasRunning) return 'running';
809
- var hasCompleted = nodes.some(function(n) { return n.status === 'completed' || n.endTime; });
951
+ var hasCompleted = nodes.some((n) => n.status === 'completed' || n.endTime);
810
952
  if (hasCompleted) return 'success';
811
953
  return 'unknown';
812
954
  }
@@ -814,7 +956,7 @@ class AgentFlowDashboard {
814
956
  getNodesArray(trace) {
815
957
  if (!trace.nodes) return [];
816
958
  if (Array.isArray(trace.nodes)) {
817
- return trace.nodes.map(function(entry) { return Array.isArray(entry) ? entry[1] : entry; });
959
+ return trace.nodes.map((entry) => (Array.isArray(entry) ? entry[1] : entry));
818
960
  }
819
961
  if (trace.nodes instanceof Map) return Array.from(trace.nodes.values());
820
962
  return Object.values(trace.nodes);
@@ -823,29 +965,33 @@ class AgentFlowDashboard {
823
965
  formatTimestamp(ts) {
824
966
  if (!ts) return '';
825
967
  var d = new Date(ts);
826
- if (isNaN(d.getTime())) return String(ts);
968
+ if (Number.isNaN(d.getTime())) return String(ts);
827
969
  var now = new Date();
828
970
  var diffMs = now - d;
829
971
  if (diffMs < 60000) return 'just now';
830
- if (diffMs < 3600000) return Math.floor(diffMs / 60000) + 'm ago';
831
- if (diffMs < 86400000) return Math.floor(diffMs / 3600000) + 'h ago';
832
- return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
972
+ if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)}m ago`;
973
+ if (diffMs < 86400000) return `${Math.floor(diffMs / 3600000)}h ago`;
974
+ return (
975
+ d.toLocaleDateString() +
976
+ ' ' +
977
+ d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
978
+ );
833
979
  }
834
980
 
835
981
  computeDuration(startTime, endTime) {
836
982
  if (!startTime || !endTime) return '--';
837
983
  var ms = new Date(endTime).getTime() - new Date(startTime).getTime();
838
- if (isNaN(ms) || ms < 0) return '--';
839
- if (ms < 1000) return ms + 'ms';
840
- if (ms < 60000) return (ms / 1000).toFixed(1) + 's';
841
- return (ms / 60000).toFixed(1) + 'm';
984
+ if (Number.isNaN(ms) || ms < 0) return '--';
985
+ if (ms < 1000) return `${ms}ms`;
986
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
987
+ return `${(ms / 60000).toFixed(1)}m`;
842
988
  }
843
989
 
844
990
  formatDuration(ms) {
845
991
  if (!ms || ms <= 0) return '--';
846
- if (ms < 1000) return Math.round(ms) + 'ms';
847
- if (ms < 60000) return (ms / 1000).toFixed(1) + 's';
848
- return (ms / 60000).toFixed(1) + 'm';
992
+ if (ms < 1000) return `${Math.round(ms)}ms`;
993
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
994
+ return `${(ms / 60000).toFixed(1)}m`;
849
995
  }
850
996
 
851
997
  // ---------------------------------------------------------------------------
@@ -853,15 +999,33 @@ class AgentFlowDashboard {
853
999
  // ---------------------------------------------------------------------------
854
1000
  renderActiveTab() {
855
1001
  switch (this.activeTab) {
856
- case 'timeline': this.renderTimeline(); break;
857
- case 'metrics': this.renderMetrics(); break;
858
- case 'graph': this.renderGraph(); break;
859
- case 'heatmap': this.renderHeatmap(); break;
860
- case 'state': this.renderStateMachine(); break;
861
- case 'summary': this.renderSummary(); break;
862
- case 'transcript': this.renderTranscript(); break;
863
- case 'agenttimeline': this.renderAgentTimeline(); break;
864
- case 'processmap': this.renderProcessMap(); break;
1002
+ case 'timeline':
1003
+ this.renderTimeline();
1004
+ break;
1005
+ case 'metrics':
1006
+ this.renderMetrics();
1007
+ break;
1008
+ case 'graph':
1009
+ this.renderGraph();
1010
+ break;
1011
+ case 'heatmap':
1012
+ this.renderHeatmap();
1013
+ break;
1014
+ case 'state':
1015
+ this.renderStateMachine();
1016
+ break;
1017
+ case 'summary':
1018
+ this.renderSummary();
1019
+ break;
1020
+ case 'transcript':
1021
+ this.renderTranscript();
1022
+ break;
1023
+ case 'agenttimeline':
1024
+ this.renderAgentTimeline();
1025
+ break;
1026
+ case 'processmap':
1027
+ this.renderProcessMap();
1028
+ break;
865
1029
  }
866
1030
  this.updateToolbarInfo();
867
1031
  }
@@ -874,7 +1038,7 @@ class AgentFlowDashboard {
874
1038
  return;
875
1039
  }
876
1040
  var nodes = this.getNodesArray(trace);
877
- info.textContent = nodes.length + ' nodes' + (trace.agentId ? ' | ' + trace.agentId : '');
1041
+ info.textContent = `${nodes.length} nodes${trace.agentId ? ` | ${trace.agentId}` : ''}`;
878
1042
  }
879
1043
 
880
1044
  // ---------------------------------------------------------------------------
@@ -884,7 +1048,8 @@ class AgentFlowDashboard {
884
1048
  var container = document.getElementById('timelineContent');
885
1049
  var trace = this.selectedTraceData || this.selectedTrace;
886
1050
  if (!trace || !trace.nodes) {
887
- container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">&#9776;</div><div class="empty-state-title">Select a trace</div><div class="empty-state-text">Choose a trace from the sidebar to view its execution timeline.</div></div>';
1051
+ container.innerHTML =
1052
+ '<div class="empty-state"><div class="empty-state-icon">&#9776;</div><div class="empty-state-title">Select a trace</div><div class="empty-state-text">Choose a trace from the sidebar to view its execution timeline.</div></div>';
888
1053
  return;
889
1054
  }
890
1055
 
@@ -896,7 +1061,8 @@ class AgentFlowDashboard {
896
1061
 
897
1062
  var nodes = this.getNodesArray(trace);
898
1063
  if (nodes.length === 0) {
899
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">No nodes in this trace.</div></div>';
1064
+ container.innerHTML =
1065
+ '<div class="empty-state"><div class="empty-state-text">No nodes in this trace.</div></div>';
900
1066
  return;
901
1067
  }
902
1068
 
@@ -906,11 +1072,14 @@ class AgentFlowDashboard {
906
1072
  if (nodes[j].id) nodeMap[nodes[j].id] = nodes[j];
907
1073
  }
908
1074
  var depthCache = {};
909
- var getDepth = function(nid, visited) {
910
- if (!nid || (visited && visited.has(nid))) return 0;
1075
+ var getDepth = (nid, visited) => {
1076
+ if (!nid || visited?.has(nid)) return 0;
911
1077
  if (depthCache[nid] !== undefined) return depthCache[nid];
912
1078
  var nd = nodeMap[nid];
913
- if (!nd || !nd.parentId) { depthCache[nid] = 0; return 0; }
1079
+ if (!nd || !nd.parentId) {
1080
+ depthCache[nid] = 0;
1081
+ return 0;
1082
+ }
914
1083
  var vis = visited || new Set();
915
1084
  vis.add(nid);
916
1085
  depthCache[nid] = 1 + getDepth(nd.parentId, vis);
@@ -919,14 +1088,18 @@ class AgentFlowDashboard {
919
1088
  for (var k = 0; k < nodes.length; k++) getDepth(nodes[k].id);
920
1089
 
921
1090
  // Compute timeline range for duration bars
922
- var allStarts = nodes.map(function(n) { return n.startTime ? new Date(n.startTime).getTime() : Infinity; }).filter(function(v) { return isFinite(v); });
923
- var allEnds = nodes.map(function(n) { return n.endTime ? new Date(n.endTime).getTime() : 0; }).filter(function(v) { return v > 0; });
1091
+ var allStarts = nodes
1092
+ .map((n) => (n.startTime ? new Date(n.startTime).getTime() : Infinity))
1093
+ .filter((v) => Number.isFinite(v));
1094
+ var allEnds = nodes
1095
+ .map((n) => (n.endTime ? new Date(n.endTime).getTime() : 0))
1096
+ .filter((v) => v > 0);
924
1097
  var timelineStart = allStarts.length > 0 ? Math.min.apply(null, allStarts) : 0;
925
1098
  var timelineEnd = allEnds.length > 0 ? Math.max.apply(null, allEnds) : 0;
926
1099
  var timelineSpan = timelineEnd - timelineStart || 1;
927
1100
 
928
1101
  // Sort by startTime then depth
929
- var sorted = nodes.slice().sort(function(a, b) {
1102
+ var sorted = nodes.slice().sort((a, b) => {
930
1103
  var sa = a.startTime ? new Date(a.startTime).getTime() : Infinity;
931
1104
  var sb = b.startTime ? new Date(b.startTime).getTime() : Infinity;
932
1105
  if (sa !== sb) return sa - sb;
@@ -934,45 +1107,90 @@ class AgentFlowDashboard {
934
1107
  });
935
1108
 
936
1109
  // Type icons
937
- var typeIcons = { agent: '\ud83e\udd16', tool: '\ud83d\udee0\ufe0f', subagent: '\ud83d\udc64', wait: '\u23f3', decision: '\ud83d\udd00', custom: '\u2b50', exec: '\u25b6\ufe0f' };
938
- var statusIcons = { completed: '\u2705', failed: '\u274c', running: '\ud83d\udfe2', hung: '\u26a0\ufe0f', timeout: '\u23f0' };
1110
+ var typeIcons = {
1111
+ agent: '\ud83e\udd16',
1112
+ tool: '\ud83d\udee0\ufe0f',
1113
+ subagent: '\ud83d\udc64',
1114
+ wait: '\u23f3',
1115
+ decision: '\ud83d\udd00',
1116
+ custom: '\u2b50',
1117
+ exec: '\u25b6\ufe0f',
1118
+ };
1119
+ var statusIcons = {
1120
+ completed: '\u2705',
1121
+ failed: '\u274c',
1122
+ running: '\ud83d\udfe2',
1123
+ hung: '\u26a0\ufe0f',
1124
+ timeout: '\u23f0',
1125
+ };
939
1126
 
940
1127
  var html = '';
941
1128
  // Summary header
942
1129
  html += '<div style="display:flex;gap:12px;margin-bottom:12px;flex-wrap:wrap;">';
943
1130
  var typeCounts = {};
944
- for (var m = 0; m < sorted.length; m++) { var tt = sorted[m].type || 'unknown'; typeCounts[tt] = (typeCounts[tt] || 0) + 1; }
945
- html += '<span style="font-size:0.85rem;color:var(--text-secondary);">' + sorted.length + ' nodes</span>';
1131
+ for (var m = 0; m < sorted.length; m++) {
1132
+ var tt = sorted[m].type || 'unknown';
1133
+ typeCounts[tt] = (typeCounts[tt] || 0) + 1;
1134
+ }
1135
+ html +=
1136
+ '<span style="font-size:0.85rem;color:var(--text-secondary);">' +
1137
+ sorted.length +
1138
+ ' nodes</span>';
946
1139
  var typeEntries = Object.entries(typeCounts);
947
1140
  for (var p = 0; p < typeEntries.length; p++) {
948
1141
  var tIcon = typeIcons[typeEntries[p][0]] || '\u25cf';
949
- html += '<span class="badge badge-type badge-' + escapeHtml(typeEntries[p][0]) + '">' + tIcon + ' ' + typeEntries[p][1] + ' ' + escapeHtml(typeEntries[p][0]) + '</span>';
1142
+ html +=
1143
+ '<span class="badge badge-type badge-' +
1144
+ escapeHtml(typeEntries[p][0]) +
1145
+ '">' +
1146
+ tIcon +
1147
+ ' ' +
1148
+ typeEntries[p][1] +
1149
+ ' ' +
1150
+ escapeHtml(typeEntries[p][0]) +
1151
+ '</span>';
950
1152
  }
951
1153
  if (timelineSpan > 1) {
952
- html += '<span style="font-size:0.85rem;color:var(--text-secondary);">Total: ' + this.formatDuration(timelineSpan) + '</span>';
1154
+ html +=
1155
+ '<span style="font-size:0.85rem;color:var(--text-secondary);">Total: ' +
1156
+ this.formatDuration(timelineSpan) +
1157
+ '</span>';
953
1158
  }
954
1159
  html += '</div>';
955
1160
 
956
1161
  for (var i = 0; i < sorted.length; i++) {
957
1162
  var n = sorted[i];
958
1163
  var depth = depthCache[n.id] || 0;
959
- var markerClass = n.status === 'failed' ? 'failed' :
960
- n.status === 'completed' ? 'completed' :
961
- n.status === 'running' ? 'running' :
962
- n.status === 'hung' || n.status === 'timeout' ? 'hung' :
963
- n.type === 'agent' ? 'agent' :
964
- n.type === 'tool' ? 'tool' :
965
- n.type === 'subagent' ? 'subagent' : 'agent';
1164
+ var markerClass =
1165
+ n.status === 'failed'
1166
+ ? 'failed'
1167
+ : n.status === 'completed'
1168
+ ? 'completed'
1169
+ : n.status === 'running'
1170
+ ? 'running'
1171
+ : n.status === 'hung' || n.status === 'timeout'
1172
+ ? 'hung'
1173
+ : n.type === 'agent'
1174
+ ? 'agent'
1175
+ : n.type === 'tool'
1176
+ ? 'tool'
1177
+ : n.type === 'subagent'
1178
+ ? 'subagent'
1179
+ : 'agent';
966
1180
 
967
1181
  var typeIcon = typeIcons[n.type] || '\u25cf';
968
1182
  var statusIcon = statusIcons[n.status] || '';
969
1183
  var eventName = escapeHtml(n.name || n.id || 'unnamed');
970
1184
  var eventTs = n.startTime ? new Date(n.startTime).toLocaleTimeString() : '--';
971
1185
  var dur = this.computeDuration(n.startTime, n.endTime);
972
- var durMs = (n.startTime && n.endTime) ? new Date(n.endTime).getTime() - new Date(n.startTime).getTime() : 0;
1186
+ var durMs =
1187
+ n.startTime && n.endTime
1188
+ ? new Date(n.endTime).getTime() - new Date(n.startTime).getTime()
1189
+ : 0;
973
1190
 
974
1191
  // Duration bar width proportional to timeline
975
- var barLeft = 0, barWidth = 0;
1192
+ var barLeft = 0,
1193
+ barWidth = 0;
976
1194
  if (n.startTime && timelineSpan > 1) {
977
1195
  barLeft = ((new Date(n.startTime).getTime() - timelineStart) / timelineSpan) * 100;
978
1196
  barWidth = Math.max(1, (durMs / timelineSpan) * 100);
@@ -980,37 +1198,67 @@ class AgentFlowDashboard {
980
1198
 
981
1199
  var details = '';
982
1200
  if (n.metadata) {
983
- var showKeys = Object.keys(n.metadata).filter(function(k) {
984
- return k !== 'error' && typeof n.metadata[k] !== 'object';
985
- });
1201
+ var showKeys = Object.keys(n.metadata).filter(
1202
+ (k) => k !== 'error' && typeof n.metadata[k] !== 'object',
1203
+ );
986
1204
  if (showKeys.length > 0) {
987
- details = showKeys.slice(0, 4).map(function(k) {
988
- return escapeHtml(k) + ': ' + escapeHtml(String(n.metadata[k]).substring(0, 50));
989
- }).join(' \u00b7 ');
1205
+ details = showKeys
1206
+ .slice(0, 4)
1207
+ .map((k) => `${escapeHtml(k)}: ${escapeHtml(String(n.metadata[k]).substring(0, 50))}`)
1208
+ .join(' \u00b7 ');
990
1209
  }
991
1210
  }
992
1211
 
993
1212
  var indent = depth * 24;
994
- html += '<div class="timeline-item" style="margin-left:' + indent + 'px;">';
995
- html += '<div class="timeline-marker ' + markerClass + '"></div>';
1213
+ html += `<div class="timeline-item" style="margin-left:${indent}px;">`;
1214
+ html += `<div class="timeline-marker ${markerClass}"></div>`;
996
1215
  html += '<div class="timeline-content">';
997
1216
  html += '<div class="timeline-header">';
998
- html += '<span class="event-type">' + typeIcon + ' <span class="badge badge-type badge-' + escapeHtml(n.type || 'unknown') + '" style="font-size:0.7rem;">' + escapeHtml(n.type || 'node') + '</span> ' + eventName + ' ' + statusIcon + '</span>';
999
- html += '<span class="event-time">' + eventTs;
1000
- if (dur !== '--') html += ' \u00b7 <strong>' + escapeHtml(dur) + '</strong>';
1217
+ html +=
1218
+ '<span class="event-type">' +
1219
+ typeIcon +
1220
+ ' <span class="badge badge-type badge-' +
1221
+ escapeHtml(n.type || 'unknown') +
1222
+ '" style="font-size:0.7rem;">' +
1223
+ escapeHtml(n.type || 'node') +
1224
+ '</span> ' +
1225
+ eventName +
1226
+ ' ' +
1227
+ statusIcon +
1228
+ '</span>';
1229
+ html += `<span class="event-time">${eventTs}`;
1230
+ if (dur !== '--') html += ` \u00b7 <strong>${escapeHtml(dur)}</strong>`;
1001
1231
  html += '</span></div>';
1002
1232
  // Duration bar
1003
1233
  if (barWidth > 0) {
1004
- var barColor = n.status === 'failed' ? 'var(--accent-error)' : n.status === 'completed' ? 'var(--accent-success)' : n.status === 'running' ? 'var(--accent-primary)' : 'var(--accent-warning)';
1005
- html += '<div style="position:relative;height:6px;background:var(--bg-tertiary);border-radius:3px;margin:4px 0;">';
1006
- html += '<div style="position:absolute;left:' + barLeft.toFixed(1) + '%;width:' + barWidth.toFixed(1) + '%;height:100%;background:' + barColor + ';border-radius:3px;"></div>';
1234
+ var barColor =
1235
+ n.status === 'failed'
1236
+ ? 'var(--accent-error)'
1237
+ : n.status === 'completed'
1238
+ ? 'var(--accent-success)'
1239
+ : n.status === 'running'
1240
+ ? 'var(--accent-primary)'
1241
+ : 'var(--accent-warning)';
1242
+ html +=
1243
+ '<div style="position:relative;height:6px;background:var(--bg-tertiary);border-radius:3px;margin:4px 0;">';
1244
+ html +=
1245
+ '<div style="position:absolute;left:' +
1246
+ barLeft.toFixed(1) +
1247
+ '%;width:' +
1248
+ barWidth.toFixed(1) +
1249
+ '%;height:100%;background:' +
1250
+ barColor +
1251
+ ';border-radius:3px;"></div>';
1007
1252
  html += '</div>';
1008
1253
  }
1009
1254
  if (details) {
1010
- html += '<div class="event-details">' + details + '</div>';
1255
+ html += `<div class="event-details">${details}</div>`;
1011
1256
  }
1012
- if (n.metadata && n.metadata.error) {
1013
- html += '<div class="event-details" style="color:var(--accent-error);">\u274c ' + escapeHtml(String(n.metadata.error).substring(0, 120)) + '</div>';
1257
+ if (n.metadata?.error) {
1258
+ html +=
1259
+ '<div class="event-details" style="color:var(--accent-error);">\u274c ' +
1260
+ escapeHtml(String(n.metadata.error).substring(0, 120)) +
1261
+ '</div>';
1014
1262
  }
1015
1263
  html += '</div></div>';
1016
1264
  }
@@ -1025,25 +1273,28 @@ class AgentFlowDashboard {
1025
1273
  var container = document.getElementById('metricsContent');
1026
1274
  var trace = this.selectedTraceData || this.selectedTrace;
1027
1275
  if (!trace || !trace.nodes) {
1028
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">Select a trace to view metrics.</div></div>';
1276
+ container.innerHTML =
1277
+ '<div class="empty-state"><div class="empty-state-text">Select a trace to view metrics.</div></div>';
1029
1278
  return;
1030
1279
  }
1031
1280
 
1032
1281
  var nodes = this.getNodesArray(trace);
1033
1282
  var totalNodes = nodes.length;
1034
- var completedNodes = nodes.filter(function(n) { return n.status === 'completed'; }).length;
1035
- var failedNodes = nodes.filter(function(n) { return n.status === 'failed'; }).length;
1036
- var runningNodes = nodes.filter(function(n) { return n.status === 'running'; }).length;
1037
- var hungNodes = nodes.filter(function(n) { return n.status === 'hung' || n.status === 'timeout'; }).length;
1038
- var successRate = totalNodes > 0 ? Math.round(completedNodes / totalNodes * 1000) / 10 : 0;
1283
+ var completedNodes = nodes.filter((n) => n.status === 'completed').length;
1284
+ var failedNodes = nodes.filter((n) => n.status === 'failed').length;
1285
+ var runningNodes = nodes.filter((n) => n.status === 'running').length;
1286
+ var _hungNodes = nodes.filter((n) => n.status === 'hung' || n.status === 'timeout').length;
1287
+ var successRate = totalNodes > 0 ? Math.round((completedNodes / totalNodes) * 1000) / 10 : 0;
1039
1288
 
1040
1289
  // Compute average and max duration
1041
- var totalDur = 0, durCount = 0, maxDur = 0;
1290
+ var totalDur = 0,
1291
+ durCount = 0,
1292
+ maxDur = 0;
1042
1293
  for (var i = 0; i < nodes.length; i++) {
1043
1294
  var n = nodes[i];
1044
1295
  if (n.startTime && n.endTime) {
1045
1296
  var ms = new Date(n.endTime).getTime() - new Date(n.startTime).getTime();
1046
- if (!isNaN(ms) && ms >= 0) {
1297
+ if (!Number.isNaN(ms) && ms >= 0) {
1047
1298
  totalDur += ms;
1048
1299
  durCount++;
1049
1300
  if (ms > maxDur) maxDur = ms;
@@ -1058,7 +1309,7 @@ class AgentFlowDashboard {
1058
1309
  if (nodes[j].id) nodeMap[nodes[j].id] = nodes[j];
1059
1310
  }
1060
1311
  var maxDepth = 0;
1061
- var depthOf = function(nid, visited) {
1312
+ var depthOf = (nid, visited) => {
1062
1313
  if (!nid || visited.has(nid)) return 0;
1063
1314
  visited.add(nid);
1064
1315
  var nd = nodeMap[nid];
@@ -1078,33 +1329,86 @@ class AgentFlowDashboard {
1078
1329
 
1079
1330
  var html = '<div class="metrics-grid">';
1080
1331
  html += this.metricCard('Total Nodes', totalNodes, 'primary');
1081
- html += this.metricCard('Success Rate', successRate + '%', successRate >= 90 ? 'success' : successRate >= 70 ? 'warning' : 'error');
1082
- html += this.metricCard('Avg Duration', this.formatDuration(avgDur), 'primary', durCount > 0 ? 'across ' + durCount + ' nodes' : 'no timing data');
1083
- html += this.metricCard('Max Duration', this.formatDuration(maxDur), 'primary', 'tool execution time');
1332
+ html += this.metricCard(
1333
+ 'Success Rate',
1334
+ `${successRate}%`,
1335
+ successRate >= 90 ? 'success' : successRate >= 70 ? 'warning' : 'error',
1336
+ );
1337
+ html += this.metricCard(
1338
+ 'Avg Duration',
1339
+ this.formatDuration(avgDur),
1340
+ 'primary',
1341
+ durCount > 0 ? `across ${durCount} nodes` : 'no timing data',
1342
+ );
1343
+ html += this.metricCard(
1344
+ 'Max Duration',
1345
+ this.formatDuration(maxDur),
1346
+ 'primary',
1347
+ 'tool execution time',
1348
+ );
1084
1349
  html += this.metricCard('Max Depth', maxDepth, 'primary');
1085
1350
  html += this.metricCard('Failures', failedNodes, failedNodes > 0 ? 'error' : 'success');
1086
- html += this.metricCard('Running/Active', runningNodes, runningNodes > 0 ? 'warning' : 'primary');
1351
+ html += this.metricCard(
1352
+ 'Running/Active',
1353
+ runningNodes,
1354
+ runningNodes > 0 ? 'warning' : 'primary',
1355
+ );
1087
1356
  html += this.metricCard('Completed', completedNodes, 'success');
1088
1357
  html += '</div>';
1089
1358
 
1090
1359
  // Token/cost metrics for session traces
1091
1360
  if (trace.tokenUsage && trace.tokenUsage.total > 0) {
1092
- html += '<h4 style="margin:1.5rem 0 0.75rem;font-size:0.85rem;color:var(--text-secondary);">Token Usage</h4>';
1361
+ html +=
1362
+ '<h4 style="margin:1.5rem 0 0.75rem;font-size:0.85rem;color:var(--text-secondary);">Token Usage</h4>';
1093
1363
  html += '<div class="metrics-grid">';
1094
- html += this.metricCard('Total Tokens', trace.tokenUsage.total > 1000 ? Math.round(trace.tokenUsage.total / 1000) + 'k' : trace.tokenUsage.total, 'primary');
1095
- html += this.metricCard('Input Tokens', trace.tokenUsage.input > 1000 ? Math.round(trace.tokenUsage.input / 1000) + 'k' : trace.tokenUsage.input, 'primary');
1096
- html += this.metricCard('Output Tokens', trace.tokenUsage.output > 1000 ? Math.round(trace.tokenUsage.output / 1000) + 'k' : trace.tokenUsage.output, 'primary');
1097
- html += this.metricCard('Estimated Cost', trace.tokenUsage.cost > 0 ? '$' + trace.tokenUsage.cost.toFixed(4) : '$0', trace.tokenUsage.cost > 0.10 ? 'warning' : 'success');
1098
- if (totalNodes > 0) html += this.metricCard('Tokens/Node', Math.round(trace.tokenUsage.total / totalNodes), 'primary');
1099
- var modelName = (trace.metadata && trace.metadata.model) || '';
1100
- if (modelName) html += this.metricCard('Model', modelName.length > 20 ? modelName.slice(0, 18) + '..' : modelName, 'primary', (trace.metadata && trace.metadata.provider) || '');
1364
+ html += this.metricCard(
1365
+ 'Total Tokens',
1366
+ trace.tokenUsage.total > 1000
1367
+ ? `${Math.round(trace.tokenUsage.total / 1000)}k`
1368
+ : trace.tokenUsage.total,
1369
+ 'primary',
1370
+ );
1371
+ html += this.metricCard(
1372
+ 'Input Tokens',
1373
+ trace.tokenUsage.input > 1000
1374
+ ? `${Math.round(trace.tokenUsage.input / 1000)}k`
1375
+ : trace.tokenUsage.input,
1376
+ 'primary',
1377
+ );
1378
+ html += this.metricCard(
1379
+ 'Output Tokens',
1380
+ trace.tokenUsage.output > 1000
1381
+ ? `${Math.round(trace.tokenUsage.output / 1000)}k`
1382
+ : trace.tokenUsage.output,
1383
+ 'primary',
1384
+ );
1385
+ html += this.metricCard(
1386
+ 'Estimated Cost',
1387
+ trace.tokenUsage.cost > 0 ? `$${trace.tokenUsage.cost.toFixed(4)}` : '$0',
1388
+ trace.tokenUsage.cost > 0.1 ? 'warning' : 'success',
1389
+ );
1390
+ if (totalNodes > 0)
1391
+ html += this.metricCard(
1392
+ 'Tokens/Node',
1393
+ Math.round(trace.tokenUsage.total / totalNodes),
1394
+ 'primary',
1395
+ );
1396
+ var modelName = trace.metadata?.model || '';
1397
+ if (modelName)
1398
+ html += this.metricCard(
1399
+ 'Model',
1400
+ modelName.length > 20 ? `${modelName.slice(0, 18)}..` : modelName,
1401
+ 'primary',
1402
+ trace.metadata?.provider || '',
1403
+ );
1101
1404
  html += '</div>';
1102
1405
  }
1103
1406
 
1104
1407
  // Type breakdown
1105
- html += '<h4 style="margin:1.5rem 0 0.75rem;font-size:0.85rem;color:var(--text-secondary);">Node Type Breakdown</h4>';
1408
+ html +=
1409
+ '<h4 style="margin:1.5rem 0 0.75rem;font-size:0.85rem;color:var(--text-secondary);">Node Type Breakdown</h4>';
1106
1410
  html += '<div class="metrics-grid">';
1107
- var typeEntries = Object.entries(typeCounts).sort(function(a, b) { return b[1] - a[1]; });
1411
+ var typeEntries = Object.entries(typeCounts).sort((a, b) => b[1] - a[1]);
1108
1412
  for (var p = 0; p < typeEntries.length; p++) {
1109
1413
  html += this.metricCard(typeEntries[p][0], typeEntries[p][1], 'primary');
1110
1414
  }
@@ -1114,8 +1418,15 @@ class AgentFlowDashboard {
1114
1418
  }
1115
1419
 
1116
1420
  metricCard(label, value, colorClass, sub) {
1117
- var html = '<div class="metric-card"><div class="metric-label">' + escapeHtml(label) + '</div><div class="metric-value ' + colorClass + '">' + escapeHtml(String(value)) + '</div>';
1118
- if (sub) html += '<div class="metric-sub">' + escapeHtml(sub) + '</div>';
1421
+ var html =
1422
+ '<div class="metric-card"><div class="metric-label">' +
1423
+ escapeHtml(label) +
1424
+ '</div><div class="metric-value ' +
1425
+ colorClass +
1426
+ '">' +
1427
+ escapeHtml(String(value)) +
1428
+ '</div>';
1429
+ if (sub) html += `<div class="metric-sub">${escapeHtml(sub)}</div>`;
1119
1430
  html += '</div>';
1120
1431
  return html;
1121
1432
  }
@@ -1127,7 +1438,10 @@ class AgentFlowDashboard {
1127
1438
  var trace = this.selectedTraceData || this.selectedTrace;
1128
1439
  if (!trace || !trace.nodes) {
1129
1440
  document.getElementById('graphEmpty').style.display = '';
1130
- if (this.cy) { this.cy.destroy(); this.cy = null; }
1441
+ if (this.cy) {
1442
+ this.cy.destroy();
1443
+ this.cy = null;
1444
+ }
1131
1445
  return;
1132
1446
  }
1133
1447
 
@@ -1145,14 +1459,18 @@ class AgentFlowDashboard {
1145
1459
 
1146
1460
  // Collect valid IDs
1147
1461
  if (typeof trace.nodes === 'object' && !Array.isArray(trace.nodes)) {
1148
- Object.keys(trace.nodes).forEach(function(key) { nodeIds.add(key); });
1462
+ Object.keys(trace.nodes).forEach((key) => {
1463
+ nodeIds.add(key);
1464
+ });
1149
1465
  }
1150
- nodes.forEach(function(n) { if (n.id) nodeIds.add(n.id); });
1466
+ nodes.forEach((n) => {
1467
+ if (n.id) nodeIds.add(n.id);
1468
+ });
1151
1469
 
1152
1470
  // Add nodes
1153
1471
  for (var i = 0; i < nodes.length; i++) {
1154
1472
  var node = nodes[i];
1155
- var id = node.id || ('n-' + i);
1473
+ var id = node.id || `n-${i}`;
1156
1474
  elements.push({
1157
1475
  group: 'nodes',
1158
1476
  data: {
@@ -1160,8 +1478,8 @@ class AgentFlowDashboard {
1160
1478
  label: node.name || node.type || id,
1161
1479
  status: node.status || 'unknown',
1162
1480
  nodeType: node.type || 'custom',
1163
- fullData: node
1164
- }
1481
+ fullData: node,
1482
+ },
1165
1483
  });
1166
1484
  }
1167
1485
 
@@ -1174,8 +1492,8 @@ class AgentFlowDashboard {
1174
1492
  data: {
1175
1493
  source: n.parentId,
1176
1494
  target: n.id,
1177
- id: 'e-' + n.parentId + '-' + n.id
1178
- }
1495
+ id: `e-${n.parentId}-${n.id}`,
1496
+ },
1179
1497
  });
1180
1498
  }
1181
1499
  }
@@ -1187,11 +1505,11 @@ class AgentFlowDashboard {
1187
1505
  var src = edge.source || edge.from;
1188
1506
  var tgt = edge.target || edge.to;
1189
1507
  if (src && tgt && nodeIds.has(src) && nodeIds.has(tgt)) {
1190
- var eid = 'e-' + src + '-' + tgt;
1191
- if (!elements.some(function(el) { return el.data && el.data.id === eid; })) {
1508
+ var eid = `e-${src}-${tgt}`;
1509
+ if (!elements.some((el) => el.data && el.data.id === eid)) {
1192
1510
  elements.push({
1193
1511
  group: 'edges',
1194
- data: { source: src, target: tgt, id: eid, edgeType: edge.type || '' }
1512
+ data: { source: src, target: tgt, id: eid, edgeType: edge.type || '' },
1195
1513
  });
1196
1514
  }
1197
1515
  }
@@ -1199,7 +1517,10 @@ class AgentFlowDashboard {
1199
1517
  }
1200
1518
 
1201
1519
  // Destroy previous instance
1202
- if (this.cy) { this.cy.destroy(); this.cy = null; }
1520
+ if (this.cy) {
1521
+ this.cy.destroy();
1522
+ this.cy = null;
1523
+ }
1203
1524
 
1204
1525
  var cyContainer = document.getElementById('cy');
1205
1526
 
@@ -1210,46 +1531,76 @@ class AgentFlowDashboard {
1210
1531
  {
1211
1532
  selector: 'node',
1212
1533
  style: {
1213
- 'label': 'data(label)',
1214
- 'width': 45,
1215
- 'height': 45,
1534
+ label: 'data(label)',
1535
+ width: 45,
1536
+ height: 45,
1216
1537
  'font-size': '10px',
1217
1538
  'text-valign': 'bottom',
1218
1539
  'text-halign': 'center',
1219
1540
  'text-margin-y': 6,
1220
- 'color': '#c9d1d9',
1541
+ color: '#c9d1d9',
1221
1542
  'text-outline-color': '#0d1117',
1222
1543
  'text-outline-width': 2,
1223
1544
  'border-width': 2,
1224
1545
  'border-color': '#30363d',
1225
- 'background-color': '#3b82f6'
1226
- }
1546
+ 'background-color': '#3b82f6',
1547
+ },
1548
+ },
1549
+ {
1550
+ selector: 'node[status="completed"]',
1551
+ style: { 'background-color': '#10b981', 'border-color': '#2ea043' },
1552
+ },
1553
+ {
1554
+ selector: 'node[status="failed"]',
1555
+ style: { 'background-color': '#ef4444', 'border-color': '#f85149', shape: 'diamond' },
1556
+ },
1557
+ {
1558
+ selector: 'node[status="running"]',
1559
+ style: { 'background-color': '#3b82f6', 'border-color': '#79b8ff' },
1560
+ },
1561
+ {
1562
+ selector: 'node[status="hung"]',
1563
+ style: { 'background-color': '#f0883e', 'border-color': '#f5a623' },
1564
+ },
1565
+ {
1566
+ selector: 'node[status="timeout"]',
1567
+ style: { 'background-color': '#f0883e', 'border-color': '#f5a623' },
1227
1568
  },
1228
- { selector: 'node[status="completed"]', style: { 'background-color': '#10b981', 'border-color': '#2ea043' } },
1229
- { selector: 'node[status="failed"]', style: { 'background-color': '#ef4444', 'border-color': '#f85149', 'shape': 'diamond' } },
1230
- { selector: 'node[status="running"]', style: { 'background-color': '#3b82f6', 'border-color': '#79b8ff' } },
1231
- { selector: 'node[status="hung"]', style: { 'background-color': '#f0883e', 'border-color': '#f5a623' } },
1232
- { selector: 'node[status="timeout"]', style: { 'background-color': '#f0883e', 'border-color': '#f5a623' } },
1233
1569
  // Shape by type
1234
- { selector: 'node[nodeType="agent"]', style: { 'shape': 'ellipse', 'width': 50, 'height': 50 } },
1235
- { selector: 'node[nodeType="tool"]', style: { 'shape': 'round-rectangle', 'width': 50, 'height': 35 } },
1236
- { selector: 'node[nodeType="subagent"]', style: { 'shape': 'ellipse', 'width': 38, 'height': 38 } },
1237
- { selector: 'node[nodeType="wait"]', style: { 'shape': 'round-rectangle', 'width': 40, 'height': 30 } },
1238
- { selector: 'node[nodeType="decision"]', style: { 'shape': 'diamond', 'width': 45, 'height': 45 } },
1239
- { selector: 'node[nodeType="custom"]', style: { 'shape': 'diamond', 'width': 40, 'height': 40 } },
1570
+ { selector: 'node[nodeType="agent"]', style: { shape: 'ellipse', width: 50, height: 50 } },
1571
+ {
1572
+ selector: 'node[nodeType="tool"]',
1573
+ style: { shape: 'round-rectangle', width: 50, height: 35 },
1574
+ },
1575
+ {
1576
+ selector: 'node[nodeType="subagent"]',
1577
+ style: { shape: 'ellipse', width: 38, height: 38 },
1578
+ },
1579
+ {
1580
+ selector: 'node[nodeType="wait"]',
1581
+ style: { shape: 'round-rectangle', width: 40, height: 30 },
1582
+ },
1583
+ {
1584
+ selector: 'node[nodeType="decision"]',
1585
+ style: { shape: 'diamond', width: 45, height: 45 },
1586
+ },
1587
+ { selector: 'node[nodeType="custom"]', style: { shape: 'diamond', width: 40, height: 40 } },
1240
1588
  // Selected node — gold border
1241
- { selector: ':selected', style: { 'border-width': 4, 'border-color': '#f59e0b', 'overlay-opacity': 0.08 } },
1589
+ {
1590
+ selector: ':selected',
1591
+ style: { 'border-width': 4, 'border-color': '#f59e0b', 'overlay-opacity': 0.08 },
1592
+ },
1242
1593
  // Edges
1243
1594
  {
1244
1595
  selector: 'edge',
1245
1596
  style: {
1246
- 'width': 2,
1597
+ width: 2,
1247
1598
  'line-color': '#6b7280',
1248
1599
  'target-arrow-color': '#6b7280',
1249
1600
  'target-arrow-shape': 'triangle',
1250
1601
  'curve-style': 'bezier',
1251
- 'arrow-scale': 0.8
1252
- }
1602
+ 'arrow-scale': 0.8,
1603
+ },
1253
1604
  },
1254
1605
  // Dashed edges for specific types
1255
1606
  {
@@ -1257,27 +1608,32 @@ class AgentFlowDashboard {
1257
1608
  style: {
1258
1609
  'line-style': 'dashed',
1259
1610
  'line-color': '#f0883e',
1260
- 'target-arrow-color': '#f0883e'
1261
- }
1262
- }
1611
+ 'target-arrow-color': '#f0883e',
1612
+ },
1613
+ },
1263
1614
  ],
1264
- layout: { name: 'breadthfirst', directed: true, padding: 40, spacingFactor: 1.4, animate: true, animationDuration: 300 },
1615
+ layout: {
1616
+ name: 'breadthfirst',
1617
+ directed: true,
1618
+ padding: 40,
1619
+ spacingFactor: 1.4,
1620
+ animate: true,
1621
+ animationDuration: 300,
1622
+ },
1265
1623
  minZoom: 0.2,
1266
1624
  maxZoom: 4,
1267
- wheelSensitivity: 0.3
1625
+ wheelSensitivity: 0.3,
1268
1626
  });
1269
1627
 
1270
- var self = this;
1271
-
1272
1628
  // Node tap -> detail panel
1273
- this.cy.on('tap', 'node', function(e) {
1629
+ this.cy.on('tap', 'node', (e) => {
1274
1630
  var data = e.target.data();
1275
- self.showNodeDetail(data.fullData);
1631
+ this.showNodeDetail(data.fullData);
1276
1632
  });
1277
1633
 
1278
1634
  // Background tap -> close panel
1279
- this.cy.on('tap', function(e) {
1280
- if (e.target === self.cy) {
1635
+ this.cy.on('tap', (e) => {
1636
+ if (e.target === this.cy) {
1281
1637
  document.getElementById('nodeDetailPanel').classList.remove('active');
1282
1638
  }
1283
1639
  });
@@ -1285,14 +1641,16 @@ class AgentFlowDashboard {
1285
1641
 
1286
1642
  runCytoscapeLayout() {
1287
1643
  if (!this.cy) return;
1288
- this.cy.layout({
1289
- name: 'breadthfirst',
1290
- directed: true,
1291
- padding: 40,
1292
- spacingFactor: 1.4,
1293
- animate: true,
1294
- animationDuration: 400
1295
- }).run();
1644
+ this.cy
1645
+ .layout({
1646
+ name: 'breadthfirst',
1647
+ directed: true,
1648
+ padding: 40,
1649
+ spacingFactor: 1.4,
1650
+ animate: true,
1651
+ animationDuration: 400,
1652
+ })
1653
+ .run();
1296
1654
  }
1297
1655
 
1298
1656
  showNodeDetail(node) {
@@ -1307,16 +1665,25 @@ class AgentFlowDashboard {
1307
1665
  var html = '';
1308
1666
  html += this.detailRow('ID', node.id);
1309
1667
  html += this.detailRow('Type', node.type);
1310
- html += '<div class="detail-row"><span class="detail-label">Status</span><span class="detail-value status-' + escapeHtml(node.status || '') + '">' + escapeHtml(node.status || 'unknown') + '</span></div>';
1668
+ html +=
1669
+ '<div class="detail-row"><span class="detail-label">Status</span><span class="detail-value status-' +
1670
+ escapeHtml(node.status || '') +
1671
+ '">' +
1672
+ escapeHtml(node.status || 'unknown') +
1673
+ '</span></div>';
1311
1674
  html += this.detailRow('Duration', duration);
1312
1675
  if (node.startTime) html += this.detailRow('Start', new Date(node.startTime).toLocaleString());
1313
1676
  if (node.endTime) html += this.detailRow('End', new Date(node.endTime).toLocaleString());
1314
1677
  if (node.parentId) html += this.detailRow('Parent', node.parentId);
1315
- if (node.children && node.children.length) html += this.detailRow('Children', node.children.length);
1678
+ if (node.children?.length) html += this.detailRow('Children', node.children.length);
1316
1679
 
1317
1680
  if (node.metadata && Object.keys(node.metadata).length > 0) {
1318
- html += '<div style="margin-top:0.5rem;font-size:0.7rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:0.3px;">Metadata</div>';
1319
- html += '<div class="detail-metadata">' + escapeHtml(JSON.stringify(node.metadata, null, 2)) + '</div>';
1681
+ html +=
1682
+ '<div style="margin-top:0.5rem;font-size:0.7rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:0.3px;">Metadata</div>';
1683
+ html +=
1684
+ '<div class="detail-metadata">' +
1685
+ escapeHtml(JSON.stringify(node.metadata, null, 2)) +
1686
+ '</div>';
1320
1687
  }
1321
1688
 
1322
1689
  body.innerHTML = html;
@@ -1325,15 +1692,23 @@ class AgentFlowDashboard {
1325
1692
 
1326
1693
  detailRow(label, value) {
1327
1694
  if (value === undefined || value === null || value === '') return '';
1328
- return '<div class="detail-row"><span class="detail-label">' + escapeHtml(label) + '</span><span class="detail-value">' + escapeHtml(String(value)) + '</span></div>';
1695
+ return (
1696
+ '<div class="detail-row"><span class="detail-label">' +
1697
+ escapeHtml(label) +
1698
+ '</span><span class="detail-value">' +
1699
+ escapeHtml(String(value)) +
1700
+ '</span></div>'
1701
+ );
1329
1702
  }
1330
1703
 
1331
1704
  exportGraphPNG() {
1332
1705
  if (!this.cy) return;
1333
1706
  var png = this.cy.png({ bg: '#0d1117', full: true, maxWidth: 4000, maxHeight: 4000 });
1334
1707
  var link = document.createElement('a');
1335
- var traceName = this.selectedTrace ? this.selectedTrace.filename.replace(/\.json$/, '') : 'graph';
1336
- link.download = 'agentflow-' + traceName + '.png';
1708
+ var traceName = this.selectedTrace
1709
+ ? this.selectedTrace.filename.replace(/\.json$/, '')
1710
+ : 'graph';
1711
+ link.download = `agentflow-${traceName}.png`;
1337
1712
  link.href = png;
1338
1713
  link.click();
1339
1714
  }
@@ -1343,12 +1718,13 @@ class AgentFlowDashboard {
1343
1718
  // ---------------------------------------------------------------------------
1344
1719
  renderHeatmap() {
1345
1720
  var container = document.getElementById('heatmapContent');
1346
- var trace = this.selectedTraceData || this.selectedTrace;
1721
+ var _trace = this.selectedTraceData || this.selectedTrace;
1347
1722
 
1348
1723
  // Build heatmap from recent traces (not just selected trace)
1349
1724
  var tracesToUse = this.traces.slice(0, 100);
1350
1725
  if (tracesToUse.length === 0) {
1351
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">No traces available for heatmap.</div></div>';
1726
+ container.innerHTML =
1727
+ '<div class="empty-state"><div class="empty-state-text">No traces available for heatmap.</div></div>';
1352
1728
  return;
1353
1729
  }
1354
1730
 
@@ -1373,22 +1749,35 @@ class AgentFlowDashboard {
1373
1749
 
1374
1750
  var cellLabel = failCount > 0 ? failCount : '';
1375
1751
  var agentName = escapeHtml(tr.agentId || tr.name || 'unknown');
1376
- var tooltipText = escapeHtml((tr.name || tr.filename || '').substring(0, 30)) + ' | ' + agentName + ' | ' + failCount + ' errors, ' + warnCount + ' warnings';
1377
-
1378
- html += '<div class="heatmap-cell" style="background:' + color + ';" title="' + tooltipText + '">';
1752
+ var tooltipText =
1753
+ escapeHtml((tr.name || tr.filename || '').substring(0, 30)) +
1754
+ ' | ' +
1755
+ agentName +
1756
+ ' | ' +
1757
+ failCount +
1758
+ ' errors, ' +
1759
+ warnCount +
1760
+ ' warnings';
1761
+
1762
+ html += `<div class="heatmap-cell" style="background:${color};" title="${tooltipText}">`;
1379
1763
  html += cellLabel;
1380
- html += '<div class="heatmap-tooltip">' + tooltipText + '</div>';
1764
+ html += `<div class="heatmap-tooltip">${tooltipText}</div>`;
1381
1765
  html += '</div>';
1382
1766
  }
1383
1767
 
1384
1768
  html += '</div>';
1385
1769
 
1386
1770
  // Legend
1387
- html += '<div style="display:flex;gap:1.5rem;font-size:0.75rem;color:var(--text-secondary);margin-top:0.5rem;">';
1388
- html += '<span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:rgba(35,134,54,0.3);vertical-align:middle;margin-right:4px;"></span>No errors</span>';
1389
- html += '<span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:rgba(240,136,62,0.5);vertical-align:middle;margin-right:4px;"></span>Warnings</span>';
1390
- html += '<span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:rgba(218,54,51,0.5);vertical-align:middle;margin-right:4px;"></span>1-2 failures</span>';
1391
- html += '<span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:rgba(218,54,51,0.9);vertical-align:middle;margin-right:4px;"></span>3+ failures</span>';
1771
+ html +=
1772
+ '<div style="display:flex;gap:1.5rem;font-size:0.75rem;color:var(--text-secondary);margin-top:0.5rem;">';
1773
+ html +=
1774
+ '<span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:rgba(35,134,54,0.3);vertical-align:middle;margin-right:4px;"></span>No errors</span>';
1775
+ html +=
1776
+ '<span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:rgba(240,136,62,0.5);vertical-align:middle;margin-right:4px;"></span>Warnings</span>';
1777
+ html +=
1778
+ '<span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:rgba(218,54,51,0.5);vertical-align:middle;margin-right:4px;"></span>1-2 failures</span>';
1779
+ html +=
1780
+ '<span><span style="display:inline-block;width:12px;height:12px;border-radius:2px;background:rgba(218,54,51,0.9);vertical-align:middle;margin-right:4px;"></span>3+ failures</span>';
1392
1781
  html += '</div>';
1393
1782
 
1394
1783
  container.innerHTML = html;
@@ -1401,12 +1790,16 @@ class AgentFlowDashboard {
1401
1790
  var container = document.getElementById('stateContent');
1402
1791
  var trace = this.selectedTraceData || this.selectedTrace;
1403
1792
  if (!trace || !trace.nodes) {
1404
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">Select a trace to view state machine.</div></div>';
1793
+ container.innerHTML =
1794
+ '<div class="empty-state"><div class="empty-state-text">Select a trace to view state machine.</div></div>';
1405
1795
  return;
1406
1796
  }
1407
1797
 
1408
1798
  var nodes = this.getNodesArray(trace);
1409
- var pendingCount = 0, runningCount = 0, completedCount = 0, failedCount = 0;
1799
+ var pendingCount = 0,
1800
+ runningCount = 0,
1801
+ completedCount = 0,
1802
+ failedCount = 0;
1410
1803
 
1411
1804
  for (var i = 0; i < nodes.length; i++) {
1412
1805
  var s = nodes[i].status;
@@ -1425,28 +1818,48 @@ class AgentFlowDashboard {
1425
1818
  var html = '<div class="state-machine">';
1426
1819
 
1427
1820
  html += '<div class="state">';
1428
- html += '<div class="state-circle' + pendingActive + '"><span class="state-count">' + pendingCount + '</span>PENDING</div>';
1821
+ html +=
1822
+ '<div class="state-circle' +
1823
+ pendingActive +
1824
+ '"><span class="state-count">' +
1825
+ pendingCount +
1826
+ '</span>PENDING</div>';
1429
1827
  html += '<span class="state-label">Queued</span>';
1430
1828
  html += '</div>';
1431
1829
 
1432
1830
  html += '<div class="state-arrow">&rarr;</div>';
1433
1831
 
1434
1832
  html += '<div class="state">';
1435
- html += '<div class="state-circle' + runningActive + '"><span class="state-count">' + runningCount + '</span>RUNNING</div>';
1833
+ html +=
1834
+ '<div class="state-circle' +
1835
+ runningActive +
1836
+ '"><span class="state-count">' +
1837
+ runningCount +
1838
+ '</span>RUNNING</div>';
1436
1839
  html += '<span class="state-label">Active</span>';
1437
1840
  html += '</div>';
1438
1841
 
1439
1842
  html += '<div class="state-arrow">&rarr;</div>';
1440
1843
 
1441
1844
  html += '<div class="state">';
1442
- html += '<div class="state-circle' + completedActive + '"><span class="state-count">' + completedCount + '</span>COMPLETED</div>';
1845
+ html +=
1846
+ '<div class="state-circle' +
1847
+ completedActive +
1848
+ '"><span class="state-count">' +
1849
+ completedCount +
1850
+ '</span>COMPLETED</div>';
1443
1851
  html += '<span class="state-label">Success</span>';
1444
1852
  html += '</div>';
1445
1853
 
1446
1854
  html += '<div class="state-arrow">&harr;</div>';
1447
1855
 
1448
1856
  html += '<div class="state">';
1449
- html += '<div class="state-circle' + failedActive + '"><span class="state-count">' + failedCount + '</span>FAILED</div>';
1857
+ html +=
1858
+ '<div class="state-circle' +
1859
+ failedActive +
1860
+ '"><span class="state-count">' +
1861
+ failedCount +
1862
+ '</span>FAILED</div>';
1450
1863
  html += '<span class="state-label">Error</span>';
1451
1864
  html += '</div>';
1452
1865
 
@@ -1471,24 +1884,30 @@ class AgentFlowDashboard {
1471
1884
  var container = document.getElementById('summaryContent');
1472
1885
  var trace = this.selectedTraceData || this.selectedTrace;
1473
1886
  if (!trace || !trace.nodes) {
1474
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">Select a trace to view summary.</div></div>';
1887
+ container.innerHTML =
1888
+ '<div class="empty-state"><div class="empty-state-text">Select a trace to view summary.</div></div>';
1475
1889
  return;
1476
1890
  }
1477
1891
 
1478
1892
  // Show spinner briefly then generate
1479
- container.innerHTML = '<div class="empty-state"><div class="spinner"></div><div class="empty-state-text">Generating summary...</div></div>';
1893
+ container.innerHTML =
1894
+ '<div class="empty-state"><div class="spinner"></div><div class="empty-state-text">Generating summary...</div></div>';
1480
1895
 
1481
- var self = this;
1482
1896
  // Use setTimeout to avoid blocking render
1483
- setTimeout(function() { self.generateSummary(trace, container); }, 50);
1897
+ setTimeout(() => {
1898
+ this.generateSummary(trace, container);
1899
+ }, 50);
1484
1900
  }
1485
1901
 
1486
1902
  generateSummary(trace, container) {
1487
1903
  var nodes = this.getNodesArray(trace);
1488
1904
  var totalNodes = nodes.length;
1489
- var completedCount = 0, failedCount = 0, runningCount = 0;
1905
+ var completedCount = 0,
1906
+ failedCount = 0,
1907
+ runningCount = 0;
1490
1908
  var agentNames = new Set();
1491
- var totalDur = 0, durCount = 0;
1909
+ var totalDur = 0,
1910
+ durCount = 0;
1492
1911
 
1493
1912
  for (var i = 0; i < nodes.length; i++) {
1494
1913
  var n = nodes[i];
@@ -1502,64 +1921,79 @@ class AgentFlowDashboard {
1502
1921
 
1503
1922
  if (n.startTime && n.endTime) {
1504
1923
  var ms = new Date(n.endTime).getTime() - new Date(n.startTime).getTime();
1505
- if (!isNaN(ms) && ms >= 0) { totalDur += ms; durCount++; }
1924
+ if (!Number.isNaN(ms) && ms >= 0) {
1925
+ totalDur += ms;
1926
+ durCount++;
1927
+ }
1506
1928
  }
1507
1929
  }
1508
1930
 
1509
- var successRate = totalNodes > 0 ? Math.round(completedCount / totalNodes * 100) : 0;
1931
+ var successRate = totalNodes > 0 ? Math.round((completedCount / totalNodes) * 100) : 0;
1510
1932
  var agentList = Array.from(agentNames);
1511
1933
 
1512
1934
  // Build summary title
1513
- var titleText = 'Trace: ' + escapeHtml(trace.name || trace.agentId || trace.filename || 'Unknown');
1935
+ var titleText = `Trace: ${escapeHtml(trace.name || trace.agentId || trace.filename || 'Unknown')}`;
1514
1936
 
1515
1937
  // Build summary text
1516
- var summaryText = 'This trace contains ' + totalNodes + ' node' + (totalNodes !== 1 ? 's' : '') + '. ';
1517
- summaryText += completedCount + ' completed successfully, ' + failedCount + ' failed';
1518
- if (runningCount > 0) summaryText += ', and ' + runningCount + ' are still running';
1938
+ var summaryText = `This trace contains ${totalNodes} node${totalNodes !== 1 ? 's' : ''}. `;
1939
+ summaryText += `${completedCount} completed successfully, ${failedCount} failed`;
1940
+ if (runningCount > 0) summaryText += `, and ${runningCount} are still running`;
1519
1941
  summaryText += '. ';
1520
1942
  if (durCount > 0) {
1521
- summaryText += 'Average node duration was ' + this.formatDuration(totalDur / durCount) + '. ';
1522
- summaryText += 'Total execution time: ' + this.formatDuration(totalDur) + '.';
1943
+ summaryText += `Average node duration was ${this.formatDuration(totalDur / durCount)}. `;
1944
+ summaryText += `Total execution time: ${this.formatDuration(totalDur)}.`;
1523
1945
  }
1524
1946
 
1525
1947
  // Build details list
1526
1948
  var details = [];
1527
- details.push('Total nodes: ' + totalNodes);
1528
- details.push('Completed: ' + completedCount);
1529
- details.push('Failed: ' + failedCount);
1530
- if (runningCount > 0) details.push('Running: ' + runningCount);
1531
- if (agentList.length > 0) details.push('Agents involved: ' + agentList.join(', '));
1532
- if (trace.trigger) details.push('Trigger: ' + trace.trigger);
1949
+ details.push(`Total nodes: ${totalNodes}`);
1950
+ details.push(`Completed: ${completedCount}`);
1951
+ details.push(`Failed: ${failedCount}`);
1952
+ if (runningCount > 0) details.push(`Running: ${runningCount}`);
1953
+ if (agentList.length > 0) details.push(`Agents involved: ${agentList.join(', ')}`);
1954
+ if (trace.trigger) details.push(`Trigger: ${trace.trigger}`);
1533
1955
 
1534
1956
  // Recommendations
1535
1957
  var recommendations = '';
1536
1958
  if (failedCount === 0 && runningCount === 0) {
1537
- recommendations = '<strong>Status:</strong> All tasks completed successfully. No issues detected.';
1959
+ recommendations =
1960
+ '<strong>Status:</strong> All tasks completed successfully. No issues detected.';
1538
1961
  } else if (failedCount > 0) {
1539
- recommendations = '<strong>Action needed:</strong> ' + failedCount + ' node' + (failedCount !== 1 ? 's' : '') + ' failed. Investigate the failed nodes in the Timeline or Dependency Graph tabs for error details.';
1962
+ recommendations =
1963
+ '<strong>Action needed:</strong> ' +
1964
+ failedCount +
1965
+ ' node' +
1966
+ (failedCount !== 1 ? 's' : '') +
1967
+ ' failed. Investigate the failed nodes in the Timeline or Dependency Graph tabs for error details.';
1540
1968
  }
1541
1969
  if (runningCount > 0) {
1542
- recommendations += (recommendations ? ' ' : '') + '<strong>Note:</strong> ' + runningCount + ' node' + (runningCount !== 1 ? 's are' : ' is') + ' still running. The trace may not be complete yet.';
1970
+ recommendations +=
1971
+ (recommendations ? ' ' : '') +
1972
+ '<strong>Note:</strong> ' +
1973
+ runningCount +
1974
+ ' node' +
1975
+ (runningCount !== 1 ? 's are' : ' is') +
1976
+ ' still running. The trace may not be complete yet.';
1543
1977
  }
1544
1978
 
1545
1979
  var html = '<div class="summary-card">';
1546
- html += '<h3 class="summary-title">' + titleText + '</h3>';
1547
- html += '<p class="summary-text">' + escapeHtml(summaryText) + '</p>';
1980
+ html += `<h3 class="summary-title">${titleText}</h3>`;
1981
+ html += `<p class="summary-text">${escapeHtml(summaryText)}</p>`;
1548
1982
  html += '<ul class="summary-details">';
1549
1983
  for (var j = 0; j < details.length; j++) {
1550
- html += '<li>' + escapeHtml(details[j]) + '</li>';
1984
+ html += `<li>${escapeHtml(details[j])}</li>`;
1551
1985
  }
1552
1986
  html += '</ul>';
1553
1987
 
1554
1988
  if (recommendations) {
1555
- html += '<div class="summary-recommendations">' + recommendations + '</div>';
1989
+ html += `<div class="summary-recommendations">${recommendations}</div>`;
1556
1990
  }
1557
1991
 
1558
1992
  // Confidence bar based on success rate
1559
1993
  html += '<div class="confidence-bar">';
1560
1994
  html += '<span>Confidence:</span>';
1561
- html += '<div class="bar"><div class="bar-fill" style="width:' + successRate + '%;"></div></div>';
1562
- html += '<span>' + successRate + '%</span>';
1995
+ html += `<div class="bar"><div class="bar-fill" style="width:${successRate}%;"></div></div>`;
1996
+ html += `<span>${successRate}%</span>`;
1563
1997
  html += '</div>';
1564
1998
 
1565
1999
  html += '</div>';
@@ -1580,51 +2014,105 @@ class AgentFlowDashboard {
1580
2014
 
1581
2015
  if (events.length === 0 && filename) {
1582
2016
  try {
1583
- var res = await fetch('/api/traces/' + encodeURIComponent(filename) + '/events');
2017
+ var res = await fetch(`/api/traces/${encodeURIComponent(filename)}/events`);
1584
2018
  if (res.ok) {
1585
2019
  var data = await res.json();
1586
2020
  events = data.events || [];
1587
2021
  tokenUsage = data.tokenUsage || null;
1588
2022
  }
1589
- } catch (e) {
2023
+ } catch (_e) {
1590
2024
  // fall through to node-based rendering
1591
2025
  }
1592
2026
  }
1593
2027
 
1594
2028
  if (events.length === 0) {
1595
2029
  // Fallback: render nodes like a normal trace
1596
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">No session events found. Try the node-based timeline.</div></div>';
2030
+ container.innerHTML =
2031
+ '<div class="empty-state"><div class="empty-state-text">No session events found. Try the node-based timeline.</div></div>';
1597
2032
  return;
1598
2033
  }
1599
2034
 
1600
2035
  // Token usage summary at top
1601
2036
  if (tokenUsage && tokenUsage.total > 0) {
1602
- html += '<div style="display:flex;gap:16px;margin-bottom:12px;flex-wrap:wrap;padding:8px 12px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;">';
1603
- html += '<span style="font-size:0.8rem;color:#bc8cff;">Tokens: ' + (tokenUsage.total > 1000 ? Math.round(tokenUsage.total / 1000) + 'k' : tokenUsage.total) + '</span>';
1604
- html += '<span style="font-size:0.8rem;color:var(--text-secondary);">In: ' + (tokenUsage.input > 1000 ? Math.round(tokenUsage.input / 1000) + 'k' : tokenUsage.input) + '</span>';
1605
- html += '<span style="font-size:0.8rem;color:var(--text-secondary);">Out: ' + (tokenUsage.output > 1000 ? Math.round(tokenUsage.output / 1000) + 'k' : tokenUsage.output) + '</span>';
1606
- if (tokenUsage.cost > 0) html += '<span style="font-size:0.8rem;color:#f0883e;">Cost: $' + tokenUsage.cost.toFixed(4) + '</span>';
2037
+ html +=
2038
+ '<div style="display:flex;gap:16px;margin-bottom:12px;flex-wrap:wrap;padding:8px 12px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;">';
2039
+ html +=
2040
+ '<span style="font-size:0.8rem;color:#bc8cff;">Tokens: ' +
2041
+ (tokenUsage.total > 1000 ? `${Math.round(tokenUsage.total / 1000)}k` : tokenUsage.total) +
2042
+ '</span>';
2043
+ html +=
2044
+ '<span style="font-size:0.8rem;color:var(--text-secondary);">In: ' +
2045
+ (tokenUsage.input > 1000 ? `${Math.round(tokenUsage.input / 1000)}k` : tokenUsage.input) +
2046
+ '</span>';
2047
+ html +=
2048
+ '<span style="font-size:0.8rem;color:var(--text-secondary);">Out: ' +
2049
+ (tokenUsage.output > 1000
2050
+ ? `${Math.round(tokenUsage.output / 1000)}k`
2051
+ : tokenUsage.output) +
2052
+ '</span>';
2053
+ if (tokenUsage.cost > 0)
2054
+ html +=
2055
+ '<span style="font-size:0.8rem;color:#f0883e;">Cost: $' +
2056
+ tokenUsage.cost.toFixed(4) +
2057
+ '</span>';
1607
2058
  html += '</div>';
1608
2059
  }
1609
2060
 
1610
2061
  // Summary badges
1611
- var userCount = 0, assistantCount = 0, toolCount = 0, thinkCount = 0, spawnCount = 0;
2062
+ var userCount = 0,
2063
+ assistantCount = 0,
2064
+ toolCount = 0,
2065
+ thinkCount = 0,
2066
+ spawnCount = 0;
1612
2067
  for (var i = 0; i < events.length; i++) {
1613
2068
  switch (events[i].type) {
1614
- case 'user': userCount++; break;
1615
- case 'assistant': assistantCount++; break;
1616
- case 'tool_call': toolCount++; break;
1617
- case 'thinking': thinkCount++; break;
1618
- case 'spawn': spawnCount++; break;
2069
+ case 'user':
2070
+ userCount++;
2071
+ break;
2072
+ case 'assistant':
2073
+ assistantCount++;
2074
+ break;
2075
+ case 'tool_call':
2076
+ toolCount++;
2077
+ break;
2078
+ case 'thinking':
2079
+ thinkCount++;
2080
+ break;
2081
+ case 'spawn':
2082
+ spawnCount++;
2083
+ break;
1619
2084
  }
1620
2085
  }
1621
2086
  html += '<div style="display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap;">';
1622
- html += '<span style="font-size:0.8rem;color:var(--text-secondary);">' + events.length + ' events</span>';
1623
- if (userCount) html += '<span class="badge" style="background:rgba(88,166,255,0.15);color:#58a6ff;">' + userCount + ' user</span>';
1624
- if (assistantCount) html += '<span class="badge" style="background:rgba(35,134,54,0.15);color:#3fb950;">' + assistantCount + ' assistant</span>';
1625
- if (toolCount) html += '<span class="badge" style="background:rgba(240,136,62,0.15);color:#f0883e;">' + toolCount + ' tools</span>';
1626
- if (thinkCount) html += '<span class="badge" style="background:rgba(188,140,255,0.15);color:#bc8cff;">' + thinkCount + ' thinking</span>';
1627
- if (spawnCount) html += '<span class="badge" style="background:rgba(0,200,200,0.15);color:#00c8c8;">' + spawnCount + ' spawns</span>';
2087
+ html +=
2088
+ '<span style="font-size:0.8rem;color:var(--text-secondary);">' +
2089
+ events.length +
2090
+ ' events</span>';
2091
+ if (userCount)
2092
+ html +=
2093
+ '<span class="badge" style="background:rgba(88,166,255,0.15);color:#58a6ff;">' +
2094
+ userCount +
2095
+ ' user</span>';
2096
+ if (assistantCount)
2097
+ html +=
2098
+ '<span class="badge" style="background:rgba(35,134,54,0.15);color:#3fb950;">' +
2099
+ assistantCount +
2100
+ ' assistant</span>';
2101
+ if (toolCount)
2102
+ html +=
2103
+ '<span class="badge" style="background:rgba(240,136,62,0.15);color:#f0883e;">' +
2104
+ toolCount +
2105
+ ' tools</span>';
2106
+ if (thinkCount)
2107
+ html +=
2108
+ '<span class="badge" style="background:rgba(188,140,255,0.15);color:#bc8cff;">' +
2109
+ thinkCount +
2110
+ ' thinking</span>';
2111
+ if (spawnCount)
2112
+ html +=
2113
+ '<span class="badge" style="background:rgba(0,200,200,0.15);color:#00c8c8;">' +
2114
+ spawnCount +
2115
+ ' spawns</span>';
1628
2116
  html += '</div>';
1629
2117
 
1630
2118
  // Render events
@@ -1652,29 +2140,48 @@ class AgentFlowDashboard {
1652
2140
  }
1653
2141
 
1654
2142
  html += '<div class="timeline-item">';
1655
- html += '<div class="timeline-marker" style="background:' + marker.color + ';"></div>';
2143
+ html += `<div class="timeline-marker" style="background:${marker.color};"></div>`;
1656
2144
  html += '<div class="timeline-content">';
1657
2145
  html += '<div class="timeline-header">';
1658
- html += '<span class="event-type">' + marker.icon + ' <strong>' + escapeHtml(evt.name || marker.label) + '</strong>';
1659
- if (evt.type === 'tool_call' && evt.toolName) html += ' <code style="font-size:0.75rem;color:#f0883e;">' + escapeHtml(evt.toolName) + '</code>';
2146
+ html +=
2147
+ '<span class="event-type">' +
2148
+ marker.icon +
2149
+ ' <strong>' +
2150
+ escapeHtml(evt.name || marker.label) +
2151
+ '</strong>';
2152
+ if (evt.type === 'tool_call' && evt.toolName)
2153
+ html += ` <code style="font-size:0.75rem;color:#f0883e;">${escapeHtml(evt.toolName)}</code>`;
1660
2154
  html += '</span>';
1661
- html += '<span class="event-time">' + evtTime;
1662
- if (evt.duration) html += ' &middot; ' + this.formatDuration(evt.duration);
1663
- if (evt.tokens && evt.tokens.total) html += ' &middot; <span style="color:#bc8cff;">' + (evt.tokens.total > 1000 ? Math.round(evt.tokens.total / 1000) + 'k' : evt.tokens.total) + ' tok</span>';
2155
+ html += `<span class="event-time">${evtTime}`;
2156
+ if (evt.duration) html += ` &middot; ${this.formatDuration(evt.duration)}`;
2157
+ if (evt.tokens?.total)
2158
+ html +=
2159
+ ' &middot; <span style="color:#bc8cff;">' +
2160
+ (evt.tokens.total > 1000 ? `${Math.round(evt.tokens.total / 1000)}k` : evt.tokens.total) +
2161
+ ' tok</span>';
1664
2162
  html += '</span></div>';
1665
2163
 
1666
2164
  if (contentPreview) {
1667
- html += '<div class="event-details" style="margin-top:4px;">' + contentPreview + '</div>';
2165
+ html += `<div class="event-details" style="margin-top:4px;">${contentPreview}</div>`;
1668
2166
  }
1669
2167
 
1670
2168
  if (evt.type === 'tool_call' && evt.toolArgs) {
1671
- var argsStr = typeof evt.toolArgs === 'string' ? evt.toolArgs : JSON.stringify(evt.toolArgs);
1672
- html += '<div class="event-details" style="margin-top:2px;font-family:monospace;font-size:0.7rem;color:var(--text-secondary);max-height:60px;overflow:hidden;">' + escapeHtml(argsStr.substring(0, 200)) + '</div>';
2169
+ var argsStr =
2170
+ typeof evt.toolArgs === 'string' ? evt.toolArgs : JSON.stringify(evt.toolArgs);
2171
+ html +=
2172
+ '<div class="event-details" style="margin-top:2px;font-family:monospace;font-size:0.7rem;color:var(--text-secondary);max-height:60px;overflow:hidden;">' +
2173
+ escapeHtml(argsStr.substring(0, 200)) +
2174
+ '</div>';
1673
2175
  }
1674
2176
 
1675
2177
  if (evt.type === 'tool_result' && evt.toolResult) {
1676
2178
  var resultColor = evt.toolError ? 'var(--accent-error)' : 'var(--text-secondary)';
1677
- html += '<div class="event-details" style="margin-top:2px;font-family:monospace;font-size:0.7rem;color:' + resultColor + ';max-height:80px;overflow:hidden;">' + escapeHtml(evt.toolResult.substring(0, 300)) + '</div>';
2179
+ html +=
2180
+ '<div class="event-details" style="margin-top:2px;font-family:monospace;font-size:0.7rem;color:' +
2181
+ resultColor +
2182
+ ';max-height:80px;overflow:hidden;">' +
2183
+ escapeHtml(evt.toolResult.substring(0, 300)) +
2184
+ '</div>';
1678
2185
  }
1679
2186
 
1680
2187
  html += '</div></div>';
@@ -1691,28 +2198,33 @@ class AgentFlowDashboard {
1691
2198
  var trace = this.selectedTraceData || this.selectedTrace;
1692
2199
 
1693
2200
  if (!trace) {
1694
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">Select a trace to view transcript.</div></div>';
2201
+ container.innerHTML =
2202
+ '<div class="empty-state"><div class="empty-state-text">Select a trace to view transcript.</div></div>';
1695
2203
  return;
1696
2204
  }
1697
2205
 
1698
2206
  if (trace.sourceType !== 'session') {
1699
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">Transcript view is only available for session traces (JSONL files).</div></div>';
2207
+ container.innerHTML =
2208
+ '<div class="empty-state"><div class="empty-state-text">Transcript view is only available for session traces (JSONL files).</div></div>';
1700
2209
  return;
1701
2210
  }
1702
2211
 
1703
2212
  var events = trace.sessionEvents || [];
1704
2213
  if (events.length === 0 && trace.filename) {
1705
2214
  try {
1706
- var res = await fetch('/api/traces/' + encodeURIComponent(trace.filename) + '/events');
2215
+ var res = await fetch(`/api/traces/${encodeURIComponent(trace.filename)}/events`);
1707
2216
  if (res.ok) {
1708
2217
  var data = await res.json();
1709
2218
  events = data.events || [];
1710
2219
  }
1711
- } catch (e) { /* ignore */ }
2220
+ } catch (_e) {
2221
+ /* ignore */
2222
+ }
1712
2223
  }
1713
2224
 
1714
2225
  if (events.length === 0) {
1715
- container.innerHTML = '<div class="empty-state"><div class="empty-state-text">No session events found.</div></div>';
2226
+ container.innerHTML =
2227
+ '<div class="empty-state"><div class="empty-state-text">No session events found.</div></div>';
1716
2228
  return;
1717
2229
  }
1718
2230
 
@@ -1726,60 +2238,89 @@ class AgentFlowDashboard {
1726
2238
  if (evt.type === 'user') {
1727
2239
  html += '<div class="chat-bubble chat-user">';
1728
2240
  html += escapeHtml(evt.content || '');
1729
- html += '<div class="chat-meta">' + evtTime + '</div>';
2241
+ html += `<div class="chat-meta">${evtTime}</div>`;
1730
2242
  html += '</div>';
1731
2243
  }
1732
2244
 
1733
2245
  if (evt.type === 'assistant') {
1734
2246
  html += '<div class="chat-bubble chat-assistant">';
1735
2247
  html += escapeHtml(evt.content || '');
1736
- html += '<div class="chat-meta">' + evtTime;
1737
- if (evt.tokens && evt.tokens.total) {
1738
- html += ' &middot; <span class="chat-tokens">' + (evt.tokens.total > 1000 ? Math.round(evt.tokens.total / 1000) + 'k' : evt.tokens.total) + ' tokens';
1739
- if (evt.tokens.cost) html += ' ($' + evt.tokens.cost.toFixed(4) + ')';
2248
+ html += `<div class="chat-meta">${evtTime}`;
2249
+ if (evt.tokens?.total) {
2250
+ html +=
2251
+ ' &middot; <span class="chat-tokens">' +
2252
+ (evt.tokens.total > 1000
2253
+ ? `${Math.round(evt.tokens.total / 1000)}k`
2254
+ : evt.tokens.total) +
2255
+ ' tokens';
2256
+ if (evt.tokens.cost) html += ` ($${evt.tokens.cost.toFixed(4)})`;
1740
2257
  html += '</span>';
1741
2258
  }
1742
- if (evt.model) html += ' &middot; ' + escapeHtml(evt.model);
2259
+ if (evt.model) html += ` &middot; ${escapeHtml(evt.model)}`;
1743
2260
  html += '</div></div>';
1744
2261
  }
1745
2262
 
1746
2263
  if (evt.type === 'thinking') {
1747
2264
  thinkingIdx++;
1748
- var tId = 'thinking-toggle-' + thinkingIdx;
2265
+ var tId = `thinking-toggle-${thinkingIdx}`;
1749
2266
  html += '<div class="chat-bubble chat-thinking">';
1750
- html += '<span class="chat-thinking-toggle" onclick="var b=document.getElementById(\'' + tId + '\');b.classList.toggle(\'open\');">\ud83d\udcad Thinking (click to expand)</span>';
1751
- html += '<div class="chat-thinking-body" id="' + tId + '">' + escapeHtml(evt.content || '') + '</div>';
1752
- html += '<div class="chat-meta">' + evtTime + '</div>';
2267
+ html +=
2268
+ '<span class="chat-thinking-toggle" onclick="var b=document.getElementById(\'' +
2269
+ tId +
2270
+ "');b.classList.toggle('open');\">\ud83d\udcad Thinking (click to expand)</span>";
2271
+ html +=
2272
+ '<div class="chat-thinking-body" id="' +
2273
+ tId +
2274
+ '">' +
2275
+ escapeHtml(evt.content || '') +
2276
+ '</div>';
2277
+ html += `<div class="chat-meta">${evtTime}</div>`;
1753
2278
  html += '</div>';
1754
2279
  }
1755
2280
 
1756
2281
  if (evt.type === 'tool_call') {
1757
2282
  html += '<div class="chat-bubble chat-tool">';
1758
- html += '<strong>\ud83d\udee0\ufe0f ' + escapeHtml(evt.toolName || evt.name || 'Tool') + '</strong>';
2283
+ html +=
2284
+ '<strong>\ud83d\udee0\ufe0f ' +
2285
+ escapeHtml(evt.toolName || evt.name || 'Tool') +
2286
+ '</strong>';
1759
2287
  if (evt.toolArgs) {
1760
- var argsStr = typeof evt.toolArgs === 'string' ? evt.toolArgs : JSON.stringify(evt.toolArgs, null, 2);
1761
- html += '<div style="margin-top:4px;max-height:100px;overflow:hidden;font-size:0.75rem;color:var(--text-secondary);">' + escapeHtml(argsStr.substring(0, 300)) + '</div>';
2288
+ var argsStr =
2289
+ typeof evt.toolArgs === 'string' ? evt.toolArgs : JSON.stringify(evt.toolArgs, null, 2);
2290
+ html +=
2291
+ '<div style="margin-top:4px;max-height:100px;overflow:hidden;font-size:0.75rem;color:var(--text-secondary);">' +
2292
+ escapeHtml(argsStr.substring(0, 300)) +
2293
+ '</div>';
1762
2294
  }
1763
- html += '<div class="chat-meta">' + evtTime;
1764
- if (evt.duration) html += ' &middot; ' + this.formatDuration(evt.duration);
2295
+ html += `<div class="chat-meta">${evtTime}`;
2296
+ if (evt.duration) html += ` &middot; ${this.formatDuration(evt.duration)}`;
1765
2297
  html += '</div></div>';
1766
2298
  }
1767
2299
 
1768
2300
  if (evt.type === 'tool_result') {
1769
2301
  var isError = !!evt.toolError;
1770
- html += '<div class="chat-bubble chat-tool" style="' + (isError ? 'border-color:var(--accent-error);' : 'border-color:rgba(35,134,54,0.3);') + '">';
1771
- html += '<strong>' + (isError ? '\u274c' : '\u2705') + ' Result</strong>';
2302
+ html +=
2303
+ '<div class="chat-bubble chat-tool" style="' +
2304
+ (isError ? 'border-color:var(--accent-error);' : 'border-color:rgba(35,134,54,0.3);') +
2305
+ '">';
2306
+ html += `<strong>${isError ? '\u274c' : '\u2705'} Result</strong>`;
1772
2307
  var resultText = evt.toolError || evt.toolResult || '';
1773
- html += '<div style="margin-top:4px;max-height:120px;overflow:hidden;font-size:0.75rem;color:' + (isError ? 'var(--accent-error)' : 'var(--text-secondary)') + ';">' + escapeHtml(resultText.substring(0, 400)) + '</div>';
1774
- html += '<div class="chat-meta">' + evtTime + '</div>';
2308
+ html +=
2309
+ '<div style="margin-top:4px;max-height:120px;overflow:hidden;font-size:0.75rem;color:' +
2310
+ (isError ? 'var(--accent-error)' : 'var(--text-secondary)') +
2311
+ ';">' +
2312
+ escapeHtml(resultText.substring(0, 400)) +
2313
+ '</div>';
2314
+ html += `<div class="chat-meta">${evtTime}</div>`;
1775
2315
  html += '</div>';
1776
2316
  }
1777
2317
 
1778
2318
  if (evt.type === 'spawn') {
1779
- html += '<div class="chat-bubble" style="margin:0 auto;max-width:70%;background:rgba(0,200,200,0.08);border:1px solid rgba(0,200,200,0.25);text-align:center;">';
2319
+ html +=
2320
+ '<div class="chat-bubble" style="margin:0 auto;max-width:70%;background:rgba(0,200,200,0.08);border:1px solid rgba(0,200,200,0.25);text-align:center;">';
1780
2321
  html += '\ud83d\udc64 Subagent spawned';
1781
- if (evt.content) html += ': <code>' + escapeHtml(evt.content.substring(0, 40)) + '</code>';
1782
- html += '<div class="chat-meta">' + evtTime + '</div>';
2322
+ if (evt.content) html += `: <code>${escapeHtml(evt.content.substring(0, 40))}</code>`;
2323
+ html += `<div class="chat-meta">${evtTime}</div>`;
1783
2324
  html += '</div>';
1784
2325
  }
1785
2326
  }
@@ -1798,15 +2339,19 @@ class AgentFlowDashboard {
1798
2339
  panel.classList.remove('show');
1799
2340
  return;
1800
2341
  }
1801
- list.innerHTML = messages.map(function(m) { return '<li>' + escapeHtml(m) + '</li>'; }).join('');
2342
+ list.innerHTML = messages.map((m) => `<li>${escapeHtml(m)}</li>`).join('');
1802
2343
  panel.classList.add('show');
1803
2344
  }
1804
2345
 
1805
2346
  // ---------------------------------------------------------------------------
1806
2347
  // Public / debug
1807
2348
  // ---------------------------------------------------------------------------
1808
- getStats() { return this.stats; }
1809
- getTraces() { return this.traces; }
2349
+ getStats() {
2350
+ return this.stats;
2351
+ }
2352
+ getTraces() {
2353
+ return this.traces;
2354
+ }
1810
2355
  reconnect() {
1811
2356
  if (this.ws) this.ws.close();
1812
2357
  this.reconnectAttempts = 0;
@@ -1834,13 +2379,13 @@ class AgentFlowDashboard {
1834
2379
  var nodeTypes = [];
1835
2380
 
1836
2381
  if (nodes instanceof Map) {
1837
- nodes.forEach(function(node) {
2382
+ nodes.forEach((node) => {
1838
2383
  if (node.type) nodeTypes.push(node.type);
1839
2384
  });
1840
2385
  } else if (typeof nodes === 'object') {
1841
2386
  for (var nodeId in nodes) {
1842
2387
  var node = nodes[nodeId];
1843
- if (node && node.type) nodeTypes.push(node.type);
2388
+ if (node?.type) nodeTypes.push(node.type);
1844
2389
  }
1845
2390
  }
1846
2391
 
@@ -1856,10 +2401,14 @@ class AgentFlowDashboard {
1856
2401
  }
1857
2402
 
1858
2403
  // Tag processes by activity type
1859
- getProcessActivityTag(cmd, cmdline, pid) {
2404
+ getProcessActivityTag(_cmd, cmdline, _pid) {
1860
2405
  // Main processes (primary orchestrators)
1861
- if (cmdline.includes('main') || cmdline.includes('orchestrator') ||
1862
- cmdline.includes('coordinator') || cmdline.includes('master')) {
2406
+ if (
2407
+ cmdline.includes('main') ||
2408
+ cmdline.includes('orchestrator') ||
2409
+ cmdline.includes('coordinator') ||
2410
+ cmdline.includes('master')
2411
+ ) {
1863
2412
  return 'main';
1864
2413
  }
1865
2414
 
@@ -1869,50 +2418,82 @@ class AgentFlowDashboard {
1869
2418
  }
1870
2419
 
1871
2420
  // Browser/UI processes
1872
- if (cmdline.includes('browser') || cmdline.includes('chrome') ||
1873
- cmdline.includes('firefox') || cmdline.includes('dashboard')) {
2421
+ if (
2422
+ cmdline.includes('browser') ||
2423
+ cmdline.includes('chrome') ||
2424
+ cmdline.includes('firefox') ||
2425
+ cmdline.includes('dashboard')
2426
+ ) {
1874
2427
  return 'browser';
1875
2428
  }
1876
2429
 
1877
2430
  // Context/memory processes
1878
- if (cmdline.includes('context') || cmdline.includes('memory') ||
1879
- cmdline.includes('cache') || cmdline.includes('embedding')) {
2431
+ if (
2432
+ cmdline.includes('context') ||
2433
+ cmdline.includes('memory') ||
2434
+ cmdline.includes('cache') ||
2435
+ cmdline.includes('embedding')
2436
+ ) {
1880
2437
  return 'context';
1881
2438
  }
1882
2439
 
1883
2440
  // Execution processes
1884
- if (cmdline.includes('exec') || cmdline.includes('runner') ||
1885
- cmdline.includes('executor') || cmdline.includes('worker')) {
2441
+ if (
2442
+ cmdline.includes('exec') ||
2443
+ cmdline.includes('runner') ||
2444
+ cmdline.includes('executor') ||
2445
+ cmdline.includes('worker')
2446
+ ) {
1886
2447
  return 'exec';
1887
2448
  }
1888
2449
 
1889
2450
  // Read operations
1890
- if (cmdline.includes('read') || cmdline.includes('scanner') ||
1891
- cmdline.includes('parser') || cmdline.includes('loader')) {
2451
+ if (
2452
+ cmdline.includes('read') ||
2453
+ cmdline.includes('scanner') ||
2454
+ cmdline.includes('parser') ||
2455
+ cmdline.includes('loader')
2456
+ ) {
1892
2457
  return 'read';
1893
2458
  }
1894
2459
 
1895
2460
  // Tool processes
1896
- if (cmdline.includes('tool') || cmdline.includes('utility') ||
1897
- cmdline.includes('helper') || cmdline.includes('script')) {
2461
+ if (
2462
+ cmdline.includes('tool') ||
2463
+ cmdline.includes('utility') ||
2464
+ cmdline.includes('helper') ||
2465
+ cmdline.includes('script')
2466
+ ) {
1898
2467
  return 'tool';
1899
2468
  }
1900
2469
 
1901
2470
  // Thinking/AI processes
1902
- if (cmdline.includes('think') || cmdline.includes('reason') ||
1903
- cmdline.includes('llm') || cmdline.includes('model')) {
2471
+ if (
2472
+ cmdline.includes('think') ||
2473
+ cmdline.includes('reason') ||
2474
+ cmdline.includes('llm') ||
2475
+ cmdline.includes('model')
2476
+ ) {
1904
2477
  return 'think';
1905
2478
  }
1906
2479
 
1907
2480
  // User interface processes
1908
- if (cmdline.includes('ui') || cmdline.includes('frontend') ||
1909
- cmdline.includes('interface') || cmdline.includes('client')) {
2481
+ if (
2482
+ cmdline.includes('ui') ||
2483
+ cmdline.includes('frontend') ||
2484
+ cmdline.includes('interface') ||
2485
+ cmdline.includes('client')
2486
+ ) {
1910
2487
  return 'user';
1911
2488
  }
1912
2489
 
1913
2490
  // Write/output processes
1914
- if (cmdline.includes('write') || cmdline.includes('output') ||
1915
- cmdline.includes('export') || cmdline.includes('save')) {
2491
+ if (
2492
+ cmdline.includes('write') ||
2493
+ cmdline.includes('output') ||
2494
+ cmdline.includes('export') ||
2495
+ cmdline.includes('save')
2496
+ ) {
1916
2497
  return 'write';
1917
2498
  }
1918
2499
 
@@ -1930,7 +2511,6 @@ class AgentFlowDashboard {
1930
2511
  }
1931
2512
 
1932
2513
  var agentId = trace.agentId;
1933
- var self = this;
1934
2514
 
1935
2515
  if (this._agentTimelineAgent === agentId && this._agentTimelineRendered) return;
1936
2516
  this._agentTimelineAgent = agentId;
@@ -1938,20 +2518,24 @@ class AgentFlowDashboard {
1938
2518
  var container = document.getElementById('agentTimelineContent');
1939
2519
  container.innerHTML =
1940
2520
  '<div class="empty-state"><div class="empty-state-icon" style="animation:spin 1s linear infinite">&#9881;</div>' +
1941
- '<div class="empty-state-text">Loading timeline for ' + escapeHtml(agentId) + '...</div></div>';
2521
+ '<div class="empty-state-text">Loading timeline for ' +
2522
+ escapeHtml(agentId) +
2523
+ '...</div></div>';
1942
2524
 
1943
- fetch('/api/agents/' + encodeURIComponent(agentId) + '/timeline?limit=50')
1944
- .then(function(r) { return r.json(); })
1945
- .then(function(data) {
2525
+ fetch(`/api/agents/${encodeURIComponent(agentId)}/timeline?limit=50`)
2526
+ .then((r) => r.json())
2527
+ .then((data) => {
1946
2528
  if (data.error || !data.executions || data.executions.length === 0) {
1947
2529
  container.innerHTML =
1948
- '<div class="empty-state"><div class="empty-state-text">No timeline data for ' + escapeHtml(agentId) + '</div></div>';
2530
+ '<div class="empty-state"><div class="empty-state-text">No timeline data for ' +
2531
+ escapeHtml(agentId) +
2532
+ '</div></div>';
1949
2533
  return;
1950
2534
  }
1951
- self._agentTimelineRendered = true;
1952
- self._renderGantt(container, data);
2535
+ this._agentTimelineRendered = true;
2536
+ this._renderGantt(container, data);
1953
2537
  })
1954
- .catch(function() {
2538
+ .catch(() => {
1955
2539
  container.innerHTML =
1956
2540
  '<div class="empty-state"><div class="empty-state-text">Failed to load agent timeline.</div></div>';
1957
2541
  });
@@ -1962,7 +2546,6 @@ class AgentFlowDashboard {
1962
2546
  var minTime = data.minTime;
1963
2547
  var maxTime = data.maxTime;
1964
2548
  var timeSpan = maxTime - minTime || 1;
1965
- var self = this;
1966
2549
 
1967
2550
  // Layout constants
1968
2551
  var labelW = 220;
@@ -1973,11 +2556,22 @@ class AgentFlowDashboard {
1973
2556
  var totalW = labelW + chartW + 20;
1974
2557
 
1975
2558
  // Build HTML
1976
- var html = '<div class="gantt-wrapper" style="font-size:11px;color:#c9d1d9;min-width:' + totalW + 'px;">';
2559
+ var html =
2560
+ '<div class="gantt-wrapper" style="font-size:11px;color:#c9d1d9;min-width:' +
2561
+ totalW +
2562
+ 'px;">';
1977
2563
 
1978
2564
  // Header with time axis
1979
- html += '<div class="gantt-header" style="display:flex;height:' + headerH + 'px;border-bottom:1px solid #30363d;position:sticky;top:0;background:#0d1117;z-index:2;">';
1980
- html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:8px 10px;font-weight:600;color:#8b949e;">Execution</div>';
2565
+ html +=
2566
+ '<div class="gantt-header" style="display:flex;height:' +
2567
+ headerH +
2568
+ 'px;border-bottom:1px solid #30363d;position:sticky;top:0;background:#0d1117;z-index:2;">';
2569
+ html +=
2570
+ '<div style="width:' +
2571
+ labelW +
2572
+ 'px;min-width:' +
2573
+ labelW +
2574
+ 'px;padding:8px 10px;font-weight:600;color:#8b949e;">Execution</div>';
1981
2575
  html += '<div style="flex:1;position:relative;">';
1982
2576
  // Time ticks
1983
2577
  var tickCount = 6;
@@ -1985,8 +2579,21 @@ class AgentFlowDashboard {
1985
2579
  var pct = (t / tickCount) * 100;
1986
2580
  var tickTime = minTime + (t / tickCount) * timeSpan;
1987
2581
  var d = new Date(tickTime);
1988
- var label = d.getMonth() + 1 + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
1989
- html += '<div style="position:absolute;left:' + pct + '%;top:0;height:100%;border-left:1px solid #21262d;padding:8px 4px;font-size:9px;color:#6b7280;white-space:nowrap;">' + label + '</div>';
2582
+ var label =
2583
+ d.getMonth() +
2584
+ 1 +
2585
+ '/' +
2586
+ d.getDate() +
2587
+ ' ' +
2588
+ String(d.getHours()).padStart(2, '0') +
2589
+ ':' +
2590
+ String(d.getMinutes()).padStart(2, '0');
2591
+ html +=
2592
+ '<div style="position:absolute;left:' +
2593
+ pct +
2594
+ '%;top:0;height:100%;border-left:1px solid #21262d;padding:8px 4px;font-size:9px;color:#6b7280;white-space:nowrap;">' +
2595
+ label +
2596
+ '</div>';
1990
2597
  }
1991
2598
  html += '</div></div>';
1992
2599
 
@@ -1996,35 +2603,74 @@ class AgentFlowDashboard {
1996
2603
  var exec = execs[i];
1997
2604
  var execStart = ((exec.startTime - minTime) / timeSpan) * 100;
1998
2605
  var execWidth = Math.max(0.3, ((exec.endTime - exec.startTime) / timeSpan) * 100);
1999
- var statusColor = exec.status === 'failed' ? '#ef4444' : exec.status === 'running' ? '#3b82f6' : '#10b981';
2606
+ var statusColor =
2607
+ exec.status === 'failed' ? '#ef4444' : exec.status === 'running' ? '#3b82f6' : '#10b981';
2000
2608
  var hasActivities = exec.activities && exec.activities.length > 0;
2001
- var execId = 'gantt-exec-' + i;
2609
+ var execId = `gantt-exec-${i}`;
2002
2610
 
2003
2611
  // Main execution row
2004
- html += '<div class="gantt-row" style="display:flex;height:' + rowH + 'px;border-bottom:1px solid #161b22;cursor:pointer;" ' +
2005
- 'onclick="(function(){var el=document.getElementById(\'' + execId + '\');if(el)el.style.display=el.style.display===\'none\'?\'block\':\'none\';})()" ' +
2006
- 'title="Click to ' + (hasActivities ? 'expand' : 'view') + '">';
2612
+ html +=
2613
+ '<div class="gantt-row" style="display:flex;height:' +
2614
+ rowH +
2615
+ 'px;border-bottom:1px solid #161b22;cursor:pointer;" ' +
2616
+ 'onclick="(function(){var el=document.getElementById(\'' +
2617
+ execId +
2618
+ "');if(el)el.style.display=el.style.display==='none'?'block':'none';})()\" " +
2619
+ 'title="Click to ' +
2620
+ (hasActivities ? 'expand' : 'view') +
2621
+ '">';
2007
2622
 
2008
2623
  // Label
2009
2624
  var execName = exec.name || exec.filename || exec.id;
2010
- if (execName.length > 28) execName = execName.slice(0, 28) + '...';
2625
+ if (execName.length > 28) execName = `${execName.slice(0, 28)}...`;
2011
2626
  var dur = this.computeDuration(exec.startTime, exec.endTime);
2012
- var triggerBadge = exec.trigger ? '<span style="background:#1f2937;padding:1px 4px;border-radius:3px;font-size:8px;margin-left:4px;">' + escapeHtml(exec.trigger) + '</span>' : '';
2013
- var expandIcon = hasActivities ? '<span style="color:#6b7280;margin-right:4px;">&#9654;</span>' : '<span style="width:14px;display:inline-block;"></span>';
2014
-
2015
- html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:4px 10px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:' + (rowH - 8) + 'px;">' +
2016
- expandIcon + escapeHtml(execName) + triggerBadge + '</div>';
2627
+ var triggerBadge = exec.trigger
2628
+ ? '<span style="background:#1f2937;padding:1px 4px;border-radius:3px;font-size:8px;margin-left:4px;">' +
2629
+ escapeHtml(exec.trigger) +
2630
+ '</span>'
2631
+ : '';
2632
+ var expandIcon = hasActivities
2633
+ ? '<span style="color:#6b7280;margin-right:4px;">&#9654;</span>'
2634
+ : '<span style="width:14px;display:inline-block;"></span>';
2635
+
2636
+ html +=
2637
+ '<div style="width:' +
2638
+ labelW +
2639
+ 'px;min-width:' +
2640
+ labelW +
2641
+ 'px;padding:4px 10px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:' +
2642
+ (rowH - 8) +
2643
+ 'px;">' +
2644
+ expandIcon +
2645
+ escapeHtml(execName) +
2646
+ triggerBadge +
2647
+ '</div>';
2017
2648
 
2018
2649
  // Bar
2019
2650
  html += '<div style="flex:1;position:relative;padding:4px 0;">';
2020
- html += '<div style="position:absolute;left:' + execStart + '%;width:' + execWidth + '%;top:4px;height:' + (rowH - 12) + 'px;' +
2021
- 'background:' + statusColor + ';border-radius:3px;opacity:0.85;min-width:3px;" ' +
2022
- 'title="' + escapeHtml(exec.name || '') + ' | ' + dur + ' | ' + escapeHtml(exec.status) + '"></div>';
2651
+ html +=
2652
+ '<div style="position:absolute;left:' +
2653
+ execStart +
2654
+ '%;width:' +
2655
+ execWidth +
2656
+ '%;top:4px;height:' +
2657
+ (rowH - 12) +
2658
+ 'px;' +
2659
+ 'background:' +
2660
+ statusColor +
2661
+ ';border-radius:3px;opacity:0.85;min-width:3px;" ' +
2662
+ 'title="' +
2663
+ escapeHtml(exec.name || '') +
2664
+ ' | ' +
2665
+ dur +
2666
+ ' | ' +
2667
+ escapeHtml(exec.status) +
2668
+ '"></div>';
2023
2669
  html += '</div></div>';
2024
2670
 
2025
2671
  // Sub-activities (collapsed by default)
2026
2672
  if (hasActivities) {
2027
- html += '<div id="' + execId + '" style="display:none;background:#0a0e14;">';
2673
+ html += `<div id="${execId}" style="display:none;background:#0a0e14;">`;
2028
2674
  // Filter to top-level activities (no parentId or parentId is root)
2029
2675
  var rootIds = new Set();
2030
2676
  if (exec.activities.length > 0) {
@@ -2036,27 +2682,63 @@ class AgentFlowDashboard {
2036
2682
  var act = exec.activities[j];
2037
2683
  var actStart = ((Math.max(act.startTime, exec.startTime) - minTime) / timeSpan) * 100;
2038
2684
  var actEnd = act.endTime || act.startTime;
2039
- var actWidth = Math.max(0.2, ((actEnd - Math.max(act.startTime, exec.startTime)) / timeSpan) * 100);
2040
- var actColor = act.status === 'failed' ? '#f87171' :
2041
- act.type === 'user' ? '#60a5fa' :
2042
- act.type === 'assistant' ? '#34d399' :
2043
- act.type === 'thinking' ? '#a78bfa' :
2044
- act.type === 'tool_call' ? '#fb923c' :
2045
- act.type === 'tool_result' ? '#4ade80' :
2046
- act.type === 'agent' ? '#38bdf8' :
2047
- '#6b7280';
2685
+ var actWidth = Math.max(
2686
+ 0.2,
2687
+ ((actEnd - Math.max(act.startTime, exec.startTime)) / timeSpan) * 100,
2688
+ );
2689
+ var actColor =
2690
+ act.status === 'failed'
2691
+ ? '#f87171'
2692
+ : act.type === 'user'
2693
+ ? '#60a5fa'
2694
+ : act.type === 'assistant'
2695
+ ? '#34d399'
2696
+ : act.type === 'thinking'
2697
+ ? '#a78bfa'
2698
+ : act.type === 'tool_call'
2699
+ ? '#fb923c'
2700
+ : act.type === 'tool_result'
2701
+ ? '#4ade80'
2702
+ : act.type === 'agent'
2703
+ ? '#38bdf8'
2704
+ : '#6b7280';
2048
2705
  var actName = act.name || act.type;
2049
- if (actName.length > 30) actName = actName.slice(0, 30) + '...';
2706
+ if (actName.length > 30) actName = `${actName.slice(0, 30)}...`;
2050
2707
  var isChild = act.parentId && !rootIds.has(act.id);
2051
2708
 
2052
- html += '<div style="display:flex;height:' + subRowH + 'px;border-bottom:1px solid #0d1117;">';
2053
- html += '<div style="width:' + labelW + 'px;min-width:' + labelW + 'px;padding:2px 10px 2px ' + (isChild ? '30' : '20') + 'px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:10px;color:#8b949e;line-height:' + (subRowH - 4) + 'px;">' +
2054
- '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' + actColor + ';margin-right:6px;vertical-align:middle;"></span>' +
2055
- escapeHtml(actName) + '</div>';
2709
+ html += `<div style="display:flex;height:${subRowH}px;border-bottom:1px solid #0d1117;">`;
2710
+ html +=
2711
+ '<div style="width:' +
2712
+ labelW +
2713
+ 'px;min-width:' +
2714
+ labelW +
2715
+ 'px;padding:2px 10px 2px ' +
2716
+ (isChild ? '30' : '20') +
2717
+ 'px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:10px;color:#8b949e;line-height:' +
2718
+ (subRowH - 4) +
2719
+ 'px;">' +
2720
+ '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' +
2721
+ actColor +
2722
+ ';margin-right:6px;vertical-align:middle;"></span>' +
2723
+ escapeHtml(actName) +
2724
+ '</div>';
2056
2725
  html += '<div style="flex:1;position:relative;">';
2057
- html += '<div style="position:absolute;left:' + actStart + '%;width:' + actWidth + '%;top:3px;height:' + (subRowH - 8) + 'px;' +
2058
- 'background:' + actColor + ';border-radius:2px;opacity:0.7;min-width:2px;" ' +
2059
- 'title="' + escapeHtml(act.name || act.type) + ' | ' + escapeHtml(act.status) + '"></div>';
2726
+ html +=
2727
+ '<div style="position:absolute;left:' +
2728
+ actStart +
2729
+ '%;width:' +
2730
+ actWidth +
2731
+ '%;top:3px;height:' +
2732
+ (subRowH - 8) +
2733
+ 'px;' +
2734
+ 'background:' +
2735
+ actColor +
2736
+ ';border-radius:2px;opacity:0.7;min-width:2px;" ' +
2737
+ 'title="' +
2738
+ escapeHtml(act.name || act.type) +
2739
+ ' | ' +
2740
+ escapeHtml(act.status) +
2741
+ '"></div>';
2060
2742
  html += '</div></div>';
2061
2743
  }
2062
2744
  html += '</div>';
@@ -2067,9 +2749,15 @@ class AgentFlowDashboard {
2067
2749
 
2068
2750
  // Summary bar
2069
2751
  html += '<div style="padding:10px;border-top:1px solid #30363d;color:#8b949e;font-size:10px;">';
2070
- html += escapeHtml(data.agentId) + ' &mdash; ' + data.executions.length + ' of ' + data.totalExecutions + ' executions shown';
2071
- var timeRange = new Date(minTime).toLocaleDateString() + ' to ' + new Date(maxTime).toLocaleDateString();
2072
- html += ' &mdash; ' + timeRange;
2752
+ html +=
2753
+ escapeHtml(data.agentId) +
2754
+ ' &mdash; ' +
2755
+ data.executions.length +
2756
+ ' of ' +
2757
+ data.totalExecutions +
2758
+ ' executions shown';
2759
+ var timeRange = `${new Date(minTime).toLocaleDateString()} to ${new Date(maxTime).toLocaleDateString()}`;
2760
+ html += ` &mdash; ${timeRange}`;
2073
2761
  html += '</div>';
2074
2762
 
2075
2763
  html += '</div>';
@@ -2087,7 +2775,6 @@ class AgentFlowDashboard {
2087
2775
  }
2088
2776
 
2089
2777
  var agentId = trace.agentId;
2090
- var self = this;
2091
2778
 
2092
2779
  // Avoid re-fetching for same agent
2093
2780
  if (this._processMapAgent === agentId && this._cyProcessMap) return;
@@ -2095,22 +2782,28 @@ class AgentFlowDashboard {
2095
2782
 
2096
2783
  document.getElementById('processMapEmpty').innerHTML =
2097
2784
  '<div class="empty-state-icon" style="animation:spin 1s linear infinite">&#9881;</div>' +
2098
- '<div class="empty-state-text">Building process map for ' + escapeHtml(agentId) + '...</div>';
2785
+ '<div class="empty-state-text">Building process map for ' +
2786
+ escapeHtml(agentId) +
2787
+ '...</div>';
2099
2788
  document.getElementById('processMapEmpty').style.display = '';
2100
2789
 
2101
- fetch('/api/agents/' + encodeURIComponent(agentId) + '/process-graph')
2102
- .then(function(r) { return r.json(); })
2103
- .then(function(data) {
2790
+ fetch(`/api/agents/${encodeURIComponent(agentId)}/process-graph`)
2791
+ .then((r) => r.json())
2792
+ .then((data) => {
2104
2793
  if (data.error || !data.nodes || data.nodes.length === 0) {
2105
2794
  document.getElementById('processMapEmpty').innerHTML =
2106
2795
  '<div class="empty-state-icon">&#9881;</div>' +
2107
- '<div class="empty-state-text">No process data for ' + escapeHtml(agentId) + '</div>';
2796
+ '<div class="empty-state-text">No process data for ' +
2797
+ escapeHtml(agentId) +
2798
+ '</div>';
2108
2799
  return;
2109
2800
  }
2110
2801
  document.getElementById('processMapEmpty').style.display = 'none';
2111
- self._buildProcessMapGraph(data);
2802
+ this._buildProcessMapGraph(data);
2803
+ this._loadVariantPanel(agentId);
2804
+ this._loadProfileCard(agentId);
2112
2805
  })
2113
- .catch(function() {
2806
+ .catch(() => {
2114
2807
  document.getElementById('processMapEmpty').innerHTML =
2115
2808
  '<div class="empty-state-icon">&#9881;</div>' +
2116
2809
  '<div class="empty-state-text">Failed to load process map.</div>';
@@ -2118,7 +2811,10 @@ class AgentFlowDashboard {
2118
2811
  }
2119
2812
 
2120
2813
  _buildProcessMapGraph(data) {
2121
- if (this._cyProcessMap) { this._cyProcessMap.destroy(); this._cyProcessMap = null; }
2814
+ if (this._cyProcessMap) {
2815
+ this._cyProcessMap.destroy();
2816
+ this._cyProcessMap = null;
2817
+ }
2122
2818
 
2123
2819
  var elements = [];
2124
2820
  var maxNode = data.maxNodeCount || 1;
@@ -2132,7 +2828,7 @@ class AgentFlowDashboard {
2132
2828
 
2133
2829
  var size = node.isVirtual ? 30 : Math.max(25, Math.min(70, 25 + 45 * (node.count / maxNode)));
2134
2830
  var label = node.label;
2135
- if (!node.isVirtual && node.count > 1) label += ' (' + node.count + ')';
2831
+ if (!node.isVirtual && node.count > 1) label += ` (${node.count})`;
2136
2832
 
2137
2833
  elements.push({
2138
2834
  group: 'nodes',
@@ -2143,15 +2839,16 @@ class AgentFlowDashboard {
2143
2839
  frequency: node.frequency,
2144
2840
  avgDuration: node.avgDuration,
2145
2841
  failRate: node.failRate,
2842
+ p95Duration: node.p95Duration || 0,
2146
2843
  isVirtual: node.isVirtual,
2147
2844
  size: size,
2148
- fullData: node
2149
- }
2845
+ fullData: node,
2846
+ },
2150
2847
  });
2151
2848
  }
2152
2849
 
2153
2850
  // Collect valid node IDs
2154
- var validIds = new Set(elements.map(function(e) { return e.data.id; }));
2851
+ var validIds = new Set(elements.map((e) => e.data.id));
2155
2852
 
2156
2853
  // Add edges (only between valid nodes)
2157
2854
  for (var j = 0; j < data.edges.length; j++) {
@@ -2166,20 +2863,19 @@ class AgentFlowDashboard {
2166
2863
  elements.push({
2167
2864
  group: 'edges',
2168
2865
  data: {
2169
- id: 'pe-' + edge.source + '-' + edge.target,
2866
+ id: `pe-${edge.source}-${edge.target}`,
2170
2867
  source: edge.source,
2171
2868
  target: edge.target,
2172
2869
  count: edge.count,
2173
2870
  frequency: edge.frequency,
2174
2871
  width: width,
2175
2872
  opacity: opacity,
2176
- label: edge.count > 1 ? String(edge.count) : ''
2177
- }
2873
+ label: edge.count > 1 ? String(edge.count) : '',
2874
+ },
2178
2875
  });
2179
2876
  }
2180
2877
 
2181
2878
  var container = document.getElementById('cyProcessMap');
2182
- var self = this;
2183
2879
 
2184
2880
  this._cyProcessMap = cytoscape({
2185
2881
  container: container,
@@ -2188,14 +2884,14 @@ class AgentFlowDashboard {
2188
2884
  {
2189
2885
  selector: 'node',
2190
2886
  style: {
2191
- 'label': 'data(label)',
2192
- 'width': 'data(size)',
2193
- 'height': 'data(size)',
2887
+ label: 'data(label)',
2888
+ width: 'data(size)',
2889
+ height: 'data(size)',
2194
2890
  'font-size': '9px',
2195
2891
  'text-valign': 'bottom',
2196
2892
  'text-halign': 'center',
2197
2893
  'text-margin-y': 6,
2198
- 'color': '#c9d1d9',
2894
+ color: '#c9d1d9',
2199
2895
  'text-outline-color': '#0d1117',
2200
2896
  'text-outline-width': 2,
2201
2897
  'text-wrap': 'ellipsis',
@@ -2203,40 +2899,80 @@ class AgentFlowDashboard {
2203
2899
  'border-width': 2,
2204
2900
  'border-color': '#30363d',
2205
2901
  'background-color': '#3b82f6',
2206
- 'shape': 'round-rectangle'
2207
- }
2902
+ shape: 'round-rectangle',
2903
+ },
2208
2904
  },
2209
2905
  // Virtual START/END nodes
2210
- { selector: 'node[?isVirtual]', style: {
2211
- 'background-color': '#6b7280', 'shape': 'ellipse', 'border-color': '#4b5563',
2212
- 'font-size': '8px', 'font-weight': 'bold', 'text-valign': 'center', 'text-margin-y': 0
2213
- }},
2906
+ {
2907
+ selector: 'node[?isVirtual]',
2908
+ style: {
2909
+ 'background-color': '#6b7280',
2910
+ shape: 'ellipse',
2911
+ 'border-color': '#4b5563',
2912
+ 'font-size': '8px',
2913
+ 'font-weight': 'bold',
2914
+ 'text-valign': 'center',
2915
+ 'text-margin-y': 0,
2916
+ },
2917
+ },
2214
2918
  // Color by fail rate: green → yellow → red
2215
- { selector: 'node[failRate <= 0]', style: { 'background-color': '#10b981', 'border-color': '#2ea043' }},
2216
- { selector: 'node[failRate > 0][failRate <= 0.1]', style: { 'background-color': '#22c55e', 'border-color': '#3fb950' }},
2217
- { selector: 'node[failRate > 0.1][failRate <= 0.3]', style: { 'background-color': '#eab308', 'border-color': '#d29922' }},
2218
- { selector: 'node[failRate > 0.3]', style: { 'background-color': '#ef4444', 'border-color': '#f85149' }},
2919
+ {
2920
+ selector: 'node[failRate <= 0]',
2921
+ style: { 'background-color': '#10b981', 'border-color': '#2ea043' },
2922
+ },
2923
+ {
2924
+ selector: 'node[failRate > 0][failRate <= 0.1]',
2925
+ style: { 'background-color': '#22c55e', 'border-color': '#3fb950' },
2926
+ },
2927
+ {
2928
+ selector: 'node[failRate > 0.1][failRate <= 0.3]',
2929
+ style: { 'background-color': '#eab308', 'border-color': '#d29922' },
2930
+ },
2931
+ {
2932
+ selector: 'node[failRate > 0.3]',
2933
+ style: { 'background-color': '#ef4444', 'border-color': '#f85149' },
2934
+ },
2935
+ // Bottleneck heat: p95 duration highlighting (overrides failRate coloring when present)
2936
+ {
2937
+ selector: 'node[p95Duration > 0][p95Duration <= 1000]',
2938
+ style: { 'border-color': '#22c55e', 'border-width': 3 },
2939
+ },
2940
+ {
2941
+ selector: 'node[p95Duration > 1000][p95Duration <= 10000]',
2942
+ style: { 'border-color': '#eab308', 'border-width': 3 },
2943
+ },
2944
+ {
2945
+ selector: 'node[p95Duration > 10000][p95Duration <= 60000]',
2946
+ style: { 'border-color': '#f97316', 'border-width': 4 },
2947
+ },
2948
+ {
2949
+ selector: 'node[p95Duration > 60000]',
2950
+ style: { 'border-color': '#ef4444', 'border-width': 4 },
2951
+ },
2219
2952
  // Selected
2220
- { selector: ':selected', style: { 'border-width': 4, 'border-color': '#f59e0b', 'overlay-opacity': 0.08 }},
2953
+ {
2954
+ selector: ':selected',
2955
+ style: { 'border-width': 4, 'border-color': '#f59e0b', 'overlay-opacity': 0.08 },
2956
+ },
2221
2957
  // Edges
2222
2958
  {
2223
2959
  selector: 'edge',
2224
2960
  style: {
2225
- 'width': 'data(width)',
2226
- 'opacity': 'data(opacity)',
2961
+ width: 'data(width)',
2962
+ opacity: 'data(opacity)',
2227
2963
  'line-color': '#6b7280',
2228
2964
  'target-arrow-color': '#6b7280',
2229
2965
  'target-arrow-shape': 'triangle',
2230
2966
  'curve-style': 'bezier',
2231
2967
  'arrow-scale': 0.7,
2232
- 'label': 'data(label)',
2968
+ label: 'data(label)',
2233
2969
  'font-size': '8px',
2234
- 'color': '#8b949e',
2970
+ color: '#8b949e',
2235
2971
  'text-outline-color': '#0d1117',
2236
2972
  'text-outline-width': 1.5,
2237
- 'text-rotation': 'autorotate'
2238
- }
2239
- }
2973
+ 'text-rotation': 'autorotate',
2974
+ },
2975
+ },
2240
2976
  ],
2241
2977
  layout: {
2242
2978
  name: 'breadthfirst',
@@ -2245,16 +2981,18 @@ class AgentFlowDashboard {
2245
2981
  spacingFactor: 1.6,
2246
2982
  animate: true,
2247
2983
  animationDuration: 400,
2248
- roots: elements.filter(function(e) { return e.data && e.data.id === '[START]'; }).length > 0
2249
- ? ['[START]'] : undefined
2984
+ roots:
2985
+ elements.filter((e) => e.data && e.data.id === '[START]').length > 0
2986
+ ? ['[START]']
2987
+ : undefined,
2250
2988
  },
2251
2989
  minZoom: 0.15,
2252
2990
  maxZoom: 4,
2253
- wheelSensitivity: 0.3
2991
+ wheelSensitivity: 0.3,
2254
2992
  });
2255
2993
 
2256
2994
  // Click node → show detail
2257
- this._cyProcessMap.on('tap', 'node', function(e) {
2995
+ this._cyProcessMap.on('tap', 'node', (e) => {
2258
2996
  var d = e.target.data().fullData;
2259
2997
  if (!d || d.isVirtual) return;
2260
2998
  var panel = document.getElementById('processMapDetailPanel');
@@ -2263,16 +3001,19 @@ class AgentFlowDashboard {
2263
3001
 
2264
3002
  title.textContent = d.label;
2265
3003
  var html = '';
2266
- html += self.detailRow('Occurrences', d.count);
2267
- html += self.detailRow('Frequency', (d.frequency * 100).toFixed(1) + '% of traces');
2268
- if (d.avgDuration > 0) html += self.detailRow('Avg Duration', self.computeDuration(0, d.avgDuration));
2269
- html += self.detailRow('Failure Rate', (d.failRate * 100).toFixed(1) + '%');
3004
+ html += this.detailRow('Occurrences', d.count);
3005
+ html += this.detailRow('Frequency', `${(d.frequency * 100).toFixed(1)}% of traces`);
3006
+ if (d.avgDuration > 0)
3007
+ html += this.detailRow('Avg Duration', this.computeDuration(0, d.avgDuration));
3008
+ if (d.p95Duration > 0)
3009
+ html += this.detailRow('p95 Duration', this.computeDuration(0, d.p95Duration));
3010
+ html += this.detailRow('Failure Rate', `${(d.failRate * 100).toFixed(1)}%`);
2270
3011
  body.innerHTML = html;
2271
3012
  panel.classList.add('active');
2272
3013
  });
2273
3014
 
2274
- this._cyProcessMap.on('tap', function(e) {
2275
- if (e.target === self._cyProcessMap) {
3015
+ this._cyProcessMap.on('tap', (e) => {
3016
+ if (e.target === this._cyProcessMap) {
2276
3017
  document.getElementById('processMapDetailPanel').classList.remove('active');
2277
3018
  }
2278
3019
  });
@@ -2280,15 +3021,92 @@ class AgentFlowDashboard {
2280
3021
  // Close button
2281
3022
  var closeBtn = document.getElementById('processMapDetailClose');
2282
3023
  if (closeBtn) {
2283
- closeBtn.onclick = function() {
3024
+ closeBtn.onclick = () => {
2284
3025
  document.getElementById('processMapDetailPanel').classList.remove('active');
2285
3026
  };
2286
3027
  }
2287
3028
  }
3029
+
3030
+ _loadVariantPanel(agentId) {
3031
+ var panel = document.getElementById('variantPanel');
3032
+ if (!panel) {
3033
+ // Create variant panel below the process map container
3034
+ var container = document.getElementById('cyProcessMap');
3035
+ if (!container) return;
3036
+ panel = document.createElement('div');
3037
+ panel.id = 'variantPanel';
3038
+ panel.style.cssText = 'margin-top:12px;padding:12px;background:#161b22;border:1px solid #30363d;border-radius:6px;max-height:200px;overflow-y:auto;display:none;';
3039
+ container.parentNode.insertBefore(panel, container.nextSibling);
3040
+ }
3041
+
3042
+ fetch(`/api/agents/${encodeURIComponent(agentId)}/variants`)
3043
+ .then(function(r) { return r.json(); })
3044
+ .then(function(data) {
3045
+ if (!data.variants || data.variants.length === 0) {
3046
+ panel.innerHTML = '<div style="color:#8b949e;font-size:12px;">No variant data available</div>';
3047
+ panel.style.display = 'block';
3048
+ return;
3049
+ }
3050
+ var html = '<div style="font-size:11px;font-weight:600;color:#c9d1d9;margin-bottom:8px;">Top Variants (' + data.totalTraces + ' traces)</div>';
3051
+ var variants = data.variants.slice(0, 5);
3052
+ for (var i = 0; i < variants.length; i++) {
3053
+ var v = variants[i];
3054
+ var sig = v.pathSignature.length > 60 ? v.pathSignature.slice(0, 57) + '...' : v.pathSignature;
3055
+ html += '<div style="margin-bottom:4px;font-size:11px;">' +
3056
+ '<span style="color:#58a6ff;font-weight:600;">' + v.percentage.toFixed(1) + '%</span>' +
3057
+ ' <span style="color:#8b949e;">(n=' + v.count + ')</span> ' +
3058
+ '<code style="color:#c9d1d9;font-size:10px;">' + escapeHtml(sig) + '</code></div>';
3059
+ }
3060
+ panel.innerHTML = html;
3061
+ panel.style.display = 'block';
3062
+ })
3063
+ .catch(function() {
3064
+ panel.style.display = 'none';
3065
+ });
3066
+ }
3067
+
3068
+ _loadProfileCard(agentId) {
3069
+ var card = document.getElementById('agentProfileCard');
3070
+ if (!card) {
3071
+ var container = document.getElementById('cyProcessMap');
3072
+ if (!container) return;
3073
+ card = document.createElement('div');
3074
+ card.id = 'agentProfileCard';
3075
+ card.style.cssText = 'margin-bottom:12px;padding:10px 14px;background:#161b22;border:1px solid #30363d;border-radius:6px;display:none;font-size:12px;';
3076
+ container.parentNode.insertBefore(card, container);
3077
+ }
3078
+
3079
+ fetch(`/api/agents/${encodeURIComponent(agentId)}/profile`)
3080
+ .then(function(r) {
3081
+ if (r.status === 404) return null;
3082
+ return r.json();
3083
+ })
3084
+ .then(function(profile) {
3085
+ if (!profile) {
3086
+ card.style.display = 'none';
3087
+ return;
3088
+ }
3089
+ var html = '<div style="display:flex;gap:20px;flex-wrap:wrap;align-items:center;">';
3090
+ html += '<span style="font-weight:600;color:#c9d1d9;">' + escapeHtml(profile.agentId) + '</span>';
3091
+ html += '<span style="color:#8b949e;">Runs: <span style="color:#c9d1d9;">' + profile.totalRuns + '</span></span>';
3092
+ html += '<span style="color:#8b949e;">Success: <span style="color:#3fb950;">' + profile.successCount + '</span></span>';
3093
+ html += '<span style="color:#8b949e;">Failed: <span style="color:' + (profile.failureCount > 0 ? '#f85149' : '#8b949e') + ';">' + profile.failureCount + '</span></span>';
3094
+ html += '<span style="color:#8b949e;">Failure Rate: <span style="color:' + (profile.failureRate > 0.3 ? '#f85149' : '#c9d1d9') + ';">' + (profile.failureRate * 100).toFixed(1) + '%</span></span>';
3095
+ if (profile.knownBottlenecks && profile.knownBottlenecks.length > 0) {
3096
+ html += '<span style="color:#8b949e;">Bottlenecks: <span style="color:#f97316;">' + profile.knownBottlenecks.slice(0, 3).join(', ') + '</span></span>';
3097
+ }
3098
+ html += '</div>';
3099
+ card.innerHTML = html;
3100
+ card.style.display = 'block';
3101
+ })
3102
+ .catch(function() {
3103
+ card.style.display = 'none';
3104
+ });
3105
+ }
2288
3106
  }
2289
3107
 
2290
3108
  // Initialize
2291
- document.addEventListener('DOMContentLoaded', function() {
3109
+ document.addEventListener('DOMContentLoaded', () => {
2292
3110
  window.dashboard = new AgentFlowDashboard();
2293
3111
  });
2294
3112