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