claude-mpm 3.4.13__py3-none-any.whl → 3.4.14__py3-none-any.whl

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.
Files changed (27) hide show
  1. claude_mpm/dashboard/index.html +13 -0
  2. claude_mpm/dashboard/static/css/dashboard.css +2722 -0
  3. claude_mpm/dashboard/static/js/components/agent-inference.js +619 -0
  4. claude_mpm/dashboard/static/js/components/event-processor.js +641 -0
  5. claude_mpm/dashboard/static/js/components/event-viewer.js +914 -0
  6. claude_mpm/dashboard/static/js/components/export-manager.js +362 -0
  7. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +611 -0
  8. claude_mpm/dashboard/static/js/components/hud-library-loader.js +211 -0
  9. claude_mpm/dashboard/static/js/components/hud-manager.js +671 -0
  10. claude_mpm/dashboard/static/js/components/hud-visualizer.js +1718 -0
  11. claude_mpm/dashboard/static/js/components/module-viewer.js +2701 -0
  12. claude_mpm/dashboard/static/js/components/session-manager.js +520 -0
  13. claude_mpm/dashboard/static/js/components/socket-manager.js +343 -0
  14. claude_mpm/dashboard/static/js/components/ui-state-manager.js +427 -0
  15. claude_mpm/dashboard/static/js/components/working-directory.js +866 -0
  16. claude_mpm/dashboard/static/js/dashboard-original.js +4134 -0
  17. claude_mpm/dashboard/static/js/dashboard.js +1978 -0
  18. claude_mpm/dashboard/static/js/socket-client.js +537 -0
  19. claude_mpm/dashboard/templates/index.html +346 -0
  20. claude_mpm/dashboard/test_dashboard.html +372 -0
  21. claude_mpm/services/socketio_server.py +41 -5
  22. {claude_mpm-3.4.13.dist-info → claude_mpm-3.4.14.dist-info}/METADATA +2 -1
  23. {claude_mpm-3.4.13.dist-info → claude_mpm-3.4.14.dist-info}/RECORD +27 -7
  24. {claude_mpm-3.4.13.dist-info → claude_mpm-3.4.14.dist-info}/WHEEL +0 -0
  25. {claude_mpm-3.4.13.dist-info → claude_mpm-3.4.14.dist-info}/entry_points.txt +0 -0
  26. {claude_mpm-3.4.13.dist-info → claude_mpm-3.4.14.dist-info}/licenses/LICENSE +0 -0
  27. {claude_mpm-3.4.13.dist-info → claude_mpm-3.4.14.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1718 @@
1
+ /**
2
+ * HUD Visualizer Component
3
+ * Manages the Cytoscape.js tree visualization for the HUD mode with lazy loading
4
+ */
5
+
6
+ class HUDVisualizer {
7
+ constructor() {
8
+ this.cy = null;
9
+ this.container = null;
10
+ this.nodes = new Map(); // Map of node IDs to node data
11
+ this.isActive = false;
12
+ this.librariesLoaded = false;
13
+ this.loadingPromise = null;
14
+ this.pendingEvents = []; // Store events received before libraries are loaded
15
+
16
+ // Layout configuration
17
+ this.layoutConfig = {
18
+ name: 'dagre',
19
+ rankDir: 'TB', // Top to bottom
20
+ animate: true,
21
+ animationDuration: 500,
22
+ fit: true,
23
+ padding: 30,
24
+ rankSep: 100,
25
+ nodeSep: 80
26
+ };
27
+
28
+ // Node type configurations
29
+ this.nodeTypes = {
30
+ PM: {
31
+ color: '#48bb78',
32
+ shape: 'rectangle',
33
+ width: 120,
34
+ height: 40,
35
+ icon: '👤'
36
+ },
37
+ AGENT: {
38
+ color: '#9f7aea',
39
+ shape: 'ellipse',
40
+ width: 100,
41
+ height: 60,
42
+ icon: '🤖'
43
+ },
44
+ TOOL: {
45
+ color: '#4299e1',
46
+ shape: 'diamond',
47
+ width: 80,
48
+ height: 50,
49
+ icon: '🔧'
50
+ },
51
+ TODO: {
52
+ color: '#e53e3e',
53
+ shape: 'triangle',
54
+ width: 70,
55
+ height: 40,
56
+ icon: '📝'
57
+ }
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Initialize the HUD visualizer (called at startup)
63
+ */
64
+ initialize() {
65
+ this.container = document.getElementById('hud-cytoscape');
66
+ if (!this.container) {
67
+ console.error('HUD container not found');
68
+ return false;
69
+ }
70
+
71
+ // Ensure container has proper attributes for interaction
72
+ this.container.style.pointerEvents = 'auto';
73
+ this.container.style.cursor = 'default';
74
+ this.container.style.position = 'relative';
75
+ this.container.style.zIndex = '1';
76
+
77
+ // Setup basic event handlers (not library-dependent)
78
+ this.setupBasicEventHandlers();
79
+
80
+ console.log('HUD Visualizer initialized (libraries will load lazily)');
81
+ return true;
82
+ }
83
+
84
+ /**
85
+ * Load libraries and initialize Cytoscape when HUD is first activated
86
+ * @returns {Promise} - Promise that resolves when libraries are loaded and Cytoscape is initialized
87
+ */
88
+ async loadLibrariesAndInitialize() {
89
+ if (this.librariesLoaded && this.cy) {
90
+ return Promise.resolve();
91
+ }
92
+
93
+ // If already loading, return the existing promise
94
+ if (this.loadingPromise) {
95
+ return this.loadingPromise;
96
+ }
97
+
98
+ this.loadingPromise = this._performLazyLoading();
99
+ return this.loadingPromise;
100
+ }
101
+
102
+ /**
103
+ * Perform the actual lazy loading process
104
+ * @private
105
+ */
106
+ async _performLazyLoading() {
107
+ try {
108
+ console.log('[HUD-VISUALIZER-DEBUG] _performLazyLoading() called');
109
+ console.log('[HUD-VISUALIZER-DEBUG] Loading HUD visualization libraries...');
110
+
111
+ // Show loading indicator
112
+ this.showLoadingIndicator();
113
+
114
+ // Load libraries using the HUD library loader
115
+ if (!window.HUDLibraryLoader) {
116
+ throw new Error('HUD Library Loader not available');
117
+ }
118
+
119
+ console.log('[HUD-VISUALIZER-DEBUG] HUD Library Loader found, loading libraries...');
120
+ await window.HUDLibraryLoader.loadHUDLibraries((progress) => {
121
+ console.log('[HUD-VISUALIZER-DEBUG] Loading progress:', progress);
122
+ this.updateLoadingProgress(progress);
123
+ });
124
+
125
+ // Verify libraries are available
126
+ console.log('[HUD-VISUALIZER-DEBUG] Verifying libraries are loaded...');
127
+ if (typeof window.cytoscape === 'undefined') {
128
+ throw new Error('Cytoscape.js not loaded');
129
+ }
130
+ if (typeof window.dagre === 'undefined') {
131
+ throw new Error('Dagre not loaded');
132
+ }
133
+ if (typeof window.cytoscapeDagre === 'undefined') {
134
+ throw new Error('Cytoscape-dagre not loaded');
135
+ }
136
+
137
+ console.log('[HUD-VISUALIZER-DEBUG] All HUD libraries loaded successfully');
138
+ this.librariesLoaded = true;
139
+
140
+ // Initialize Cytoscape instance
141
+ console.log('[HUD-VISUALIZER-DEBUG] Initializing Cytoscape...');
142
+ this.initializeCytoscape();
143
+
144
+ // Setup library-dependent event handlers
145
+ console.log('[HUD-VISUALIZER-DEBUG] Setting up Cytoscape event handlers...');
146
+ this.setupCytoscapeEventHandlers();
147
+
148
+ // Process any pending events
149
+ console.log('[HUD-VISUALIZER-DEBUG] Processing pending events...');
150
+ this.processPendingEvents();
151
+
152
+ // Hide loading indicator
153
+ this.hideLoadingIndicator();
154
+
155
+ console.log('[HUD-VISUALIZER-DEBUG] HUD Visualizer fully initialized with lazy loading');
156
+ return true;
157
+
158
+ } catch (error) {
159
+ console.error('[HUD-VISUALIZER-DEBUG] Failed to load HUD libraries:', error);
160
+ console.error('[HUD-VISUALIZER-DEBUG] Error stack:', error.stack);
161
+ this.showLoadingError(error.message);
162
+ this.librariesLoaded = false;
163
+ this.loadingPromise = null;
164
+ throw error;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Initialize Cytoscape.js instance (called after libraries are loaded)
170
+ */
171
+ initializeCytoscape() {
172
+ if (!this.librariesLoaded || !window.cytoscape) {
173
+ console.error('Cannot initialize Cytoscape: libraries not loaded');
174
+ return;
175
+ }
176
+
177
+ // Register dagre extension for hierarchical layouts
178
+ if (typeof window.cytoscape !== 'undefined' && typeof window.cytoscapeDagre !== 'undefined') {
179
+ window.cytoscape.use(window.cytoscapeDagre);
180
+ }
181
+
182
+ this.cy = window.cytoscape({
183
+ container: this.container,
184
+
185
+ elements: [],
186
+
187
+ // Enable user interaction
188
+ userZoomingEnabled: true,
189
+ userPanningEnabled: true,
190
+ boxSelectionEnabled: false,
191
+ autoungrabify: false,
192
+ autounselectify: false,
193
+
194
+ style: [
195
+ // Node styles
196
+ {
197
+ selector: 'node',
198
+ style: {
199
+ 'background-color': 'data(color)',
200
+ 'border-color': 'data(borderColor)',
201
+ 'border-width': 2,
202
+ 'color': '#ffffff',
203
+ 'label': 'data(label)',
204
+ 'text-valign': 'center',
205
+ 'text-halign': 'center',
206
+ 'font-size': '12px',
207
+ 'font-weight': 'bold',
208
+ 'width': 'data(width)',
209
+ 'height': 'data(height)',
210
+ 'shape': 'data(shape)',
211
+ 'text-wrap': 'wrap',
212
+ 'text-max-width': '100px'
213
+ }
214
+ },
215
+
216
+ // Edge styles
217
+ {
218
+ selector: 'edge',
219
+ style: {
220
+ 'width': 2,
221
+ 'line-color': '#718096',
222
+ 'target-arrow-color': '#718096',
223
+ 'target-arrow-shape': 'triangle',
224
+ 'curve-style': 'bezier',
225
+ 'arrow-scale': 1.2
226
+ }
227
+ },
228
+
229
+ // Node type specific styles
230
+ {
231
+ selector: '.pm-node',
232
+ style: {
233
+ 'background-color': '#48bb78',
234
+ 'border-color': '#38a169',
235
+ 'shape': 'rectangle'
236
+ }
237
+ },
238
+
239
+ {
240
+ selector: '.agent-node',
241
+ style: {
242
+ 'background-color': '#9f7aea',
243
+ 'border-color': '#805ad5',
244
+ 'shape': 'ellipse'
245
+ }
246
+ },
247
+
248
+ {
249
+ selector: '.tool-node',
250
+ style: {
251
+ 'background-color': '#4299e1',
252
+ 'border-color': '#3182ce',
253
+ 'shape': 'diamond'
254
+ }
255
+ },
256
+
257
+ {
258
+ selector: '.todo-node',
259
+ style: {
260
+ 'background-color': '#e53e3e',
261
+ 'border-color': '#c53030',
262
+ 'shape': 'triangle'
263
+ }
264
+ },
265
+
266
+ // Hover effects
267
+ {
268
+ selector: 'node:active',
269
+ style: {
270
+ 'overlay-opacity': 0.2,
271
+ 'overlay-color': '#000000'
272
+ }
273
+ }
274
+ ],
275
+
276
+ layout: this.layoutConfig
277
+ });
278
+
279
+ // Setup resize handler
280
+ this.setupResizeHandler();
281
+ }
282
+
283
+ /**
284
+ * Setup basic event handlers (not dependent on libraries)
285
+ */
286
+ setupBasicEventHandlers() {
287
+ // Reset layout button
288
+ const resetBtn = document.getElementById('hud-reset-layout');
289
+ if (resetBtn) {
290
+ resetBtn.addEventListener('click', () => {
291
+ this.resetLayout();
292
+ });
293
+ }
294
+
295
+ // Center view button
296
+ const centerBtn = document.getElementById('hud-center-view');
297
+ if (centerBtn) {
298
+ centerBtn.addEventListener('click', () => {
299
+ this.centerView();
300
+ });
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Setup Cytoscape-dependent event handlers (called after libraries are loaded)
306
+ */
307
+ setupCytoscapeEventHandlers() {
308
+ if (!this.cy) {
309
+ console.warn('[HUD-VISUALIZER-DEBUG] Cannot setup Cytoscape event handlers: no cy instance');
310
+ return;
311
+ }
312
+
313
+ console.log('[HUD-VISUALIZER-DEBUG] Setting up Cytoscape event handlers...');
314
+
315
+ // Node click events
316
+ this.cy.on('tap', 'node', (evt) => {
317
+ const node = evt.target;
318
+ const data = node.data();
319
+ console.log('[HUD-VISUALIZER-DEBUG] Node clicked:', data);
320
+
321
+ // Highlight connected nodes
322
+ this.highlightConnectedNodes(node);
323
+ });
324
+
325
+ // Background click events
326
+ this.cy.on('tap', (evt) => {
327
+ if (evt.target === this.cy) {
328
+ console.log('[HUD-VISUALIZER-DEBUG] Background clicked - resetting highlights');
329
+ // Reset all node styles
330
+ this.cy.nodes().style({
331
+ 'opacity': 1
332
+ });
333
+
334
+ this.cy.edges().style({
335
+ 'opacity': 1
336
+ });
337
+ }
338
+ });
339
+
340
+ // Mouse events for debugging
341
+ this.cy.on('mouseover', 'node', (evt) => {
342
+ const node = evt.target;
343
+ node.style('opacity', 0.8);
344
+ });
345
+
346
+ this.cy.on('mouseout', 'node', (evt) => {
347
+ const node = evt.target;
348
+ node.style('opacity', 1);
349
+ });
350
+
351
+ console.log('[HUD-VISUALIZER-DEBUG] Cytoscape event handlers set up successfully');
352
+ }
353
+
354
+ /**
355
+ * Setup resize handler for container
356
+ */
357
+ setupResizeHandler() {
358
+ const resizeObserver = new ResizeObserver(() => {
359
+ if (this.cy && this.isActive) {
360
+ this.ensureContainerResize();
361
+ }
362
+ });
363
+
364
+ if (this.container) {
365
+ resizeObserver.observe(this.container);
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Ensure container is properly resized and visible
371
+ */
372
+ ensureContainerResize() {
373
+ if (!this.cy || !this.container) {
374
+ console.log('[HUD-VISUALIZER-DEBUG] Cannot resize: missing cy or container');
375
+ return;
376
+ }
377
+
378
+ // Ensure container can receive events
379
+ this.ensureContainerInteractivity();
380
+
381
+ // Log container dimensions
382
+ const containerRect = this.container.getBoundingClientRect();
383
+ console.log('[HUD-VISUALIZER-DEBUG] Container dimensions:', {
384
+ width: containerRect.width,
385
+ height: containerRect.height,
386
+ offsetWidth: this.container.offsetWidth,
387
+ offsetHeight: this.container.offsetHeight,
388
+ isVisible: containerRect.width > 0 && containerRect.height > 0
389
+ });
390
+
391
+ // Only proceed if container is visible
392
+ if (containerRect.width > 0 && containerRect.height > 0) {
393
+ console.log('[HUD-VISUALIZER-DEBUG] Container is visible, resizing Cytoscape...');
394
+
395
+ try {
396
+ // Force Cytoscape to resize
397
+ this.cy.resize();
398
+
399
+ // Log Cytoscape elements
400
+ const nodeCount = this.cy.nodes().length;
401
+ const edgeCount = this.cy.edges().length;
402
+ console.log('[HUD-VISUALIZER-DEBUG] Cytoscape elements after resize:', {
403
+ nodes: nodeCount,
404
+ edges: edgeCount
405
+ });
406
+
407
+ // If we have nodes, fit and run layout
408
+ if (nodeCount > 0) {
409
+ console.log('[HUD-VISUALIZER-DEBUG] Running fit and layout...');
410
+ this.cy.fit();
411
+ this.runLayout();
412
+ } else {
413
+ console.log('[HUD-VISUALIZER-DEBUG] No nodes to display');
414
+ }
415
+
416
+ } catch (error) {
417
+ console.error('[HUD-VISUALIZER-DEBUG] Error during resize:', error);
418
+ }
419
+ } else {
420
+ console.log('[HUD-VISUALIZER-DEBUG] Container not visible yet, skipping resize');
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Ensure container can receive mouse and touch events
426
+ */
427
+ ensureContainerInteractivity() {
428
+ if (!this.container) return;
429
+
430
+ // Force container to be interactive
431
+ this.container.style.pointerEvents = 'auto';
432
+ this.container.style.cursor = 'default';
433
+ this.container.style.userSelect = 'none';
434
+ this.container.style.touchAction = 'manipulation';
435
+
436
+ // Remove any overlapping elements that might block events
437
+ const parent = this.container.parentElement;
438
+ if (parent) {
439
+ parent.style.pointerEvents = 'auto';
440
+ parent.style.position = 'relative';
441
+ }
442
+
443
+ console.log('[HUD-VISUALIZER-DEBUG] Container interactivity ensured');
444
+ }
445
+
446
+ /**
447
+ * Activate the HUD visualizer (triggers lazy loading if needed)
448
+ */
449
+ async activate() {
450
+ console.log('[HUD-VISUALIZER-DEBUG] activate() called');
451
+ this.isActive = true;
452
+
453
+ try {
454
+ console.log('[HUD-VISUALIZER-DEBUG] Loading libraries and initializing...');
455
+ // Load libraries if not already loaded
456
+ await this.loadLibrariesAndInitialize();
457
+
458
+ console.log('[HUD-VISUALIZER-DEBUG] Libraries loaded, cy exists:', !!this.cy);
459
+
460
+ // If Cytoscape was destroyed during clearing, recreate it
461
+ if (!this.cy) {
462
+ console.log('[HUD-VISUALIZER-DEBUG] Cytoscape instance missing, recreating...');
463
+ this.initializeCytoscape();
464
+ this.setupCytoscapeEventHandlers();
465
+ }
466
+
467
+ if (this.cy) {
468
+ // Wait for container to be visible, then trigger resize and fit
469
+ console.log('[HUD-VISUALIZER-DEBUG] Triggering resize and fit...');
470
+
471
+ // Multiple resize attempts to ensure container visibility
472
+ setTimeout(() => {
473
+ console.log('[HUD-VISUALIZER-DEBUG] First resize attempt...');
474
+ this.ensureContainerResize();
475
+ }, 50);
476
+
477
+ setTimeout(() => {
478
+ console.log('[HUD-VISUALIZER-DEBUG] Second resize attempt...');
479
+ this.ensureContainerResize();
480
+ }, 200);
481
+
482
+ setTimeout(() => {
483
+ console.log('[HUD-VISUALIZER-DEBUG] Final resize attempt...');
484
+ this.ensureContainerResize();
485
+ }, 500);
486
+ }
487
+ console.log('[HUD-VISUALIZER-DEBUG] activate() completed successfully');
488
+ } catch (error) {
489
+ console.error('[HUD-VISUALIZER-DEBUG] Failed to activate HUD:', error);
490
+ console.error('[HUD-VISUALIZER-DEBUG] Error stack:', error.stack);
491
+ // Keep isActive true so user can retry
492
+ throw error; // Re-throw so the promise rejects properly
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Deactivate the HUD visualizer
498
+ */
499
+ deactivate() {
500
+ this.isActive = false;
501
+ }
502
+
503
+ /**
504
+ * Process pending events that were received before libraries loaded
505
+ */
506
+ processPendingEvents() {
507
+ if (this.pendingEvents.length > 0) {
508
+ console.log(`Processing ${this.pendingEvents.length} pending events`);
509
+
510
+ for (const event of this.pendingEvents) {
511
+ this._processEventInternal(event);
512
+ }
513
+
514
+ this.pendingEvents = [];
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Process existing events from dashboard when HUD is activated
520
+ * This builds the complete tree structure from historical events
521
+ * @param {Array} events - Array of sorted historical events
522
+ */
523
+ processExistingEvents(events) {
524
+ console.log(`[HUD-VISUALIZER-DEBUG] processExistingEvents called with ${events ? events.length : 0} events`);
525
+
526
+ if (!events) {
527
+ console.error('[HUD-VISUALIZER-DEBUG] No events provided to processExistingEvents');
528
+ return;
529
+ }
530
+
531
+ if (!Array.isArray(events)) {
532
+ console.error('[HUD-VISUALIZER-DEBUG] Events is not an array:', typeof events);
533
+ return;
534
+ }
535
+
536
+ console.log(`[HUD-VISUALIZER-DEBUG] Libraries loaded: ${this.librariesLoaded}, Cytoscape available: ${!!this.cy}`);
537
+
538
+ if (!this.librariesLoaded || !this.cy) {
539
+ console.warn('[HUD-VISUALIZER-DEBUG] HUD libraries not loaded, cannot process existing events');
540
+ console.log(`[HUD-VISUALIZER-DEBUG] Storing ${events.length} events as pending`);
541
+ this.pendingEvents = [...events];
542
+ return;
543
+ }
544
+
545
+ console.log(`[HUD-VISUALIZER-DEBUG] 🏗️ Building HUD tree structure from ${events.length} historical events`);
546
+
547
+ // Log sample events to understand structure
548
+ if (events.length > 0) {
549
+ console.log('[HUD-VISUALIZER-DEBUG] Sample events:');
550
+ events.slice(0, 3).forEach((event, i) => {
551
+ console.log(`[HUD-VISUALIZER-DEBUG] Event ${i + 1}:`, {
552
+ timestamp: event.timestamp,
553
+ hook_event_name: event.hook_event_name,
554
+ type: event.type,
555
+ subtype: event.subtype,
556
+ session_id: event.session_id,
557
+ data_session_id: event.data?.session_id,
558
+ data_keys: event.data ? Object.keys(event.data) : 'no data'
559
+ });
560
+ });
561
+ }
562
+
563
+ // Clear any existing visualization
564
+ this.clear();
565
+
566
+ // Group events by session to build proper hierarchies
567
+ const sessionGroups = this.groupEventsBySession(events);
568
+
569
+ // Process each session group to build trees
570
+ Object.entries(sessionGroups).forEach(([sessionId, sessionEvents]) => {
571
+ console.log(` 📂 Processing session ${sessionId}: ${sessionEvents.length} events`);
572
+ this.buildSessionTree(sessionId, sessionEvents);
573
+ });
574
+
575
+ // Run final layout to organize the complete visualization
576
+ this.runLayout();
577
+
578
+ console.log(`✅ HUD tree structure built successfully`);
579
+ }
580
+
581
+ /**
582
+ * Group events by session ID for hierarchical processing
583
+ * @param {Array} events - Array of events
584
+ * @returns {Object} Object with session IDs as keys and event arrays as values
585
+ */
586
+ groupEventsBySession(events) {
587
+ const sessionGroups = {};
588
+
589
+ events.forEach(event => {
590
+ const sessionId = event.session_id || event.data?.session_id || 'unknown';
591
+ if (!sessionGroups[sessionId]) {
592
+ sessionGroups[sessionId] = [];
593
+ }
594
+ sessionGroups[sessionId].push(event);
595
+ });
596
+
597
+ return sessionGroups;
598
+ }
599
+
600
+ /**
601
+ * Build a tree structure for a specific session
602
+ * @param {string} sessionId - Session identifier
603
+ * @param {Array} sessionEvents - Events for this session
604
+ */
605
+ buildSessionTree(sessionId, sessionEvents) {
606
+ console.log(`[HUD-VISUALIZER-DEBUG] Building session tree for ${sessionId} with ${sessionEvents.length} events`);
607
+
608
+ const sessionNodes = new Map(); // Track nodes created for this session
609
+ let sessionRootNode = null;
610
+
611
+ // Sort events chronologically within the session
612
+ const sortedEvents = sessionEvents.sort((a, b) => {
613
+ return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
614
+ });
615
+
616
+ console.log(`[HUD-VISUALIZER-DEBUG] Sorted ${sortedEvents.length} events chronologically`);
617
+
618
+ sortedEvents.forEach((event, index) => {
619
+ const nodeData = this.createNodeFromEvent(event, sessionId);
620
+ if (!nodeData) return;
621
+
622
+ // Add the node to visualization
623
+ this.addNode(nodeData.id, nodeData.type, nodeData.label, {
624
+ sessionId: sessionId,
625
+ timestamp: event.timestamp,
626
+ eventData: event,
627
+ isSessionRoot: nodeData.isSessionRoot
628
+ });
629
+
630
+ sessionNodes.set(nodeData.id, {
631
+ ...nodeData,
632
+ event: event,
633
+ index: index
634
+ });
635
+
636
+ // Track session root node
637
+ if (nodeData.isSessionRoot && !sessionRootNode) {
638
+ sessionRootNode = nodeData.id;
639
+ }
640
+
641
+ // Create relationships based on event context
642
+ this.createHierarchicalRelationships(nodeData.id, event, sessionNodes, sessionRootNode);
643
+ });
644
+ }
645
+
646
+ /**
647
+ * Create node data from an event
648
+ * @param {Object} event - Event object
649
+ * @param {string} sessionId - Session ID
650
+ * @returns {Object|null} Node data or null if event should be skipped
651
+ */
652
+ createNodeFromEvent(event, sessionId) {
653
+ const eventType = event.hook_event_name || event.type || '';
654
+ const subtype = event.subtype || '';
655
+ const timestamp = new Date(event.timestamp || Date.now());
656
+
657
+ console.log(`[HUD-VISUALIZER-DEBUG] Creating node from event: ${eventType}/${subtype} for session ${sessionId}`);
658
+
659
+ let nodeId, nodeType, label, isSessionRoot = false;
660
+
661
+ // Generate a unique timestamp-based ID suffix
662
+ const timestampId = timestamp.getTime();
663
+ const randomSuffix = Math.random().toString(36).substring(2, 7);
664
+
665
+ // Determine node type and create appropriate visualization
666
+ if (eventType === 'session' && subtype === 'started') {
667
+ // Session root node
668
+ nodeType = 'PM';
669
+ label = `Session ${sessionId.substring(0, 8)}...`;
670
+ nodeId = `session-${sessionId.replace(/[^a-zA-Z0-9]/g, '')}`;
671
+ isSessionRoot = true;
672
+
673
+ } else if (eventType === 'hook' && subtype === 'user_prompt') {
674
+ // User prompts are major workflow nodes
675
+ nodeType = 'PM';
676
+ const promptPreview = event.data?.prompt_preview || 'User Prompt';
677
+ label = promptPreview.length > 20 ? promptPreview.substring(0, 20) + '...' : promptPreview;
678
+ nodeId = `user-prompt-${timestampId}-${randomSuffix}`;
679
+
680
+ } else if (eventType === 'hook' && subtype === 'claude_response') {
681
+ // Claude responses
682
+ nodeType = 'PM';
683
+ label = 'Claude Response';
684
+ nodeId = `claude-response-${timestampId}-${randomSuffix}`;
685
+
686
+ } else if (eventType === 'hook' && subtype === 'pre_tool') {
687
+ // Tool calls - pre hook
688
+ nodeType = 'TOOL';
689
+ const toolName = event.data?.tool_name || 'Unknown Tool';
690
+ // Clean tool name for ID
691
+ const cleanToolName = toolName.replace(/[^a-zA-Z0-9]/g, '');
692
+ label = `${toolName}`;
693
+ nodeId = `tool-${cleanToolName}-${timestampId}-${randomSuffix}`;
694
+
695
+ } else if (eventType === 'agent' || event.data?.agent_type) {
696
+ // Agent operations
697
+ nodeType = 'AGENT';
698
+ const agentName = event.data?.agent_type || event.data?.agent_name || 'Agent';
699
+ // Clean agent name for ID
700
+ const cleanAgentName = agentName.replace(/[^a-zA-Z0-9]/g, '');
701
+ label = agentName;
702
+ nodeId = `agent-${cleanAgentName}-${timestampId}-${randomSuffix}`;
703
+
704
+ } else if (eventType === 'todo' || subtype.includes('todo')) {
705
+ // Todo operations
706
+ nodeType = 'TODO';
707
+ label = 'Todo Update';
708
+ nodeId = `todo-${timestampId}-${randomSuffix}`;
709
+
710
+ } else if (eventType === 'hook' && subtype === 'notification') {
711
+ // Skip notifications for cleaner visualization
712
+ return null;
713
+
714
+ } else if (eventType === 'log') {
715
+ // Skip log events for cleaner visualization unless they're errors
716
+ const level = event.data?.level || 'info';
717
+ if (!['error', 'critical'].includes(level)) {
718
+ return null;
719
+ }
720
+ nodeType = 'PM';
721
+ label = `${level.toUpperCase()} Log`;
722
+ nodeId = `log-${level}-${timestampId}-${randomSuffix}`;
723
+
724
+ } else {
725
+ // Generic event node
726
+ nodeType = 'PM';
727
+ const cleanEventType = eventType.replace(/[^a-zA-Z0-9]/g, '') || 'Event';
728
+ label = eventType || 'Event';
729
+ nodeId = `generic-${cleanEventType}-${timestampId}-${randomSuffix}`;
730
+ }
731
+
732
+ return {
733
+ id: nodeId,
734
+ type: nodeType,
735
+ label: label,
736
+ isSessionRoot: isSessionRoot
737
+ };
738
+ }
739
+
740
+ /**
741
+ * Create hierarchical relationships between nodes based on event context
742
+ * @param {string} nodeId - Current node ID
743
+ * @param {Object} event - Current event
744
+ * @param {Map} sessionNodes - Map of all nodes in this session
745
+ * @param {string} sessionRootNode - Root node ID for this session
746
+ */
747
+ createHierarchicalRelationships(nodeId, event, sessionNodes, sessionRootNode) {
748
+ const eventType = event.hook_event_name || event.type || '';
749
+ const subtype = event.subtype || '';
750
+
751
+ // Find appropriate parent node based on event context
752
+ let parentNodeId = null;
753
+
754
+ if (eventType === 'session' && subtype === 'started') {
755
+ // Session start nodes have no parent
756
+ return;
757
+
758
+ } else if (eventType === 'hook' && subtype === 'pre_tool') {
759
+ // Tool calls should connect to the most recent user prompt or agent
760
+ parentNodeId = this.findRecentParentNode(sessionNodes, ['user-prompt', 'agent'], nodeId);
761
+
762
+ } else if (eventType === 'hook' && subtype === 'claude_response') {
763
+ // Claude responses should connect to user prompts
764
+ parentNodeId = this.findRecentParentNode(sessionNodes, ['user-prompt'], nodeId);
765
+
766
+ } else if (eventType === 'agent') {
767
+ // Agents should connect to user prompts or other agents (delegation)
768
+ parentNodeId = this.findRecentParentNode(sessionNodes, ['user-prompt', 'agent'], nodeId);
769
+
770
+ } else if (eventType === 'todo') {
771
+ // Todos should connect to agents or user prompts
772
+ parentNodeId = this.findRecentParentNode(sessionNodes, ['agent', 'user-prompt'], nodeId);
773
+
774
+ } else {
775
+ // Default: connect to most recent significant node
776
+ parentNodeId = this.findRecentParentNode(sessionNodes, ['user-prompt', 'agent', 'session'], nodeId);
777
+ }
778
+
779
+ // If no specific parent found, connect to session root
780
+ if (!parentNodeId && sessionRootNode && nodeId !== sessionRootNode) {
781
+ parentNodeId = sessionRootNode;
782
+ }
783
+
784
+ // Create the edge if parent exists
785
+ if (parentNodeId && parentNodeId !== nodeId) {
786
+ this.addEdge(parentNodeId, nodeId);
787
+ }
788
+ }
789
+
790
+ /**
791
+ * Find the most recent parent node of specified types
792
+ * @param {Map} sessionNodes - Map of session nodes
793
+ * @param {Array} nodeTypes - Array of node type prefixes to search for
794
+ * @param {string} currentNodeId - Current node ID to exclude from search
795
+ * @returns {string|null} Parent node ID or null
796
+ */
797
+ findRecentParentNode(sessionNodes, nodeTypes, currentNodeId) {
798
+ const nodeEntries = Array.from(sessionNodes.entries()).reverse(); // Most recent first
799
+
800
+ for (const [nodeId, nodeData] of nodeEntries) {
801
+ if (nodeId === currentNodeId) continue; // Skip current node
802
+
803
+ // Check if this node matches any of the desired parent types
804
+ for (const typePrefix of nodeTypes) {
805
+ if (nodeId.startsWith(typePrefix)) {
806
+ return nodeId;
807
+ }
808
+ }
809
+ }
810
+
811
+ return null;
812
+ }
813
+
814
+ /**
815
+ * Process a socket event and add appropriate nodes/edges
816
+ * @param {Object} event - Socket event data
817
+ */
818
+ processEvent(event) {
819
+ if (!this.isActive) return;
820
+
821
+ // If libraries aren't loaded yet, store the event for later processing
822
+ if (!this.librariesLoaded || !this.cy) {
823
+ this.pendingEvents.push(event);
824
+ return;
825
+ }
826
+
827
+ this._processEventInternal(event);
828
+ }
829
+
830
+ /**
831
+ * Internal event processing (assumes libraries are loaded)
832
+ * @private
833
+ */
834
+ _processEventInternal(event) {
835
+ const eventType = event.hook_event_name || event.type || '';
836
+ const sessionId = event.session_id || 'unknown';
837
+ const timestamp = new Date(event.timestamp || Date.now());
838
+
839
+ // Create a unique node ID based on event type and data
840
+ let nodeId = `${eventType}-${timestamp.getTime()}`;
841
+ let nodeType = 'PM';
842
+ let label = eventType;
843
+
844
+ // Determine node type based on event
845
+ if (eventType.includes('tool_call')) {
846
+ nodeType = 'TOOL';
847
+ const toolName = event.data?.tool_name || 'Unknown Tool';
848
+ label = toolName;
849
+ nodeId = `tool-${toolName}-${timestamp.getTime()}`;
850
+ } else if (eventType.includes('agent')) {
851
+ nodeType = 'AGENT';
852
+ const agentName = event.data?.agent_name || 'Agent';
853
+ label = agentName;
854
+ nodeId = `agent-${agentName}-${timestamp.getTime()}`;
855
+ } else if (eventType.includes('todo')) {
856
+ nodeType = 'TODO';
857
+ label = 'Todo List';
858
+ nodeId = `todo-${timestamp.getTime()}`;
859
+ } else if (eventType.includes('user_prompt') || eventType.includes('claude_response')) {
860
+ nodeType = 'PM';
861
+ label = eventType.includes('user_prompt') ? 'User Prompt' : 'Claude Response';
862
+ nodeId = `pm-${label.replace(' ', '')}-${timestamp.getTime()}`;
863
+ }
864
+
865
+ // Add the node
866
+ this.addNode(nodeId, nodeType, label, {
867
+ sessionId: sessionId,
868
+ timestamp: timestamp.toISOString(),
869
+ eventData: event
870
+ });
871
+
872
+ // Add edges based on relationships
873
+ this.createEventRelationships(nodeId, event);
874
+ }
875
+
876
+ /**
877
+ * Add a node to the visualization
878
+ * @param {string} id - Unique node identifier
879
+ * @param {string} type - Node type (PM, AGENT, TOOL, TODO)
880
+ * @param {string} label - Node label
881
+ * @param {Object} data - Additional node data
882
+ */
883
+ addNode(id, type, label, data = {}) {
884
+ console.log(`[HUD-VISUALIZER-DEBUG] Adding node: ${id} (${type}) - ${label}`);
885
+
886
+ if (this.nodes.has(id)) {
887
+ console.log(`[HUD-VISUALIZER-DEBUG] Node ${id} already exists, skipping`);
888
+ return; // Node already exists
889
+ }
890
+
891
+ const nodeType = this.nodeTypes[type] || this.nodeTypes.PM;
892
+ const nodeData = {
893
+ id: id,
894
+ label: `${nodeType.icon} ${label}`,
895
+ type: type,
896
+ color: nodeType.color,
897
+ borderColor: this.darkenColor(nodeType.color, 20),
898
+ shape: nodeType.shape,
899
+ width: nodeType.width,
900
+ height: nodeType.height,
901
+ ...data
902
+ };
903
+
904
+ this.nodes.set(id, nodeData);
905
+
906
+ if (this.cy) {
907
+ const element = {
908
+ group: 'nodes',
909
+ data: nodeData,
910
+ classes: `${type.toLowerCase()}-node`
911
+ };
912
+
913
+ console.log(`[HUD-VISUALIZER-DEBUG] Adding node element to Cytoscape:`, element);
914
+ this.cy.add(element);
915
+ console.log(`[HUD-VISUALIZER-DEBUG] Node added successfully. Total nodes in cy: ${this.cy.nodes().length}`);
916
+ this.runLayout();
917
+ }
918
+ }
919
+
920
+ /**
921
+ * Add an edge between two nodes
922
+ * @param {string} sourceId - Source node ID
923
+ * @param {string} targetId - Target node ID
924
+ * @param {string} edgeId - Unique edge identifier
925
+ * @param {Object} data - Additional edge data
926
+ */
927
+ addEdge(sourceId, targetId, edgeId = null, data = {}) {
928
+ if (!sourceId || !targetId) {
929
+ console.warn(`[HUD-VISUALIZER-DEBUG] Cannot create edge: missing source (${sourceId}) or target (${targetId})`);
930
+ return;
931
+ }
932
+
933
+ if (sourceId === targetId) {
934
+ console.warn(`[HUD-VISUALIZER-DEBUG] Cannot create self-loop edge from ${sourceId} to itself`);
935
+ return;
936
+ }
937
+
938
+ if (!edgeId) {
939
+ edgeId = `edge-${sourceId}-to-${targetId}`;
940
+ }
941
+
942
+ if (this.cy) {
943
+ // Check if edge already exists
944
+ const existingEdge = this.cy.getElementById(edgeId);
945
+ if (existingEdge.length > 0) {
946
+ console.log(`[HUD-VISUALIZER-DEBUG] Edge ${edgeId} already exists, skipping`);
947
+ return;
948
+ }
949
+
950
+ // Check if nodes exist
951
+ const sourceNode = this.cy.getElementById(sourceId);
952
+ const targetNode = this.cy.getElementById(targetId);
953
+
954
+ if (sourceNode.length === 0) {
955
+ console.warn(`[HUD-VISUALIZER-DEBUG] Source node ${sourceId} does not exist, cannot create edge`);
956
+ return;
957
+ }
958
+
959
+ if (targetNode.length === 0) {
960
+ console.warn(`[HUD-VISUALIZER-DEBUG] Target node ${targetId} does not exist, cannot create edge`);
961
+ return;
962
+ }
963
+
964
+ const element = {
965
+ group: 'edges',
966
+ data: {
967
+ id: edgeId,
968
+ source: sourceId,
969
+ target: targetId,
970
+ ...data
971
+ }
972
+ };
973
+
974
+ console.log(`[HUD-VISUALIZER-DEBUG] Adding edge element to Cytoscape:`, element);
975
+
976
+ try {
977
+ this.cy.add(element);
978
+ console.log(`[HUD-VISUALIZER-DEBUG] Edge added successfully. Total edges in cy: ${this.cy.edges().length}`);
979
+ this.runLayout();
980
+ } catch (error) {
981
+ console.error(`[HUD-VISUALIZER-DEBUG] Failed to add edge ${edgeId}:`, error);
982
+ console.error(`[HUD-VISUALIZER-DEBUG] Element details:`, element);
983
+ }
984
+ }
985
+ }
986
+
987
+ /**
988
+ * Create relationships between events
989
+ * @param {string} nodeId - Current node ID
990
+ * @param {Object} event - Event data
991
+ */
992
+ createEventRelationships(nodeId, event) {
993
+ const eventType = event.hook_event_name || event.type || '';
994
+ const sessionId = event.session_id || 'unknown';
995
+
996
+ // Find parent nodes based on event relationships
997
+ const allNodeEntries = Array.from(this.nodes.entries());
998
+
999
+ // Tool call relationships
1000
+ if (eventType.includes('tool_call') && event.data?.tool_name) {
1001
+ // Connect tool calls to their invoking agent/PM nodes
1002
+ const parentNode = this.findParentNode(sessionId, ['PM', 'AGENT']);
1003
+ if (parentNode) {
1004
+ this.addEdge(parentNode, nodeId);
1005
+ return;
1006
+ }
1007
+ }
1008
+
1009
+ // Agent delegation relationships
1010
+ if (eventType.includes('agent') || event.data?.agent_name) {
1011
+ // Connect agents to PM nodes
1012
+ const pmNode = this.findParentNode(sessionId, ['PM']);
1013
+ if (pmNode) {
1014
+ this.addEdge(pmNode, nodeId);
1015
+ return;
1016
+ }
1017
+ }
1018
+
1019
+ // Todo relationships - connect to agent or PM nodes
1020
+ if (eventType.includes('todo')) {
1021
+ const parentNode = this.findParentNode(sessionId, ['AGENT', 'PM']);
1022
+ if (parentNode) {
1023
+ this.addEdge(parentNode, nodeId);
1024
+ return;
1025
+ }
1026
+ }
1027
+
1028
+ // Default sequential relationship
1029
+ const allNodes = Array.from(this.nodes.keys());
1030
+ const currentIndex = allNodes.indexOf(nodeId);
1031
+
1032
+ if (currentIndex > 0) {
1033
+ const previousNodeId = allNodes[currentIndex - 1];
1034
+ this.addEdge(previousNodeId, nodeId);
1035
+ }
1036
+ }
1037
+
1038
+ /**
1039
+ * Find a parent node of specific types for the same session
1040
+ * @param {string} sessionId - Session ID
1041
+ * @param {Array} nodeTypes - Array of node types to search for
1042
+ * @returns {string|null} - Parent node ID or null
1043
+ */
1044
+ findParentNode(sessionId, nodeTypes) {
1045
+ const nodeEntries = Array.from(this.nodes.entries()).reverse(); // Start from most recent
1046
+
1047
+ for (const [nodeId, nodeData] of nodeEntries) {
1048
+ if (nodeData.sessionId === sessionId && nodeTypes.includes(nodeData.type)) {
1049
+ return nodeId;
1050
+ }
1051
+ }
1052
+
1053
+ return null;
1054
+ }
1055
+
1056
+ /**
1057
+ * Highlight connected nodes
1058
+ * @param {Object} node - Cytoscape node object
1059
+ */
1060
+ highlightConnectedNodes(node) {
1061
+ if (!this.cy) return;
1062
+
1063
+ // Reset all node styles
1064
+ this.cy.nodes().style({
1065
+ 'opacity': 0.3
1066
+ });
1067
+
1068
+ this.cy.edges().style({
1069
+ 'opacity': 0.2
1070
+ });
1071
+
1072
+ // Highlight selected node and its neighborhood
1073
+ const neighborhood = node.neighborhood();
1074
+ node.style('opacity', 1);
1075
+ neighborhood.style('opacity', 1);
1076
+ }
1077
+
1078
+ /**
1079
+ * Reset layout
1080
+ */
1081
+ resetLayout() {
1082
+ if (this.cy) {
1083
+ this.cy.layout(this.layoutConfig).run();
1084
+ }
1085
+ }
1086
+
1087
+ /**
1088
+ * Center view
1089
+ */
1090
+ centerView() {
1091
+ if (this.cy) {
1092
+ this.cy.fit();
1093
+ this.cy.center();
1094
+ }
1095
+ }
1096
+
1097
+ /**
1098
+ * Run layout animation
1099
+ */
1100
+ runLayout() {
1101
+ console.log(`[HUD-VISUALIZER-DEBUG] runLayout called - isActive: ${this.isActive}, cy exists: ${!!this.cy}`);
1102
+ if (this.cy && this.isActive) {
1103
+ const nodeCount = this.cy.nodes().length;
1104
+ const edgeCount = this.cy.edges().length;
1105
+ console.log(`[HUD-VISUALIZER-DEBUG] Running layout with ${nodeCount} nodes and ${edgeCount} edges`);
1106
+
1107
+ // Check container dimensions before layout
1108
+ if (this.container) {
1109
+ const rect = this.container.getBoundingClientRect();
1110
+ console.log(`[HUD-VISUALIZER-DEBUG] Container dimensions before layout:`, {
1111
+ width: rect.width,
1112
+ height: rect.height,
1113
+ offsetWidth: this.container.offsetWidth,
1114
+ offsetHeight: this.container.offsetHeight
1115
+ });
1116
+ }
1117
+
1118
+ const layout = this.cy.layout(this.layoutConfig);
1119
+
1120
+ // Listen for layout completion
1121
+ layout.on('layoutstop', () => {
1122
+ console.log(`[HUD-VISUALIZER-DEBUG] Layout completed. Final node positions:`);
1123
+ this.cy.nodes().forEach((node, index) => {
1124
+ const position = node.position();
1125
+ const data = node.data();
1126
+ console.log(`[HUD-VISUALIZER-DEBUG] Node ${index + 1}: ${data.label} at (${position.x.toFixed(1)}, ${position.y.toFixed(1)})`);
1127
+ });
1128
+ });
1129
+
1130
+ layout.run();
1131
+ } else {
1132
+ console.log(`[HUD-VISUALIZER-DEBUG] Skipping layout - not active or no Cytoscape instance`);
1133
+ }
1134
+ }
1135
+
1136
+ /**
1137
+ * Clear all nodes and edges
1138
+ */
1139
+ clear() {
1140
+ console.log(`[HUD-VISUALIZER-DEBUG] Clearing HUD: ${this.nodes.size} nodes, ${this.pendingEvents.length} pending events`);
1141
+ this.nodes.clear();
1142
+ this.pendingEvents = [];
1143
+ if (this.cy) {
1144
+ const elementCount = this.cy.elements().length;
1145
+ try {
1146
+ this.cy.elements().remove();
1147
+ console.log(`[HUD-VISUALIZER-DEBUG] Removed ${elementCount} Cytoscape elements`);
1148
+ } catch (error) {
1149
+ console.error(`[HUD-VISUALIZER-DEBUG] Error clearing Cytoscape elements:`, error);
1150
+ // Try to destroy and recreate if clearing fails
1151
+ try {
1152
+ this.cy.destroy();
1153
+ this.cy = null;
1154
+ console.log(`[HUD-VISUALIZER-DEBUG] Destroyed Cytoscape instance due to clear error`);
1155
+ } catch (destroyError) {
1156
+ console.error(`[HUD-VISUALIZER-DEBUG] Error destroying Cytoscape:`, destroyError);
1157
+ }
1158
+ }
1159
+ }
1160
+ }
1161
+
1162
+ /**
1163
+ * Show loading indicator
1164
+ */
1165
+ showLoadingIndicator() {
1166
+ if (this.container) {
1167
+ this.container.innerHTML = `
1168
+ <div class="hud-loading-container">
1169
+ <div class="hud-loading-spinner"></div>
1170
+ <div class="hud-loading-text">Loading HUD visualization libraries...</div>
1171
+ <div class="hud-loading-progress" id="hud-loading-progress"></div>
1172
+ </div>
1173
+ `;
1174
+ }
1175
+ }
1176
+
1177
+ /**
1178
+ * Update loading progress
1179
+ */
1180
+ updateLoadingProgress(progress) {
1181
+ const progressElement = document.getElementById('hud-loading-progress');
1182
+ if (progressElement) {
1183
+ if (progress.error) {
1184
+ progressElement.innerHTML = `<span class="hud-error">❌ ${progress.message}</span>`;
1185
+ } else {
1186
+ progressElement.innerHTML = `
1187
+ <div class="hud-progress-bar">
1188
+ <div class="hud-progress-fill" style="width: ${(progress.current / progress.total) * 100}%"></div>
1189
+ </div>
1190
+ <div class="hud-progress-text">${progress.message} (${progress.current}/${progress.total})</div>
1191
+ `;
1192
+ }
1193
+ }
1194
+ }
1195
+
1196
+ /**
1197
+ * Hide loading indicator
1198
+ */
1199
+ hideLoadingIndicator() {
1200
+ if (this.container) {
1201
+ this.container.innerHTML = '';
1202
+ }
1203
+ }
1204
+
1205
+ /**
1206
+ * Show loading error
1207
+ */
1208
+ showLoadingError(message) {
1209
+ if (this.container) {
1210
+ this.container.innerHTML = `
1211
+ <div class="hud-error-container">
1212
+ <div class="hud-error-icon">⚠️</div>
1213
+ <div class="hud-error-text">Failed to load HUD libraries</div>
1214
+ <div class="hud-error-message">${message}</div>
1215
+ <button class="hud-retry-button" onclick="window.hudVisualizer && window.hudVisualizer.retryLoading()">
1216
+ Retry Loading
1217
+ </button>
1218
+ </div>
1219
+ `;
1220
+ }
1221
+ }
1222
+
1223
+ /**
1224
+ * Retry loading libraries (called from error UI)
1225
+ */
1226
+ retryLoading() {
1227
+ this.librariesLoaded = false;
1228
+ this.loadingPromise = null;
1229
+ this.activate();
1230
+ }
1231
+
1232
+ /**
1233
+ * Debug method to manually test HUD visualizer
1234
+ * Can be called from browser console: window.hudVisualizer.debugTest()
1235
+ */
1236
+ debugTest() {
1237
+ console.log('[HUD-VISUALIZER-DEBUG] debugTest() called manually');
1238
+ console.log('[HUD-VISUALIZER-DEBUG] Current state:', {
1239
+ isActive: this.isActive,
1240
+ librariesLoaded: this.librariesLoaded,
1241
+ hasCy: !!this.cy,
1242
+ hasContainer: !!this.container,
1243
+ nodeCount: this.nodes.size,
1244
+ pendingEventCount: this.pendingEvents.length,
1245
+ hasHUDLibraryLoader: !!window.HUDLibraryLoader
1246
+ });
1247
+
1248
+ // Test container
1249
+ if (this.container) {
1250
+ console.log('[HUD-VISUALIZER-DEBUG] Container info:', {
1251
+ id: this.container.id,
1252
+ className: this.container.className,
1253
+ offsetWidth: this.container.offsetWidth,
1254
+ offsetHeight: this.container.offsetHeight,
1255
+ innerHTML: this.container.innerHTML ? 'has content' : 'empty'
1256
+ });
1257
+ }
1258
+
1259
+ // Test library availability
1260
+ console.log('[HUD-VISUALIZER-DEBUG] Library availability:', {
1261
+ cytoscape: typeof window.cytoscape,
1262
+ dagre: typeof window.dagre,
1263
+ cytoscapeDagre: typeof window.cytoscapeDagre,
1264
+ HUDLibraryLoader: typeof window.HUDLibraryLoader
1265
+ });
1266
+
1267
+ return {
1268
+ isActive: this.isActive,
1269
+ librariesLoaded: this.librariesLoaded,
1270
+ hasCy: !!this.cy,
1271
+ containerFound: !!this.container
1272
+ };
1273
+ }
1274
+
1275
+ /**
1276
+ * Comprehensive debug method to identify blank screen issues
1277
+ * Can be called from browser console: window.hudVisualizer.debugBlankScreen()
1278
+ */
1279
+ debugBlankScreen() {
1280
+ console.log('[HUD-BLANK-SCREEN-DEBUG] =================================');
1281
+ console.log('[HUD-BLANK-SCREEN-DEBUG] COMPREHENSIVE BLANK SCREEN DEBUG');
1282
+ console.log('[HUD-BLANK-SCREEN-DEBUG] =================================');
1283
+
1284
+ // 1. Check basic state
1285
+ const basicState = {
1286
+ isActive: this.isActive,
1287
+ librariesLoaded: this.librariesLoaded,
1288
+ hasCy: !!this.cy,
1289
+ hasContainer: !!this.container,
1290
+ nodeCount: this.nodes.size,
1291
+ cytoscapeElementCount: this.cy ? this.cy.elements().length : 0
1292
+ };
1293
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 1. Basic State:', basicState);
1294
+
1295
+ // 2. Check container visibility and dimensions
1296
+ if (this.container) {
1297
+ const containerInfo = this.getContainerDebugInfo();
1298
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 2. Container Info:', containerInfo);
1299
+
1300
+ // Add background color to verify container is visible
1301
+ this.debugAddContainerBackground();
1302
+ } else {
1303
+ console.error('[HUD-BLANK-SCREEN-DEBUG] 2. Container not found!');
1304
+ return false;
1305
+ }
1306
+
1307
+ // 3. Check Cytoscape state
1308
+ if (this.cy) {
1309
+ const cytoscapeInfo = this.getCytoscapeDebugInfo();
1310
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 3. Cytoscape Info:', cytoscapeInfo);
1311
+ } else {
1312
+ console.error('[HUD-BLANK-SCREEN-DEBUG] 3. Cytoscape instance not found!');
1313
+ return false;
1314
+ }
1315
+
1316
+ // 4. Check node positions
1317
+ this.debugNodePositions();
1318
+
1319
+ // 5. Try manual rendering triggers
1320
+ this.debugManualRenderingTriggers();
1321
+
1322
+ // 6. Add test nodes if none exist
1323
+ if (this.cy && this.cy.nodes().length === 0) {
1324
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 6. No nodes found, adding test nodes...');
1325
+ this.debugAddTestNodes();
1326
+ }
1327
+
1328
+ // 7. Force zoom fit
1329
+ this.debugForceZoomFit();
1330
+
1331
+ console.log('[HUD-BLANK-SCREEN-DEBUG] Debug complete. Check visual results.');
1332
+ return true;
1333
+ }
1334
+
1335
+ /**
1336
+ * Get comprehensive container debug information
1337
+ */
1338
+ getContainerDebugInfo() {
1339
+ const rect = this.container.getBoundingClientRect();
1340
+ const computed = window.getComputedStyle(this.container);
1341
+
1342
+ return {
1343
+ id: this.container.id,
1344
+ className: this.container.className,
1345
+ // Dimensions
1346
+ offsetWidth: this.container.offsetWidth,
1347
+ offsetHeight: this.container.offsetHeight,
1348
+ clientWidth: this.container.clientWidth,
1349
+ clientHeight: this.container.clientHeight,
1350
+ scrollWidth: this.container.scrollWidth,
1351
+ scrollHeight: this.container.scrollHeight,
1352
+ // Bounding rect
1353
+ boundingRect: {
1354
+ width: rect.width,
1355
+ height: rect.height,
1356
+ top: rect.top,
1357
+ left: rect.left,
1358
+ bottom: rect.bottom,
1359
+ right: rect.right
1360
+ },
1361
+ // Computed styles that affect visibility
1362
+ computedStyles: {
1363
+ display: computed.display,
1364
+ visibility: computed.visibility,
1365
+ opacity: computed.opacity,
1366
+ position: computed.position,
1367
+ overflow: computed.overflow,
1368
+ zIndex: computed.zIndex,
1369
+ backgroundColor: computed.backgroundColor,
1370
+ transform: computed.transform
1371
+ },
1372
+ // Check if visible
1373
+ isVisible: rect.width > 0 && rect.height > 0 && computed.display !== 'none' && computed.visibility !== 'hidden',
1374
+ // Parent info
1375
+ parentElement: this.container.parentElement ? {
1376
+ tagName: this.container.parentElement.tagName,
1377
+ className: this.container.parentElement.className,
1378
+ offsetWidth: this.container.parentElement.offsetWidth,
1379
+ offsetHeight: this.container.parentElement.offsetHeight
1380
+ } : null
1381
+ };
1382
+ }
1383
+
1384
+ /**
1385
+ * Get comprehensive Cytoscape debug information
1386
+ */
1387
+ getCytoscapeDebugInfo() {
1388
+ const extent = this.cy.extent();
1389
+ const zoom = this.cy.zoom();
1390
+ const pan = this.cy.pan();
1391
+ const viewport = this.cy.viewport();
1392
+
1393
+ return {
1394
+ // Elements
1395
+ nodeCount: this.cy.nodes().length,
1396
+ edgeCount: this.cy.edges().length,
1397
+ elementCount: this.cy.elements().length,
1398
+ // Viewport
1399
+ zoom: zoom,
1400
+ pan: pan,
1401
+ extent: extent,
1402
+ viewport: viewport,
1403
+ // Container
1404
+ containerWidth: this.cy.width(),
1405
+ containerHeight: this.cy.height(),
1406
+ // Check if initialized
1407
+ isInitialized: this.cy.scratch('_cytoscape-initialized') !== undefined,
1408
+ // Renderer info
1409
+ renderer: this.cy.renderer() ? {
1410
+ name: this.cy.renderer().name,
1411
+ options: this.cy.renderer().options
1412
+ } : null
1413
+ };
1414
+ }
1415
+
1416
+ /**
1417
+ * Debug node positions to check if they're outside viewport
1418
+ */
1419
+ debugNodePositions() {
1420
+ if (!this.cy || this.cy.nodes().length === 0) {
1421
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 4. No nodes to check positions');
1422
+ return;
1423
+ }
1424
+
1425
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 4. Node Positions:');
1426
+ const nodes = this.cy.nodes();
1427
+ const extent = this.cy.extent();
1428
+ const viewport = this.cy.viewport();
1429
+
1430
+ console.log('[HUD-BLANK-SCREEN-DEBUG] Viewport extent:', extent);
1431
+ console.log('[HUD-BLANK-SCREEN-DEBUG] Current viewport:', viewport);
1432
+
1433
+ nodes.forEach((node, index) => {
1434
+ const position = node.position();
1435
+ const data = node.data();
1436
+ const boundingBox = node.boundingBox();
1437
+
1438
+ console.log(`[HUD-BLANK-SCREEN-DEBUG] Node ${index + 1}:`, {
1439
+ id: data.id,
1440
+ label: data.label,
1441
+ position: position,
1442
+ boundingBox: boundingBox,
1443
+ isVisible: node.visible(),
1444
+ opacity: node.style('opacity'),
1445
+ width: node.style('width'),
1446
+ height: node.style('height')
1447
+ });
1448
+ });
1449
+ }
1450
+
1451
+ /**
1452
+ * Add background color to container to verify it's visible
1453
+ */
1454
+ debugAddContainerBackground() {
1455
+ if (this.container) {
1456
+ this.container.style.backgroundColor = '#ff000020'; // Light red background
1457
+ this.container.style.border = '2px solid #ff0000'; // Red border
1458
+ this.container.style.minHeight = '400px'; // Ensure minimum height
1459
+ console.log('[HUD-BLANK-SCREEN-DEBUG] Added red background and border to container for visibility test');
1460
+ }
1461
+ }
1462
+
1463
+ /**
1464
+ * Manual rendering triggers to force Cytoscape to render
1465
+ */
1466
+ debugManualRenderingTriggers() {
1467
+ if (!this.cy) {
1468
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 5. No Cytoscape instance for manual rendering');
1469
+ return;
1470
+ }
1471
+
1472
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 5. Triggering manual rendering operations...');
1473
+
1474
+ try {
1475
+ // Force resize
1476
+ console.log('[HUD-BLANK-SCREEN-DEBUG] - Forcing resize...');
1477
+ this.cy.resize();
1478
+
1479
+ // Force redraw
1480
+ console.log('[HUD-BLANK-SCREEN-DEBUG] - Forcing redraw...');
1481
+ this.cy.forceRender();
1482
+
1483
+ // Force layout
1484
+ if (this.cy.nodes().length > 0) {
1485
+ console.log('[HUD-BLANK-SCREEN-DEBUG] - Running layout...');
1486
+ this.cy.layout(this.layoutConfig).run();
1487
+ }
1488
+
1489
+ // Force viewport update
1490
+ console.log('[HUD-BLANK-SCREEN-DEBUG] - Updating viewport...');
1491
+ this.cy.viewport({
1492
+ zoom: this.cy.zoom(),
1493
+ pan: this.cy.pan()
1494
+ });
1495
+
1496
+ console.log('[HUD-BLANK-SCREEN-DEBUG] Manual rendering triggers completed');
1497
+ } catch (error) {
1498
+ console.error('[HUD-BLANK-SCREEN-DEBUG] Error during manual rendering:', error);
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * Add test nodes to verify Cytoscape is working
1504
+ */
1505
+ debugAddTestNodes() {
1506
+ if (!this.cy) return;
1507
+
1508
+ console.log('[HUD-BLANK-SCREEN-DEBUG] Adding test nodes...');
1509
+
1510
+ try {
1511
+ // Clear existing elements
1512
+ this.cy.elements().remove();
1513
+
1514
+ // Add test nodes
1515
+ const testNodes = [
1516
+ {
1517
+ group: 'nodes',
1518
+ data: {
1519
+ id: 'test-node-1',
1520
+ label: '🤖 Test Node 1',
1521
+ color: '#48bb78',
1522
+ borderColor: '#38a169',
1523
+ shape: 'rectangle',
1524
+ width: 120,
1525
+ height: 40
1526
+ },
1527
+ classes: 'pm-node'
1528
+ },
1529
+ {
1530
+ group: 'nodes',
1531
+ data: {
1532
+ id: 'test-node-2',
1533
+ label: '🔧 Test Node 2',
1534
+ color: '#4299e1',
1535
+ borderColor: '#3182ce',
1536
+ shape: 'diamond',
1537
+ width: 80,
1538
+ height: 50
1539
+ },
1540
+ classes: 'tool-node'
1541
+ },
1542
+ {
1543
+ group: 'nodes',
1544
+ data: {
1545
+ id: 'test-node-3',
1546
+ label: '📝 Test Node 3',
1547
+ color: '#e53e3e',
1548
+ borderColor: '#c53030',
1549
+ shape: 'triangle',
1550
+ width: 70,
1551
+ height: 40
1552
+ },
1553
+ classes: 'todo-node'
1554
+ }
1555
+ ];
1556
+
1557
+ // Add test edges
1558
+ const testEdges = [
1559
+ {
1560
+ group: 'edges',
1561
+ data: {
1562
+ id: 'test-edge-1',
1563
+ source: 'test-node-1',
1564
+ target: 'test-node-2'
1565
+ }
1566
+ },
1567
+ {
1568
+ group: 'edges',
1569
+ data: {
1570
+ id: 'test-edge-2',
1571
+ source: 'test-node-2',
1572
+ target: 'test-node-3'
1573
+ }
1574
+ }
1575
+ ];
1576
+
1577
+ // Add elements to Cytoscape
1578
+ this.cy.add(testNodes);
1579
+ this.cy.add(testEdges);
1580
+
1581
+ console.log('[HUD-BLANK-SCREEN-DEBUG] Added 3 test nodes and 2 test edges');
1582
+
1583
+ // Update our internal nodes map
1584
+ testNodes.forEach(nodeElement => {
1585
+ this.nodes.set(nodeElement.data.id, nodeElement.data);
1586
+ });
1587
+
1588
+ // Run layout
1589
+ this.runLayout();
1590
+
1591
+ } catch (error) {
1592
+ console.error('[HUD-BLANK-SCREEN-DEBUG] Error adding test nodes:', error);
1593
+ }
1594
+ }
1595
+
1596
+ /**
1597
+ * Force zoom fit after layout with multiple attempts
1598
+ */
1599
+ debugForceZoomFit() {
1600
+ if (!this.cy) return;
1601
+
1602
+ console.log('[HUD-BLANK-SCREEN-DEBUG] 7. Forcing zoom fit...');
1603
+
1604
+ const attemptZoomFit = (attemptNumber) => {
1605
+ try {
1606
+ console.log(`[HUD-BLANK-SCREEN-DEBUG] Zoom fit attempt ${attemptNumber}...`);
1607
+
1608
+ // Get current state before fit
1609
+ const beforeZoom = this.cy.zoom();
1610
+ const beforePan = this.cy.pan();
1611
+ const elements = this.cy.elements();
1612
+
1613
+ console.log('[HUD-BLANK-SCREEN-DEBUG] Before fit:', {
1614
+ zoom: beforeZoom,
1615
+ pan: beforePan,
1616
+ elementCount: elements.length
1617
+ });
1618
+
1619
+ if (elements.length > 0) {
1620
+ // Try fit with specific options
1621
+ this.cy.fit(elements, 50); // 50px padding
1622
+
1623
+ // Get state after fit
1624
+ const afterZoom = this.cy.zoom();
1625
+ const afterPan = this.cy.pan();
1626
+
1627
+ console.log('[HUD-BLANK-SCREEN-DEBUG] After fit:', {
1628
+ zoom: afterZoom,
1629
+ pan: afterPan,
1630
+ changed: beforeZoom !== afterZoom || beforePan.x !== afterPan.x || beforePan.y !== afterPan.y
1631
+ });
1632
+
1633
+ // Force center
1634
+ this.cy.center(elements);
1635
+
1636
+ } else {
1637
+ console.log('[HUD-BLANK-SCREEN-DEBUG] No elements to fit');
1638
+ }
1639
+
1640
+ } catch (error) {
1641
+ console.error(`[HUD-BLANK-SCREEN-DEBUG] Zoom fit attempt ${attemptNumber} failed:`, error);
1642
+ }
1643
+ };
1644
+
1645
+ // Multiple attempts with delays
1646
+ attemptZoomFit(1);
1647
+ setTimeout(() => attemptZoomFit(2), 100);
1648
+ setTimeout(() => attemptZoomFit(3), 500);
1649
+ setTimeout(() => attemptZoomFit(4), 1000);
1650
+ }
1651
+
1652
+ /**
1653
+ * Quick test to draw a simple shape to verify Cytoscape canvas is working
1654
+ */
1655
+ debugDrawSimpleShape() {
1656
+ if (!this.cy) {
1657
+ console.log('[HUD-CANVAS-TEST] No Cytoscape instance');
1658
+ return false;
1659
+ }
1660
+
1661
+ console.log('[HUD-CANVAS-TEST] Testing Cytoscape canvas rendering...');
1662
+
1663
+ try {
1664
+ // Clear everything
1665
+ this.cy.elements().remove();
1666
+
1667
+ // Add a single, simple node at center
1668
+ this.cy.add({
1669
+ group: 'nodes',
1670
+ data: {
1671
+ id: 'canvas-test',
1672
+ label: '✅ CANVAS TEST',
1673
+ color: '#ff0000',
1674
+ borderColor: '#000000',
1675
+ width: 200,
1676
+ height: 100,
1677
+ shape: 'rectangle'
1678
+ },
1679
+ position: { x: 200, y: 200 } // Fixed position
1680
+ });
1681
+
1682
+ // Force immediate render
1683
+ this.cy.forceRender();
1684
+
1685
+ // Zoom to fit this single node
1686
+ this.cy.fit(this.cy.$('#canvas-test'), 50);
1687
+
1688
+ console.log('[HUD-CANVAS-TEST] Canvas test node added and positioned');
1689
+ console.log('[HUD-CANVAS-TEST] If you see a red rectangle with "CANVAS TEST", rendering works!');
1690
+
1691
+ return true;
1692
+
1693
+ } catch (error) {
1694
+ console.error('[HUD-CANVAS-TEST] Canvas test failed:', error);
1695
+ return false;
1696
+ }
1697
+ }
1698
+
1699
+ /**
1700
+ * Utility function to darken a color
1701
+ * @param {string} color - Hex color
1702
+ * @param {number} percent - Percentage to darken
1703
+ * @returns {string} - Darkened hex color
1704
+ */
1705
+ darkenColor(color, percent) {
1706
+ const num = parseInt(color.replace("#", ""), 16);
1707
+ const amt = Math.round(2.55 * percent);
1708
+ const R = (num >> 16) - amt;
1709
+ const G = (num >> 8 & 0x00FF) - amt;
1710
+ const B = (num & 0x0000FF) - amt;
1711
+ return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
1712
+ (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
1713
+ (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
1714
+ }
1715
+ }
1716
+
1717
+ // Export for use in dashboard
1718
+ window.HUDVisualizer = HUDVisualizer;