claude-mpm 4.2.39__py3-none-any.whl → 4.2.42__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 (39) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +114 -1
  3. claude_mpm/agents/BASE_OPS.md +156 -1
  4. claude_mpm/agents/INSTRUCTIONS.md +120 -11
  5. claude_mpm/agents/WORKFLOW.md +160 -10
  6. claude_mpm/agents/templates/agentic-coder-optimizer.json +17 -12
  7. claude_mpm/agents/templates/react_engineer.json +217 -0
  8. claude_mpm/agents/templates/web_qa.json +40 -4
  9. claude_mpm/cli/__init__.py +3 -5
  10. claude_mpm/commands/mpm-browser-monitor.md +370 -0
  11. claude_mpm/commands/mpm-monitor.md +177 -0
  12. claude_mpm/dashboard/static/built/components/code-viewer.js +1076 -2
  13. claude_mpm/dashboard/static/built/components/ui-state-manager.js +465 -2
  14. claude_mpm/dashboard/static/css/dashboard.css +2 -0
  15. claude_mpm/dashboard/static/js/browser-console-monitor.js +495 -0
  16. claude_mpm/dashboard/static/js/components/browser-log-viewer.js +763 -0
  17. claude_mpm/dashboard/static/js/components/code-viewer.js +931 -340
  18. claude_mpm/dashboard/static/js/components/diff-viewer.js +891 -0
  19. claude_mpm/dashboard/static/js/components/file-change-tracker.js +443 -0
  20. claude_mpm/dashboard/static/js/components/file-change-viewer.js +690 -0
  21. claude_mpm/dashboard/static/js/components/ui-state-manager.js +307 -19
  22. claude_mpm/dashboard/static/js/socket-client.js +2 -2
  23. claude_mpm/dashboard/static/test-browser-monitor.html +470 -0
  24. claude_mpm/dashboard/templates/index.html +62 -99
  25. claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
  26. claude_mpm/services/monitor/daemon.py +69 -36
  27. claude_mpm/services/monitor/daemon_manager.py +186 -29
  28. claude_mpm/services/monitor/handlers/browser.py +451 -0
  29. claude_mpm/services/monitor/server.py +272 -5
  30. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/METADATA +1 -1
  31. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/RECORD +35 -29
  32. claude_mpm/agents/templates/agentic-coder-optimizer.md +0 -44
  33. claude_mpm/agents/templates/agentic_coder_optimizer.json +0 -238
  34. claude_mpm/agents/templates/test-non-mpm.json +0 -20
  35. claude_mpm/dashboard/static/dist/components/code-viewer.js +0 -2
  36. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/WHEEL +0 -0
  37. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/entry_points.txt +0 -0
  38. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/licenses/LICENSE +0 -0
  39. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,763 @@
