agentflow-dashboard 0.3.0 → 0.3.1

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.
@@ -30,6 +30,7 @@ class AgentFlowDashboard {
30
30
  this.searchFilter = '';
31
31
  this.statusFilter = 'all';
32
32
  this.timeRangeFilter = 'all';
33
+ this.activityFilter = 'all';
33
34
  this.isLive = true;
34
35
 
35
36
  this.cy = null;
@@ -239,6 +240,15 @@ class AgentFlowDashboard {
239
240
  self.renderTraceList();
240
241
  });
241
242
 
243
+ // Activity filter dropdown (if exists)
244
+ var activityFilter = document.getElementById('activityFilter');
245
+ if (activityFilter) {
246
+ activityFilter.addEventListener('change', function(e) {
247
+ self.activityFilter = e.target.value;
248
+ self.renderTraceList();
249
+ });
250
+ }
251
+
242
252
  // Toolbar buttons
243
253
  document.getElementById('btnFit').addEventListener('click', function() {
244
254
  if (self.cy) self.cy.fit(50);
@@ -339,7 +349,8 @@ class AgentFlowDashboard {
339
349
  }
340
350
 
341
351
  var r = this.processHealth;
342
- var hasContent = r.pidFile || r.systemd || r.workers || (r.orphans && r.orphans.length > 0) || (r.problems && r.problems.length > 0);
352
+ var hasContent = r.pidFile || r.systemd || r.workers || (r.orphans && r.orphans.length > 0) ||
353
+ (r.osProcesses && r.osProcesses.length > 0) || (r.problems && r.problems.length > 0);
343
354
  if (!hasContent) {
344
355
  section.style.display = 'none';
345
356
  return;
@@ -348,6 +359,7 @@ class AgentFlowDashboard {
348
359
  section.style.display = '';
349
360
  var html = '<h4>Process Health</h4>';
350
361
 
362
+ // PID File section
351
363
  if (r.pidFile) {
352
364
  var pf = r.pidFile;
353
365
  var cls = pf.alive && pf.matchesProcess ? 'ok' : pf.stale ? 'bad' : 'warn';
@@ -358,6 +370,7 @@ class AgentFlowDashboard {
358
370
  html += '</span></div>';
359
371
  }
360
372
 
373
+ // Systemd section
361
374
  if (r.systemd) {
362
375
  var sd = r.systemd;
363
376
  var sdCls = sd.activeState === 'active' ? 'ok' : sd.failed ? 'bad' : 'warn';
@@ -369,49 +382,298 @@ class AgentFlowDashboard {
369
382
  html += '</span></div>';
370
383
  }
371
384
 
385
+ // Workers detailed view
372
386
  if (r.workers && r.workers.workers) {
373
- html += '<div class="ph-row">';
387
+ html += '<div class="ph-section">';
374
388
  html += '<span class="ph-label">Workers</span>';
375
- html += '<div class="worker-dots">';
389
+ html += '<div class="process-grid">';
376
390
  for (var i = 0; i < r.workers.workers.length; i++) {
377
391
  var worker = r.workers.workers[i];
378
- var dotCls = worker.alive ? 'alive' : worker.stale ? 'stale' : 'unknown';
379
- html += '<span class="worker-dot ' + dotCls + '" title="' + escapeHtml(worker.name) + ' (pid ' + (worker.pid || '-') + ') \u2014 ' + escapeHtml(worker.declaredStatus) + '"></span>';
380
- html += '<span class="worker-dot-label">' + escapeHtml(worker.name) + '</span>';
392
+ var statusCls = worker.alive ? 'ok' : worker.stale ? 'bad' : 'warn';
393
+ html += '<div class="worker-card ' + statusCls + '">';
394
+ html += '<div class="worker-name">' + escapeHtml(worker.name) + '</div>';
395
+ html += '<div class="worker-details">';
396
+ html += '<span>PID: ' + (worker.pid || '-') + '</span>';
397
+ html += '<span>' + escapeHtml(worker.declaredStatus) + '</span>';
398
+ html += '</div></div>';
381
399
  }
382
400
  html += '</div></div>';
383
401
  }
384
402
 
385
- if (r.orphans && r.orphans.length > 0) {
386
- html += '<div class="ph-row" style="flex-direction:column;align-items:flex-start;gap:0.3rem;">';
387
- html += '<span class="ph-label">Orphans (' + r.orphans.length + ')</span>';
388
- html += '<table class="orphan-table"><thead><tr>';
389
- html += '<th>PID</th><th>CPU%</th><th>MEM%</th><th>Uptime</th><th>Command</th>';
390
- html += '</tr></thead><tbody>';
391
- for (var j = 0; j < r.orphans.length; j++) {
392
- var o = r.orphans[j];
393
- html += '<tr>';
394
- html += '<td>' + o.pid + '</td>';
395
- html += '<td>' + escapeHtml(o.cpu) + '</td>';
396
- html += '<td>' + escapeHtml(o.mem) + '</td>';
397
- html += '<td>' + escapeHtml(o.elapsed) + '</td>';
398
- html += '<td title="' + escapeHtml(o.cmdline || o.command) + '">' + escapeHtml(o.command) + '</td>';
399
- html += '</tr>';
403
+ // Agent Services - categorize processes and build tree
404
+ var categorized = this.categorizeProcesses(r.osProcesses || []);
405
+
406
+ if (categorized.agents.length > 0) {
407
+ html += '<div class="ph-section">';
408
+ html += '<span class="ph-label">Agent Services (' + categorized.agents.length + ')</span>';
409
+
410
+ // Build process tree for agents
411
+ var agentTree = this.buildProcessTree(categorized.agents);
412
+ html += this.renderProcessTree(agentTree, 'agent');
413
+ html += '</div>';
414
+ }
415
+
416
+ // Infrastructure processes
417
+ if (categorized.infrastructure.length > 0) {
418
+ html += '<div class="ph-section">';
419
+ html += '<span class="ph-label">Infrastructure (' + categorized.infrastructure.length + ')</span>';
420
+
421
+ // Build process tree for infrastructure
422
+ var infraTree = this.buildProcessTree(categorized.infrastructure);
423
+ html += this.renderProcessTree(infraTree, 'infrastructure');
424
+ html += '</div>';
425
+ }
426
+
427
+ // Orphaned processes (uncategorized)
428
+ var uncategorized = this.getUncategorizedOrphans(r.orphans || [], categorized);
429
+ if (uncategorized.length > 0) {
430
+ html += '<div class="ph-section">';
431
+ html += '<span class="ph-label">Orphans (' + uncategorized.length + ')</span>';
432
+ html += '<div class="orphan-list">';
433
+ for (var j = 0; j < uncategorized.length; j++) {
434
+ var o = uncategorized[j];
435
+ html += '<div class="orphan-row">';
436
+ html += '<span class="orphan-pid">PID ' + o.pid + '</span>';
437
+ html += '<span class="orphan-resources">CPU: ' + escapeHtml(o.cpu) + '% | MEM: ' + escapeHtml(o.mem) + '%</span>';
438
+ html += '<span class="orphan-cmd" title="' + escapeHtml(o.cmdline || o.command) + '">' +
439
+ escapeHtml((o.command || '').substring(0, 60)) + (o.command && o.command.length > 60 ? '...' : '') + '</span>';
440
+ html += '</div>';
400
441
  }
401
- html += '</tbody></table></div>';
442
+ html += '</div></div>';
402
443
  }
403
444
 
445
+ // Problems section
404
446
  if (r.problems && r.problems.length > 0) {
405
- html += '<ul class="problems-list">';
447
+ html += '<div class="ph-section problems-section">';
448
+ html += '<span class="ph-label problems">Issues</span>';
449
+ html += '<div class="problems-list">';
406
450
  for (var k = 0; k < r.problems.length; k++) {
407
- html += '<li>' + escapeHtml(r.problems[k]) + '</li>';
451
+ html += '<div class="problem-item">⚠️ ' + escapeHtml(r.problems[k]) + '</div>';
408
452
  }
409
- html += '</ul>';
453
+ html += '</div></div>';
410
454
  }
411
455
 
412
456
  section.innerHTML = html;
413
457
  }
414
458
 
459
+ // Helper to categorize processes with enhanced detection and tagging
460
+ categorizeProcesses(processes) {
461
+ var agents = [];
462
+ var infrastructure = [];
463
+
464
+ console.log('Categorizing', processes.length, 'processes');
465
+
466
+ for (var i = 0; i < processes.length; i++) {
467
+ var proc = processes[i];
468
+ var cmd = proc.command.toLowerCase();
469
+ var cmdline = (proc.cmdline || '').toLowerCase();
470
+ var service = this.detectAgentService(cmd, cmdline);
471
+ var component = this.detectInfrastructureComponent(cmd, cmdline);
472
+ var activityTag = this.getProcessActivityTag(cmd, cmdline, proc.pid);
473
+
474
+ if (component) {
475
+ infrastructure.push({
476
+ component: component,
477
+ pid: proc.pid,
478
+ cpu: proc.cpu,
479
+ mem: proc.mem,
480
+ elapsed: proc.elapsed,
481
+ ppid: proc.ppid,
482
+ cmdline: proc.cmdline || proc.command,
483
+ tag: activityTag
484
+ });
485
+ console.log('Detected infrastructure:', proc.pid, component, 'tag:', activityTag);
486
+ }
487
+ else if (service) {
488
+ agents.push({
489
+ service: service,
490
+ pid: proc.pid,
491
+ cpu: proc.cpu,
492
+ mem: proc.mem,
493
+ elapsed: proc.elapsed,
494
+ ppid: proc.ppid,
495
+ cmdline: proc.cmdline || proc.command,
496
+ tag: activityTag
497
+ });
498
+ console.log('Detected agent:', proc.pid, service, 'tag:', activityTag);
499
+ }
500
+ }
501
+
502
+ console.log('Categorization result:', {agents: agents.length, infrastructure: infrastructure.length});
503
+ return { agents: agents, infrastructure: infrastructure };
504
+ }
505
+
506
+ // Enhanced agent service detection
507
+ detectAgentService(cmd, cmdline) {
508
+ // AgentFlow processes
509
+ if (cmdline.includes('agentflow-dashboard')) return 'AgentFlow Dashboard';
510
+ if (cmdline.includes('agentflow live')) return 'AgentFlow Live';
511
+ if (cmdline.includes('agentflow') && cmdline.includes('server')) return 'AgentFlow Server';
512
+
513
+ // OpenClaw ecosystem
514
+ if (cmdline.includes('openclaw-gateway')) return 'OpenClaw Gateway';
515
+ if (cmdline.includes('openclaw-agent')) return 'OpenClaw Agent';
516
+ if (cmdline.includes('openclaw') && cmdline.includes('worker')) return 'OpenClaw Worker';
517
+ if (cmdline.includes('claw-gateway')) return 'Claw Gateway';
518
+
519
+ // Alfred workers and processes
520
+ if (cmdline.includes('alfred') && cmdline.includes('curator')) return 'Alfred Curator';
521
+ if (cmdline.includes('alfred') && cmdline.includes('janitor')) return 'Alfred Janitor';
522
+ if (cmdline.includes('alfred') && cmdline.includes('distiller')) return 'Alfred Distiller';
523
+ if (cmdline.includes('alfred') && cmdline.includes('surveyor')) return 'Alfred Surveyor';
524
+ if (cmdline.includes('alfred') && (cmdline.includes('worker') || cmdline.includes('daemon'))) return 'Alfred Worker';
525
+ if (cmdline.includes('.alfred')) return 'Alfred Process';
526
+
527
+ // AI/ML agent frameworks
528
+ if (cmdline.includes('langchain') && cmdline.includes('agent')) return 'LangChain Agent';
529
+ if (cmdline.includes('crewai')) return 'CrewAI Agent';
530
+ if (cmdline.includes('autogen')) return 'AutoGen Agent';
531
+ if (cmdline.includes('mastra')) return 'Mastra Agent';
532
+
533
+ // Node.js/Python AI processes
534
+ if ((cmd.includes('node') || cmd.includes('python')) &&
535
+ (cmdline.includes('agent') || cmdline.includes('ai') || cmdline.includes('llm'))) {
536
+ return 'AI Agent Process';
537
+ }
538
+
539
+ // Temporal workflow processes
540
+ if (cmdline.includes('temporal') && (cmdline.includes('worker') || cmdline.includes('agent'))) {
541
+ return 'Temporal Agent';
542
+ }
543
+
544
+ // Generic agent indicators
545
+ if (cmdline.includes('agent') &&
546
+ (cmdline.includes('server') || cmdline.includes('worker') || cmdline.includes('daemon'))) {
547
+ return 'Agent Service';
548
+ }
549
+
550
+ return null;
551
+ }
552
+
553
+ // Enhanced infrastructure component detection
554
+ detectInfrastructureComponent(cmd, cmdline) {
555
+ // Debug logging
556
+ if (cmdline.includes('milvus')) {
557
+ console.log('Found Milvus process:', cmdline.substring(0, 100));
558
+ }
559
+
560
+ // Vector databases
561
+ if (cmd.includes('milvus') || cmdline.includes('milvus')) return 'Milvus Vector DB';
562
+ if (cmd.includes('weaviate') || cmdline.includes('weaviate')) return 'Weaviate Vector DB';
563
+ if (cmd.includes('pinecone') || cmdline.includes('pinecone')) return 'Pinecone Vector DB';
564
+ if (cmd.includes('qdrant') || cmdline.includes('qdrant')) return 'Qdrant Vector DB';
565
+
566
+ // Traditional databases
567
+ if (cmd.includes('redis') || cmdline.includes('redis')) return 'Redis Cache';
568
+ if (cmd.includes('postgres') || cmdline.includes('postgres')) return 'PostgreSQL';
569
+ if (cmd.includes('mongodb') || cmdline.includes('mongo')) return 'MongoDB';
570
+
571
+ // Message queues and workflows
572
+ if (cmdline.includes('temporal') && cmdline.includes('server')) return 'Temporal Server';
573
+ if (cmd.includes('rabbitmq') || cmdline.includes('rabbitmq')) return 'RabbitMQ';
574
+ if (cmd.includes('kafka') || cmdline.includes('kafka')) return 'Apache Kafka';
575
+
576
+ // Observability
577
+ if (cmdline.includes('prometheus')) return 'Prometheus';
578
+ if (cmdline.includes('grafana')) return 'Grafana';
579
+ if (cmdline.includes('jaeger')) return 'Jaeger Tracing';
580
+
581
+ // Container/orchestration
582
+ if (cmd.includes('docker') && !cmdline.includes('agent')) return 'Docker';
583
+ if (cmd.includes('k3s') || cmd.includes('kubectl')) return 'Kubernetes';
584
+
585
+ return null;
586
+ }
587
+
588
+ // Get orphans that weren't categorized
589
+ getUncategorizedOrphans(orphans, categorized) {
590
+ var allCategorizedPids = categorized.agents.concat(categorized.infrastructure).map(function(p) { return p.pid; });
591
+ return orphans.filter(function(o) { return allCategorizedPids.indexOf(o.pid) === -1; });
592
+ }
593
+
594
+ // Build hierarchical process tree from flat process list
595
+ buildProcessTree(processes) {
596
+ var tree = [];
597
+ var processMap = {};
598
+
599
+ // Create a map of all processes
600
+ for (var i = 0; i < processes.length; i++) {
601
+ var proc = processes[i];
602
+ processMap[proc.pid] = {
603
+ process: proc,
604
+ children: []
605
+ };
606
+ }
607
+
608
+ // Build parent-child relationships
609
+ for (var j = 0; j < processes.length; j++) {
610
+ var proc = processes[j];
611
+ if (proc.ppid && processMap[proc.ppid]) {
612
+ // This process has a parent in our categorized list
613
+ processMap[proc.ppid].children.push(processMap[proc.pid]);
614
+ } else {
615
+ // This is a root process (no parent in our list)
616
+ tree.push(processMap[proc.pid]);
617
+ }
618
+ }
619
+
620
+ return tree;
621
+ }
622
+
623
+ // Render process tree with indentation
624
+ renderProcessTree(tree, type) {
625
+ var html = '<div class="process-tree">';
626
+
627
+ for (var i = 0; i < tree.length; i++) {
628
+ html += this.renderProcessNode(tree[i], type, 0);
629
+ }
630
+
631
+ html += '</div>';
632
+ return html;
633
+ }
634
+
635
+ // Render individual process node recursively
636
+ renderProcessNode(node, type, depth) {
637
+ var proc = node.process;
638
+ var indent = 'padding-left: ' + (depth * 20) + 'px;';
639
+ var cpuNum = parseFloat(proc.cpu) || 0;
640
+ var cpuCls = type === 'agent'
641
+ ? (cpuNum > 50 ? 'high' : cpuNum > 10 ? 'medium' : 'low')
642
+ : (cpuNum > 20 ? 'high' : cpuNum > 5 ? 'medium' : 'low');
643
+
644
+ var serviceName = type === 'agent' ? proc.service : proc.component;
645
+
646
+ var html = '<div class="process-node ' + type + '-node ' + cpuCls + '" style="' + indent + '">';
647
+
648
+ // Process icon and name
649
+ if (depth > 0) {
650
+ html += '<span class="tree-connector">└─ </span>';
651
+ }
652
+ html += '<div class="process-main">';
653
+ html += '<div class="process-name" title="' + escapeHtml(proc.cmdline) + '">' + escapeHtml(serviceName) + '</div>';
654
+
655
+ // Add activity tag
656
+ if (proc.tag && proc.tag !== 'other') {
657
+ html += '<span class="activity-tag tag-' + proc.tag + '">' + proc.tag + '</span>';
658
+ }
659
+
660
+ html += '<div class="process-metrics">';
661
+ html += '<span class="pid-badge">PID: ' + proc.pid + '</span>';
662
+ html += '<span class="cpu-badge">CPU: ' + escapeHtml(proc.cpu) + '%</span>';
663
+ html += '<span class="mem-badge">MEM: ' + escapeHtml(proc.mem) + '%</span>';
664
+ html += '<span class="time-badge">Up: ' + escapeHtml(proc.elapsed) + '</span>';
665
+ html += '</div>';
666
+ html += '</div>';
667
+ html += '</div>';
668
+
669
+ // Render children recursively
670
+ for (var i = 0; i < node.children.length; i++) {
671
+ html += this.renderProcessNode(node.children[i], type, depth + 1);
672
+ }
673
+
674
+ return html;
675
+ }
676
+
415
677
  // ---------------------------------------------------------------------------
416
678
  // Rendering: Trace list (limit to 100 most recent for perf)
417
679
  // ---------------------------------------------------------------------------
@@ -456,6 +718,14 @@ class AgentFlowDashboard {
456
718
  });
457
719
  }
458
720
 
721
+ // Activity filter
722
+ if (this.activityFilter && this.activityFilter !== 'all') {
723
+ var activityTarget = this.activityFilter;
724
+ filtered = filtered.filter(function(t) {
725
+ return self.getTraceActivity(t) === activityTarget;
726
+ });
727
+ }
728
+
459
729
  countEl.textContent = filtered.length + ' of ' + this.traces.length + ' traces';
460
730
 
461
731
  // Render max 100 items for performance
@@ -1530,6 +1800,112 @@ class AgentFlowDashboard {
1530
1800
  this.reconnectAttempts = 0;
1531
1801
  this.connectWebSocket();
1532
1802
  }
1803
+
1804
+ // Categorize traces by activity type
1805
+ getTraceActivity(trace) {
1806
+ if (!trace) return 'unknown';
1807
+
1808
+ var agentId = (trace.agentId || '').toLowerCase();
1809
+ var name = (trace.name || '').toLowerCase();
1810
+ var filename = (trace.filename || '').toLowerCase();
1811
+
1812
+ // Check for specific agent types
1813
+ if (agentId.includes('main') || name.includes('main')) return 'main';
1814
+ if (agentId.includes('agent') || name.includes('agent')) return 'agents';
1815
+
1816
+ // Check filename patterns
1817
+ if (filename.includes('browser') || name.includes('browser')) return 'browser';
1818
+ if (filename.includes('context') || name.includes('context')) return 'context';
1819
+
1820
+ // Check for activity types in trace content
1821
+ var nodes = trace.nodes || {};
1822
+ var nodeTypes = [];
1823
+
1824
+ if (nodes instanceof Map) {
1825
+ nodes.forEach(function(node) {
1826
+ if (node.type) nodeTypes.push(node.type);
1827
+ });
1828
+ } else if (typeof nodes === 'object') {
1829
+ for (var nodeId in nodes) {
1830
+ var node = nodes[nodeId];
1831
+ if (node && node.type) nodeTypes.push(node.type);
1832
+ }
1833
+ }
1834
+
1835
+ // Categorize based on node types and content
1836
+ if (nodeTypes.includes('tool') || nodeTypes.includes('exec')) return 'exec';
1837
+ if (nodeTypes.includes('read') || name.includes('read')) return 'read';
1838
+ if (nodeTypes.includes('write') || name.includes('write')) return 'write';
1839
+ if (nodeTypes.includes('think') || name.includes('think')) return 'think';
1840
+ if (nodeTypes.includes('user') || name.includes('user')) return 'user';
1841
+ if (nodeTypes.includes('tool')) return 'tool';
1842
+
1843
+ return 'other';
1844
+ }
1845
+
1846
+ // Tag processes by activity type
1847
+ getProcessActivityTag(cmd, cmdline, pid) {
1848
+ // Main processes (primary orchestrators)
1849
+ if (cmdline.includes('main') || cmdline.includes('orchestrator') ||
1850
+ cmdline.includes('coordinator') || cmdline.includes('master')) {
1851
+ return 'main';
1852
+ }
1853
+
1854
+ // Agent processes
1855
+ if (cmdline.includes('agent') && !cmdline.includes('browser')) {
1856
+ return 'agents';
1857
+ }
1858
+
1859
+ // Browser/UI processes
1860
+ if (cmdline.includes('browser') || cmdline.includes('chrome') ||
1861
+ cmdline.includes('firefox') || cmdline.includes('dashboard')) {
1862
+ return 'browser';
1863
+ }
1864
+
1865
+ // Context/memory processes
1866
+ if (cmdline.includes('context') || cmdline.includes('memory') ||
1867
+ cmdline.includes('cache') || cmdline.includes('embedding')) {
1868
+ return 'context';
1869
+ }
1870
+
1871
+ // Execution processes
1872
+ if (cmdline.includes('exec') || cmdline.includes('runner') ||
1873
+ cmdline.includes('executor') || cmdline.includes('worker')) {
1874
+ return 'exec';
1875
+ }
1876
+
1877
+ // Read operations
1878
+ if (cmdline.includes('read') || cmdline.includes('scanner') ||
1879
+ cmdline.includes('parser') || cmdline.includes('loader')) {
1880
+ return 'read';
1881
+ }
1882
+
1883
+ // Tool processes
1884
+ if (cmdline.includes('tool') || cmdline.includes('utility') ||
1885
+ cmdline.includes('helper') || cmdline.includes('script')) {
1886
+ return 'tool';
1887
+ }
1888
+
1889
+ // Thinking/AI processes
1890
+ if (cmdline.includes('think') || cmdline.includes('reason') ||
1891
+ cmdline.includes('llm') || cmdline.includes('model')) {
1892
+ return 'think';
1893
+ }
1894
+
1895
+ // User interface processes
1896
+ if (cmdline.includes('ui') || cmdline.includes('frontend') ||
1897
+ cmdline.includes('interface') || cmdline.includes('client')) {
1898
+ return 'user';
1899
+ }
1900
+
1901
+ // Write/output processes
1902
+ if (cmdline.includes('write') || cmdline.includes('output') ||
1903
+ cmdline.includes('export') || cmdline.includes('save')) {
1904
+ return 'write';
1905
+ }
1906
+
1907
+ return 'other';
1908
+ }
1533
1909
  }
1534
1910
 
1535
1911
  // Initialize
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><title>Test Dashboard API</title></head>
4
+ <body>
5
+ <script>
6
+ fetch('/api/process-health')
7
+ .then(r => r.json())
8
+ .then(data => {
9
+ console.log('Process Health Data:', data);
10
+
11
+ // Test categorization
12
+ const processes = data.osProcesses || [];
13
+ let agents = [];
14
+ let infrastructure = [];
15
+
16
+ processes.forEach(proc => {
17
+ const cmd = proc.command.toLowerCase();
18
+ const cmdline = (proc.cmdline || '').toLowerCase();
19
+
20
+ // Test infrastructure detection
21
+ if (cmd.includes('milvus') || cmdline.includes('milvus')) {
22
+ infrastructure.push({component: 'Milvus Vector DB', ...proc});
23
+ } else if (cmdline.includes('agentflow-dashboard')) {
24
+ agents.push({service: 'AgentFlow Dashboard', ...proc});
25
+ }
26
+ });
27
+
28
+ console.log('Detected Infrastructure:', infrastructure);
29
+ console.log('Detected Agents:', agents);
30
+
31
+ document.body.innerHTML = `
32
+ <h2>Process Health Debug</h2>
33
+ <h3>Infrastructure (${infrastructure.length}):</h3>
34
+ <ul>${infrastructure.map(i => `<li>PID ${i.pid}: ${i.component} (CPU: ${i.cpu}%)</li>`).join('')}</ul>
35
+ <h3>Agents (${agents.length}):</h3>
36
+ <ul>${agents.map(a => `<li>PID ${a.pid}: ${a.service} (CPU: ${a.cpu}%)</li>`).join('')}</ul>
37
+ <h3>Total OS Processes: ${processes.length}</h3>
38
+ `;
39
+ })
40
+ .catch(console.error);
41
+ </script>
42
+ </body>
43
+ </html>