1
+ /**
2
+ * Browser Log Viewer Component - VERSION 3.0 EXTREME - MAXIMUM HOOK BLOCKING
3
+ * Displays real-time browser console logs from monitored browser sessions
4
+ * EXTREME NUCLEAR ISOLATION FROM HOOK EVENTS
5
+ */
6
+
7
+ class BrowserLogViewer {
8
+ constructor(container) {
9
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] 🚀🚀🚀 CONSTRUCTOR CALLED');
10
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] Container ID:', container?.id);
11
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] Container parent:', container?.parentElement?.id);
12
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] Call stack:', new Error().stack);
13
+
14
+ this.container = container;
15
+ this.logs = [];
16
+ this.filters = {
17
+ browserId: 'all',
18
+ level: 'all'
19
+ };
20
+ this.autoRefresh = true;
21
+ this.autoScroll = true;
22
+ this.refreshInterval = null;
23
+ this.maxLogs = 1000;
24
+ this.browserSessions = new Set();
25
+ this.VERSION = '3.0-EXTREME-NUCLEAR';
26
+
27
+ // EXTREME: Mark territory immediately
28
+ if (container) {
29
+ container.setAttribute('data-browser-logs-extreme', 'active');
30
+ container.setAttribute('data-version', this.VERSION);
31
+ }
32
+
33
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] STARTING EXTREME INIT');
34
+ this.init();
35
+ }
36
+
37
+ init() {
38
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] 🚨 INIT - EXTREME NUCLEAR CLEARING');
39
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] Container before clear:', this.container?.innerHTML?.substring(0, 100));
40
+
41
+ // EXTREME NUCLEAR: Multiple clearing passes
42
+ for (let i = 0; i < 5; i++) {
43
+ this.container.innerHTML = '';
44
+ this.container.textContent = '';
45
+ while (this.container.firstChild) {
46
+ this.container.removeChild(this.container.firstChild);
47
+ }
48
+ }
49
+
50
+ // Remove ALL classes and attributes that might cause confusion
51
+ this.container.className = '';
52
+ const attributesToRemove = ['data-owner', 'data-events', 'data-component'];
53
+ attributesToRemove.forEach(attr => this.container.removeAttribute(attr));
54
+
55
+ // Now mark it as EXCLUSIVELY OURS with EXTREME markers
56
+ this.container.setAttribute('data-owner', 'BrowserLogViewer-v3-EXTREME-NUCLEAR');
57
+ this.container.setAttribute('data-browser-logs-only', 'true');
58
+ this.container.setAttribute('data-no-hooks', 'ABSOLUTELY');
59
+ this.container.classList.add('browser-log-viewer-container-v3-extreme');
60
+ this.container.style.background = '#ffffff';
61
+ this.container.style.border = '3px solid lime'; // Visual indicator
62
+
63
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] RENDERING EXTREME CLEAN INTERFACE');
64
+ this.render();
65
+ this.attachEventListeners();
66
+ this.startAutoRefresh();
67
+
68
+ // Set up EXTREME container protection
69
+ this.protectContainer();
70
+
71
+ console.error('[BROWSER-LOG-VIEWER v3.0 EXTREME] ✅ INIT COMPLETE - CONTAINER SECURED');
72
+
73
+ // Listen for real-time browser console events from the browser handler
74
+ if (window.socket) {
75
+ // Listen ONLY for browser console events with proper validation
76
+ // These events come from the injected browser monitoring script
77
+ window.socket.on('browser_log', (logEntry) => {
78
+ // Extra validation: ensure this is truly a browser log
79
+ if (logEntry && logEntry.browser_id) {
80
+ console.log('[BrowserLogViewer] Received valid browser_log event:', logEntry);
81
+ this.addLog(logEntry);
82
+ } else {
83
+ console.warn('[BrowserLogViewer] Rejected invalid browser_log event (no browser_id):', logEntry);
84
+ }
85
+ });
86
+
87
+ // Also listen for dashboard:browser:console events from the browser handler
88
+ window.socket.on('dashboard:browser:console', (logEntry) => {
89
+ // Extra validation: ensure this is truly a browser log
90
+ if (logEntry && logEntry.browser_id) {
91
+ console.log('[BrowserLogViewer] Received valid dashboard:browser:console event:', logEntry);
92
+ this.addLog(logEntry);
93
+ } else {
94
+ console.warn('[BrowserLogViewer] Rejected invalid dashboard:browser:console event (no browser_id):', logEntry);
95
+ }
96
+ });
97
+ }
98
+ }
99
+
100
+ render() {
101
+ console.error('[BROWSER-LOG-VIEWER v2.0] RENDER CALLED - FORCING CLEAN CONTENT');
102
+
103
+ // NUCLEAR: Clear container AGAIN before rendering
104
+ this.container.innerHTML = '';
105
+
106
+ // Add version marker at the top
107
+ this.container.innerHTML = `
108
+ <!-- BROWSER-LOG-VIEWER VERSION 2.0 NUCLEAR - NO HOOKS ALLOWED -->
109
+ <div class="browser-log-viewer" data-version="2.0-NUCLEAR">
110
+ <div class="browser-log-controls">
111
+ <div class="filter-group">
112
+ <label for="browser-filter">Browser:</label>
113
+ <select id="browser-filter" class="filter-select">
114
+ <option value="all">All Browsers</option>
115
+ </select>
116
+ </div>
117
+
118
+ <div class="filter-group">
119
+ <label for="level-filter">Level:</label>
120
+ <select id="level-filter" class="filter-select">
121
+ <option value="all">All Levels</option>
122
+ <option value="ERROR">Error</option>
123
+ <option value="WARN">Warning</option>
124
+ <option value="INFO">Info</option>
125
+ <option value="DEBUG">Debug</option>
126
+ </select>
127
+ </div>
128
+
129
+ <div class="control-buttons">
130
+ <button id="refresh-logs" class="btn btn-secondary" title="Refresh logs">
131
+ <i class="fas fa-sync"></i> Refresh
132
+ </button>
133
+
134
+ <button id="clear-view" class="btn btn-secondary" title="Clear view">
135
+ <i class="fas fa-eraser"></i> Clear View
136
+ </button>
137
+
138
+ <button id="toggle-auto-refresh" class="btn btn-primary active" title="Toggle auto-refresh">
139
+ <i class="fas fa-sync-alt"></i> Auto-Refresh
140
+ </button>
141
+
142
+ <button id="toggle-auto-scroll" class="btn btn-primary active" title="Toggle auto-scroll">
143
+ <i class="fas fa-arrow-down"></i> Auto-Scroll
144
+ </button>
145
+
146
+ <button id="export-logs" class="btn btn-secondary" title="Export logs">
147
+ <i class="fas fa-download"></i> Export
148
+ </button>
149
+ </div>
150
+
151
+ <div class="log-stats">
152
+ <span id="log-count">0 logs</span>
153
+ <span id="session-count">0 sessions</span>
154
+ </div>
155
+ </div>
156
+
157
+ <div class="browser-log-container" id="browser-log-container">
158
+ <div class="log-entries" id="log-entries">
159
+ <div class="empty-state">
160
+ <i class="fas fa-globe fa-3x"></i>
161
+ <h3 style="color: #28a745; font-weight: bold;">BROWSER LOGS ONLY - VERSION 2.0</h3>
162
+ <p style="color: red; font-weight: bold;">⚠️ HOOK EVENTS ARE BLOCKED HERE ⚠️</p>
163
+ <p>No browser console logs yet</p>
164
+ <p class="text-muted">
165
+ Browser console logs will appear here when the monitoring script is injected.<br>
166
+ Use <code>/mpm-browser-monitor start</code> to begin monitoring.
167
+ </p>
168
+ <p class="text-muted" style="font-size: 11px; margin-top: 10px;">
169
+ This tab shows console.log, console.error, and console.warn from monitored browsers only.<br>
170
+ <strong>Hook events ([hook]) are FORCEFULLY BLOCKED and will NEVER appear here.</strong>
171
+ </p>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ `;
177
+
178
+ this.addStyles();
179
+ }
180
+
181
+ protectContainer() {
182
+ console.error('[BROWSER-LOG-VIEWER v2.0] ENABLING NUCLEAR PROTECTION');
183
+
184
+ // NUCLEAR PROTECTION: Monitor and DESTROY any contamination
185
+ const observer = new MutationObserver((mutations) => {
186
+ mutations.forEach((mutation) => {
187
+ if (mutation.type === 'childList') {
188
+ mutation.addedNodes.forEach((node) => {
189
+ if (node.nodeType === 1) { // Element node
190
+ // Check for ANY contamination
191
+ const nodeHTML = node.outerHTML || '';
192
+ const isContaminated =
193
+ node.classList && (node.classList.contains('event-item') ||
194
+ node.classList.contains('events-list')) ||
195
+ nodeHTML.includes('[hook]') ||
196
+ nodeHTML.includes('hook.pre_tool') ||
197
+ nodeHTML.includes('hook.post_tool');
198
+
199
+ if (isContaminated) {
200
+ console.error('[BROWSER-LOG-VIEWER v2.0] 🚨 NUCLEAR ALERT: HOOK CONTAMINATION DETECTED! DESTROYING!');
201
+ console.error('[BROWSER-LOG-VIEWER v2.0] Contaminated node:', node);
202
+ node.remove();
203
+
204
+ // NUCLEAR RESPONSE: Complete re-render
205
+ this.container.innerHTML = '';
206
+ this.render();
207
+ console.error('[BROWSER-LOG-VIEWER v2.0] ✅ CONTAMINATION ELIMINATED - CONTAINER RESTORED');
208
+ }
209
+ }
210
+ });
211
+ }
212
+ });
213
+ });
214
+
215
+ // Start observing with AGGRESSIVE settings
216
+ observer.observe(this.container, {
217
+ childList: true,
218
+ subtree: true,
219
+ characterData: true, // Also watch for text changes
220
+ attributes: true // Watch for attribute changes
221
+ });
222
+
223
+ console.error('[BROWSER-LOG-VIEWER v2.0] ✅ NUCLEAR PROTECTION ACTIVE');
224
+ }
225
+
226
+ addStyles() {
227
+ if (!document.getElementById('browser-log-viewer-styles')) {
228
+ const style = document.createElement('style');
229
+ style.id = 'browser-log-viewer-styles';
230
+ style.textContent = `
231
+ .browser-log-viewer {
232
+ height: 100%;
233
+ display: flex;
234
+ flex-direction: column;
235
+ }
236
+
237
+ .browser-log-controls {
238
+ padding: 15px;
239
+ background: #f8f9fa;
240
+ border-bottom: 1px solid #dee2e6;
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 15px;
244
+ flex-wrap: wrap;
245
+ }
246
+
247
+ .filter-group {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 8px;
251
+ }
252
+
253
+ .filter-group label {
254
+ font-weight: 500;
255
+ margin: 0;
256
+ }
257
+
258
+ .filter-select {
259
+ padding: 5px 10px;
260
+ border: 1px solid #ced4da;
261
+ border-radius: 4px;
262
+ background: white;
263
+ min-width: 120px;
264
+ }
265
+
266
+ .control-buttons {
267
+ display: flex;
268
+ gap: 8px;
269
+ margin-left: auto;
270
+ }
271
+
272
+ .btn {
273
+ padding: 6px 12px;
274
+ border: 1px solid transparent;
275
+ border-radius: 4px;
276
+ cursor: pointer;
277
+ font-size: 14px;
278
+ display: inline-flex;
279
+ align-items: center;
280
+ gap: 5px;
281
+ transition: all 0.2s;
282
+ }
283
+
284
+ .btn-primary {
285
+ background: #007bff;
286
+ color: white;
287
+ border-color: #007bff;
288
+ }
289
+
290
+ .btn-primary:hover {
291
+ background: #0056b3;
292
+ border-color: #0056b3;
293
+ }
294
+
295
+ .btn-primary.active {
296
+ background: #28a745;
297
+ border-color: #28a745;
298
+ }
299
+
300
+ .btn-secondary {
301
+ background: #6c757d;
302
+ color: white;
303
+ border-color: #6c757d;
304
+ }
305
+
306
+ .btn-secondary:hover {
307
+ background: #545b62;
308
+ border-color: #545b62;
309
+ }
310
+
311
+ .log-stats {
312
+ display: flex;
313
+ gap: 15px;
314
+ font-size: 14px;
315
+ color: #6c757d;
316
+ }
317
+
318
+ .browser-log-container {
319
+ flex: 1;
320
+ overflow-y: auto;
321
+ background: white;
322
+ padding: 10px;
323
+ }
324
+
325
+ .log-entries {
326
+ font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
327
+ font-size: 12px;
328
+ line-height: 1.5;
329
+ }
330
+
331
+ .log-entry {
332
+ padding: 8px 12px;
333
+ margin-bottom: 4px;
334
+ border-left: 3px solid #dee2e6;
335
+ background: #f8f9fa;
336
+ border-radius: 0 4px 4px 0;
337
+ display: flex;
338
+ align-items: flex-start;
339
+ gap: 10px;
340
+ word-break: break-word;
341
+ }
342
+
343
+ .log-entry.error {
344
+ border-left-color: #dc3545;
345
+ background: #f8d7da;
346
+ }
347
+
348
+ .log-entry.warn {
349
+ border-left-color: #ffc107;
350
+ background: #fff3cd;
351
+ }
352
+
353
+ .log-entry.info {
354
+ border-left-color: #17a2b8;
355
+ background: #d1ecf1;
356
+ }
357
+
358
+ .log-entry.debug {
359
+ border-left-color: #6c757d;
360
+ background: #e2e3e5;
361
+ }
362
+
363
+ .log-timestamp {
364
+ color: #6c757d;
365
+ white-space: nowrap;
366
+ flex-shrink: 0;
367
+ }
368
+
369
+ .log-level {
370
+ font-weight: bold;
371
+ text-transform: uppercase;
372
+ padding: 2px 6px;
373
+ border-radius: 3px;
374
+ font-size: 10px;
375
+ flex-shrink: 0;
376
+ }
377
+
378
+ .log-level.error {
379
+ background: #dc3545;
380
+ color: white;
381
+ }
382
+
383
+ .log-level.warn {
384
+ background: #ffc107;
385
+ color: #212529;
386
+ }
387
+
388
+ .log-level.info {
389
+ background: #17a2b8;
390
+ color: white;
391
+ }
392
+
393
+ .log-level.debug {
394
+ background: #6c757d;
395
+ color: white;
396
+ }
397
+
398
+ .log-browser {
399
+ color: #007bff;
400
+ font-size: 10px;
401
+ flex-shrink: 0;
402
+ }
403
+
404
+ .log-message {
405
+ flex: 1;
406
+ color: #212529;
407
+ }
408
+
409
+ .log-url {
410
+ color: #6c757d;
411
+ font-size: 10px;
412
+ margin-top: 4px;
413
+ }
414
+
415
+ .empty-state {
416
+ text-align: center;
417
+ padding: 60px 20px;
418
+ color: #6c757d;
419
+ }
420
+
421
+ .empty-state i {
422
+ color: #dee2e6;
423
+ margin-bottom: 20px;
424
+ }
425
+
426
+ .empty-state p {
427
+ margin: 10px 0;
428
+ }
429
+
430
+ .text-muted {
431
+ color: #adb5bd !important;
432
+ }
433
+ `;
434
+ document.head.appendChild(style);
435
+ }
436
+ }
437
+
438
+ attachEventListeners() {
439
+ // Browser filter
440
+ document.getElementById('browser-filter').addEventListener('change', (e) => {
441
+ this.filters.browserId = e.target.value;
442
+ this.renderLogs();
443
+ });
444
+
445
+ // Level filter
446
+ document.getElementById('level-filter').addEventListener('change', (e) => {
447
+ this.filters.level = e.target.value;
448
+ this.renderLogs();
449
+ });
450
+
451
+ // Refresh button - just clears and re-renders in real-time mode
452
+ document.getElementById('refresh-logs').addEventListener('click', () => {
453
+ console.log('[BrowserLogViewer] Refresh clicked - re-rendering current logs');
454
+ this.renderLogs();
455
+ });
456
+
457
+ // Clear view button
458
+ document.getElementById('clear-view').addEventListener('click', () => {
459
+ this.logs = [];
460
+ this.renderLogs();
461
+ });
462
+
463
+ // Auto-refresh toggle
464
+ document.getElementById('toggle-auto-refresh').addEventListener('click', (e) => {
465
+ this.autoRefresh = !this.autoRefresh;
466
+ e.currentTarget.classList.toggle('active', this.autoRefresh);
467
+
468
+ if (this.autoRefresh) {
469
+ this.startAutoRefresh();
470
+ } else {
471
+ this.stopAutoRefresh();
472
+ }
473
+ });
474
+
475
+ // Auto-scroll toggle
476
+ document.getElementById('toggle-auto-scroll').addEventListener('click', (e) => {
477
+ this.autoScroll = !this.autoScroll;
478
+ e.currentTarget.classList.toggle('active', this.autoScroll);
479
+ });
480
+
481
+ // Export logs
482
+ document.getElementById('export-logs').addEventListener('click', () => {
483
+ this.exportLogs();
484
+ });
485
+ }
486
+
487
+ async loadInitialLogs() {
488
+ // Deprecated - we don't load logs from files anymore
489
+ // Browser logs are only shown in real-time from WebSocket events
490
+ console.log('[BrowserLogViewer] Skipping file-based log loading - using real-time events only');
491
+ }
492
+
493
+ async loadLogs() {
494
+ // Deprecated - we don't load logs from files anymore
495
+ // Browser logs are only shown in real-time from WebSocket events
496
+ console.log('[BrowserLogViewer] Real-time mode only - not loading from files');
497
+ this.updateStats();
498
+ }
499
+
500
+ async loadLogFile(filename) {
501
+ // Deprecated - we don't load logs from files anymore
502
+ // Browser logs are only shown in real-time from WebSocket events
503
+ console.log('[BrowserLogViewer] File loading disabled - real-time mode only');
504
+ }
505
+
506
+ addLog(logEntry, render = true) {
507
+ // NUCLEAR VALIDATION: Multiple layers of protection
508
+
509
+ // Layer 1: Must have browser_id
510
+ if (!logEntry.browser_id) {
511
+ console.error('[BROWSER-LOG-VIEWER v2.0] ❌ REJECTED: No browser_id:', logEntry);
512
+ return;
513
+ }
514
+
515
+ // Layer 2: Check for hook contamination in ANY field
516
+ const entryString = JSON.stringify(logEntry).toLowerCase();
517
+ if (entryString.includes('hook') ||
518
+ entryString.includes('pre_tool') ||
519
+ entryString.includes('post_tool')) {
520
+ console.error('[BROWSER-LOG-VIEWER v2.0] 🚨 NUCLEAR REJECTION: Hook contamination detected!', logEntry);
521
+ return;
522
+ }
523
+
524
+ // Layer 3: Explicit hook type check
525
+ if (logEntry.type === 'hook' ||
526
+ logEntry.event_type === 'hook' ||
527
+ logEntry.event === 'hook' ||
528
+ (logEntry.message && typeof logEntry.message === 'string' &&
529
+ (logEntry.message.includes('[hook]') ||
530
+ logEntry.message.includes('hook.') ||
531
+ logEntry.message.includes('pre_tool') ||
532
+ logEntry.message.includes('post_tool')))) {
533
+ console.error('[BROWSER-LOG-VIEWER v2.0] 🚨🚨 CRITICAL REJECTION: Hook event blocked!', logEntry);
534
+ return;
535
+ }
536
+
537
+ console.log('[BROWSER-LOG-VIEWER v2.0] ✅ ACCEPTED: Valid browser log:', logEntry);
538
+
539
+ // Additionally validate that we have proper browser log structure
540
+ if (!logEntry.message && !logEntry.level) {
541
+ console.warn('[BrowserLogViewer] Rejecting malformed browser log entry:', logEntry);
542
+ return;
543
+ }
544
+
545
+ // Ensure proper structure for browser logs
546
+ const normalizedEntry = {
547
+ browser_id: logEntry.browser_id || 'unknown',
548
+ level: logEntry.level || 'INFO',
549
+ message: logEntry.message || '',
550
+ timestamp: logEntry.timestamp || new Date().toISOString(),
551
+ url: logEntry.url || '',
552
+ line_info: logEntry.line_info || null
553
+ };
554
+
555
+ // Add to logs array
556
+ this.logs.push(normalizedEntry);
557
+
558
+ // Track browser session
559
+ if (normalizedEntry.browser_id) {
560
+ this.browserSessions.add(normalizedEntry.browser_id);
561
+ this.updateBrowserFilter();
562
+ }
563
+
564
+ // Limit logs to prevent memory issues
565
+ if (this.logs.length > this.maxLogs) {
566
+ this.logs = this.logs.slice(-this.maxLogs);
567
+ }
568
+
569
+ if (render) {
570
+ this.renderLogs();
571
+ }
572
+ }
573
+
574
+ updateBrowserFilter() {
575
+ const select = document.getElementById('browser-filter');
576
+ const currentValue = select.value;
577
+
578
+ // Clear existing options except "All"
579
+ select.innerHTML = '<option value="all">All Browsers</option>';
580
+
581
+ // Add browser sessions
582
+ Array.from(this.browserSessions).sort().forEach(browserId => {
583
+ const option = document.createElement('option');
584
+ option.value = browserId;
585
+ option.textContent = browserId.substring(0, 20) + '...';
586
+ select.appendChild(option);
587
+ });
588
+
589
+ // Restore selection
590
+ select.value = currentValue;
591
+ }
592
+
593
+ renderLogs() {
594
+ console.error('[BROWSER-LOG-VIEWER v2.0] RENDER-LOGS: Starting render');
595
+ const container = document.getElementById('log-entries');
596
+
597
+ // NUCLEAR SAFETY: Verify we're in the right place
598
+ const parentContainer = document.getElementById('browser-logs-container');
599
+ if (!parentContainer || !parentContainer.contains(container)) {
600
+ console.error('[BROWSER-LOG-VIEWER v2.0] 🚨 WRONG CONTAINER - ABORTING!');
601
+ return;
602
+ }
603
+
604
+ // NUCLEAR CLEAN: Clear any potential contamination
605
+ const existingContent = container.innerHTML;
606
+ if (existingContent.includes('[hook]') ||
607
+ existingContent.includes('hook.pre_tool') ||
608
+ existingContent.includes('hook.post_tool')) {
609
+ console.error('[BROWSER-LOG-VIEWER v2.0] 🚨 CONTAMINATION FOUND IN RENDER - NUKING!');
610
+ container.innerHTML = '';
611
+ }
612
+
613
+ // Filter logs
614
+ const filteredLogs = this.logs.filter(log => {
615
+ if (this.filters.browserId !== 'all' && log.browser_id !== this.filters.browserId) {
616
+ return false;
617
+ }
618
+ if (this.filters.level !== 'all' && log.level !== this.filters.level) {
619
+ return false;
620
+ }
621
+ return true;
622
+ });
623
+
624
+ if (filteredLogs.length === 0) {
625
+ // Show proper empty state based on whether we have any logs at all
626
+ if (this.logs.length === 0) {
627
+ container.innerHTML = `
628
+ <div class="empty-state">
629
+ <i class="fas fa-globe fa-3x"></i>
630
+ <p>No browser console logs yet</p>
631
+ <p class="text-muted">
632
+ Browser console logs will appear here when the monitoring script is injected.<br>
633
+ Use <code>/mpm-browser-monitor start</code> to begin monitoring.
634
+ </p>
635
+ <p class="text-muted" style="font-size: 11px; margin-top: 10px;">
636
+ This tab shows console.log, console.error, and console.warn from monitored browsers only.
637
+ </p>
638
+ </div>
639
+ `;
640
+ } else {
641
+ container.innerHTML = `
642
+ <div class="empty-state">
643
+ <i class="fas fa-filter fa-3x"></i>
644
+ <p>No logs match the current filters</p>
645
+ </div>
646
+ `;
647
+ }
648
+ } else {
649
+ container.innerHTML = filteredLogs.map(log => this.renderLogEntry(log)).join('');
650
+
651
+ // Auto-scroll to bottom if enabled
652
+ if (this.autoScroll) {
653
+ const logContainer = document.getElementById('browser-log-container');
654
+ logContainer.scrollTop = logContainer.scrollHeight;
655
+ }
656
+ }
657
+
658
+ this.updateStats();
659
+ }
660
+
661
+ renderLogEntry(log) {
662
+ const levelClass = log.level ? log.level.toLowerCase() : 'info';
663
+ const timestamp = log.timestamp ? new Date(log.timestamp).toLocaleTimeString() : '';
664
+ const browserId = log.browser_id ? log.browser_id.substring(0, 12) : 'unknown';
665
+
666
+ let urlInfo = '';
667
+ if (log.url) {
668
+ try {
669
+ const url = new URL(log.url);
670
+ urlInfo = `<div class="log-url">${url.pathname}</div>`;
671
+ } catch {
672
+ urlInfo = `<div class="log-url">${log.url}</div>`;
673
+ }
674
+ }
675
+
676
+ return `
677
+ <div class="log-entry ${levelClass}">
678
+ <span class="log-timestamp">${timestamp}</span>
679
+ <span class="log-level ${levelClass}">${log.level || 'INFO'}</span>
680
+ <span class="log-browser">[${browserId}]</span>
681
+ <div class="log-message">
682
+ ${this.escapeHtml(log.message || '')}
683
+ ${urlInfo}
684
+ </div>
685
+ </div>
686
+ `;
687
+ }
688
+
689
+ escapeHtml(text) {
690
+ const div = document.createElement('div');
691
+ div.textContent = text;
692
+ return div.innerHTML;
693
+ }
694
+
695
+ updateStats() {
696
+ document.getElementById('log-count').textContent = `${this.logs.length} logs`;
697
+ document.getElementById('session-count').textContent = `${this.browserSessions.size} sessions`;
698
+ }
699
+
700
+ startAutoRefresh() {
701
+ if (this.refreshInterval) {
702
+ clearInterval(this.refreshInterval);
703
+ }
704
+
705
+ if (this.autoRefresh) {
706
+ // Refresh every 5 seconds
707
+ this.refreshInterval = setInterval(() => {
708
+ this.loadLogs();
709
+ }, 5000);
710
+ }
711
+ }
712
+
713
+ stopAutoRefresh() {
714
+ if (this.refreshInterval) {
715
+ clearInterval(this.refreshInterval);
716
+ this.refreshInterval = null;
717
+ }
718
+ }
719
+
720
+ exportLogs() {
721
+ const filteredLogs = this.logs.filter(log => {
722
+ if (this.filters.browserId !== 'all' && log.browser_id !== this.filters.browserId) {
723
+ return false;
724
+ }
725
+ if (this.filters.level !== 'all' && log.level !== this.filters.level) {
726
+ return false;
727
+ }
728
+ return true;
729
+ });
730
+
731
+ const jsonData = JSON.stringify(filteredLogs, null, 2);
732
+ const blob = new Blob([jsonData], { type: 'application/json' });
733
+ const url = URL.createObjectURL(blob);
734
+
735
+ const a = document.createElement('a');
736
+ a.href = url;
737
+ a.download = `browser-logs-${new Date().toISOString()}.json`;
738
+ document.body.appendChild(a);
739
+ a.click();
740
+ document.body.removeChild(a);
741
+ URL.revokeObjectURL(url);
742
+ }
743
+
744
+ destroy() {
745
+ this.stopAutoRefresh();
746
+
747
+ if (window.socket) {
748
+ window.socket.off('browser_log');
749
+ window.socket.off('dashboard:browser:console');
750
+ }
751
+
752
+ // Remove container ownership marker
753
+ if (this.container) {
754
+ this.container.removeAttribute('data-owner');
755
+ this.container.classList.remove('browser-log-viewer-container');
756
+ }
757
+ }
758
+ }
759
+
760
+ // Export for use in dashboard
761
+ if (typeof module !== 'undefined' && module.exports) {
762
+ module.exports = BrowserLogViewer;
763
+ }