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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_ENGINEER.md +114 -1
- claude_mpm/agents/BASE_OPS.md +156 -1
- claude_mpm/agents/INSTRUCTIONS.md +120 -11
- claude_mpm/agents/WORKFLOW.md +160 -10
- claude_mpm/agents/templates/agentic-coder-optimizer.json +17 -12
- claude_mpm/agents/templates/react_engineer.json +217 -0
- claude_mpm/agents/templates/web_qa.json +40 -4
- claude_mpm/cli/__init__.py +3 -5
- claude_mpm/commands/mpm-browser-monitor.md +370 -0
- claude_mpm/commands/mpm-monitor.md +177 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +1076 -2
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +465 -2
- claude_mpm/dashboard/static/css/dashboard.css +2 -0
- claude_mpm/dashboard/static/js/browser-console-monitor.js +495 -0
- claude_mpm/dashboard/static/js/components/browser-log-viewer.js +763 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +931 -340
- claude_mpm/dashboard/static/js/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +307 -19
- claude_mpm/dashboard/static/js/socket-client.js +2 -2
- claude_mpm/dashboard/static/test-browser-monitor.html +470 -0
- claude_mpm/dashboard/templates/index.html +62 -99
- claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
- claude_mpm/services/monitor/daemon.py +69 -36
- claude_mpm/services/monitor/daemon_manager.py +186 -29
- claude_mpm/services/monitor/handlers/browser.py +451 -0
- claude_mpm/services/monitor/server.py +272 -5
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/RECORD +35 -29
- claude_mpm/agents/templates/agentic-coder-optimizer.md +0 -44
- claude_mpm/agents/templates/agentic_coder_optimizer.json +0 -238
- claude_mpm/agents/templates/test-non-mpm.json +0 -20
- claude_mpm/dashboard/static/dist/components/code-viewer.js +0 -2
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,495 @@
|
|
1
|
+
/**
|
2
|
+
* Browser Console Monitor for Claude MPM
|
3
|
+
* =====================================
|
4
|
+
*
|
5
|
+
* Injectable script that captures browser console events and sends them
|
6
|
+
* to the Claude MPM monitor server for centralized logging and debugging.
|
7
|
+
*
|
8
|
+
* DESIGN DECISIONS:
|
9
|
+
* - Generates unique browser ID for session tracking
|
10
|
+
* - Intercepts all console methods without disrupting normal operation
|
11
|
+
* - Provides real-time streaming to monitor server via Socket.IO
|
12
|
+
* - Includes visual indicator showing monitoring status
|
13
|
+
* - Handles reconnection gracefully for reliability
|
14
|
+
* - Captures stack traces for error context
|
15
|
+
*
|
16
|
+
* USAGE:
|
17
|
+
* Include this script in any page: <script src="http://localhost:8765/api/browser-monitor.js"></script>
|
18
|
+
* The script will automatically connect to the monitor server and start capturing console events.
|
19
|
+
*/
|
20
|
+
|
21
|
+
(function() {
|
22
|
+
'use strict';
|
23
|
+
|
24
|
+
// Configuration - MONITOR_PORT will be replaced dynamically by server
|
25
|
+
const MONITOR_PORT = __MONITOR_PORT__;
|
26
|
+
const MONITOR_HOST = 'localhost';
|
27
|
+
|
28
|
+
// Generate unique browser ID for this session
|
29
|
+
const BROWSER_ID = `browser-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
30
|
+
|
31
|
+
// Console levels to intercept
|
32
|
+
const CONSOLE_LEVELS = ['log', 'warn', 'error', 'info', 'debug', 'trace'];
|
33
|
+
|
34
|
+
// State management
|
35
|
+
let socket = null;
|
36
|
+
let isConnected = false;
|
37
|
+
let eventQueue = [];
|
38
|
+
let indicator = null;
|
39
|
+
let originalConsole = {};
|
40
|
+
|
41
|
+
// Connection retry configuration
|
42
|
+
const RETRY_CONFIG = {
|
43
|
+
maxRetries: 5,
|
44
|
+
retryDelay: 1000,
|
45
|
+
currentRetries: 0
|
46
|
+
};
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Initialize the browser console monitoring system
|
50
|
+
*/
|
51
|
+
function initializeMonitor() {
|
52
|
+
try {
|
53
|
+
console.log(`[Browser Monitor] Initializing for browser ID: ${BROWSER_ID}`);
|
54
|
+
|
55
|
+
// Store original console methods before interception
|
56
|
+
storeOriginalConsoleMethods();
|
57
|
+
|
58
|
+
// Setup Socket.IO connection
|
59
|
+
setupSocketConnection();
|
60
|
+
|
61
|
+
// Intercept console methods
|
62
|
+
interceptConsoleMethods();
|
63
|
+
|
64
|
+
// Create visual indicator
|
65
|
+
createStatusIndicator();
|
66
|
+
|
67
|
+
// Setup cleanup handlers
|
68
|
+
setupCleanupHandlers();
|
69
|
+
|
70
|
+
} catch (error) {
|
71
|
+
console.error('[Browser Monitor] Failed to initialize:', error);
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Store references to original console methods
|
77
|
+
*/
|
78
|
+
function storeOriginalConsoleMethods() {
|
79
|
+
CONSOLE_LEVELS.forEach(level => {
|
80
|
+
if (console[level] && typeof console[level] === 'function') {
|
81
|
+
originalConsole[level] = console[level].bind(console);
|
82
|
+
}
|
83
|
+
});
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Setup Socket.IO connection to monitor server
|
88
|
+
*/
|
89
|
+
function setupSocketConnection() {
|
90
|
+
try {
|
91
|
+
// Load Socket.IO if not already available
|
92
|
+
if (typeof io === 'undefined') {
|
93
|
+
loadSocketIO(() => {
|
94
|
+
createSocketConnection();
|
95
|
+
});
|
96
|
+
} else {
|
97
|
+
createSocketConnection();
|
98
|
+
}
|
99
|
+
} catch (error) {
|
100
|
+
console.error('[Browser Monitor] Socket setup error:', error);
|
101
|
+
updateIndicatorStatus('error', 'Socket setup failed');
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
/**
|
106
|
+
* Load Socket.IO library dynamically
|
107
|
+
*/
|
108
|
+
function loadSocketIO(callback) {
|
109
|
+
const script = document.createElement('script');
|
110
|
+
script.src = `http://${MONITOR_HOST}:${MONITOR_PORT}/socket.io/socket.io.js`;
|
111
|
+
script.onload = callback;
|
112
|
+
script.onerror = () => {
|
113
|
+
console.error('[Browser Monitor] Failed to load Socket.IO');
|
114
|
+
updateIndicatorStatus('error', 'Failed to load Socket.IO');
|
115
|
+
};
|
116
|
+
document.head.appendChild(script);
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Create the actual Socket.IO connection
|
121
|
+
*/
|
122
|
+
function createSocketConnection() {
|
123
|
+
try {
|
124
|
+
socket = io(`http://${MONITOR_HOST}:${MONITOR_PORT}`, {
|
125
|
+
transports: ['websocket', 'polling'],
|
126
|
+
timeout: 5000,
|
127
|
+
reconnection: true,
|
128
|
+
reconnectionDelay: 1000,
|
129
|
+
reconnectionAttempts: RETRY_CONFIG.maxRetries
|
130
|
+
});
|
131
|
+
|
132
|
+
// Connection event handlers
|
133
|
+
socket.on('connect', handleSocketConnect);
|
134
|
+
socket.on('disconnect', handleSocketDisconnect);
|
135
|
+
socket.on('error', handleSocketError);
|
136
|
+
socket.on('reconnect', handleSocketReconnect);
|
137
|
+
|
138
|
+
// Send initial connection event
|
139
|
+
setTimeout(() => {
|
140
|
+
if (socket && socket.connected) {
|
141
|
+
sendBrowserEvent('connect', {
|
142
|
+
browser_id: BROWSER_ID,
|
143
|
+
user_agent: navigator.userAgent,
|
144
|
+
url: window.location.href,
|
145
|
+
timestamp: new Date().toISOString()
|
146
|
+
});
|
147
|
+
}
|
148
|
+
}, 100);
|
149
|
+
|
150
|
+
} catch (error) {
|
151
|
+
console.error('[Browser Monitor] Socket connection error:', error);
|
152
|
+
updateIndicatorStatus('error', 'Connection failed');
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
/**
|
157
|
+
* Handle socket connection
|
158
|
+
*/
|
159
|
+
function handleSocketConnect() {
|
160
|
+
isConnected = true;
|
161
|
+
RETRY_CONFIG.currentRetries = 0;
|
162
|
+
|
163
|
+
updateIndicatorStatus('connected', `Connected: ${BROWSER_ID}`);
|
164
|
+
|
165
|
+
// Send queued events
|
166
|
+
flushEventQueue();
|
167
|
+
|
168
|
+
console.log(`[Browser Monitor] Connected to monitor server: ${BROWSER_ID}`);
|
169
|
+
}
|
170
|
+
|
171
|
+
/**
|
172
|
+
* Handle socket disconnection
|
173
|
+
*/
|
174
|
+
function handleSocketDisconnect(reason) {
|
175
|
+
isConnected = false;
|
176
|
+
updateIndicatorStatus('disconnected', `Disconnected: ${reason}`);
|
177
|
+
console.log(`[Browser Monitor] Disconnected from monitor server: ${reason}`);
|
178
|
+
}
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Handle socket errors
|
182
|
+
*/
|
183
|
+
function handleSocketError(error) {
|
184
|
+
console.error('[Browser Monitor] Socket error:', error);
|
185
|
+
updateIndicatorStatus('error', 'Connection error');
|
186
|
+
}
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Handle socket reconnection
|
190
|
+
*/
|
191
|
+
function handleSocketReconnect() {
|
192
|
+
console.log('[Browser Monitor] Reconnected to monitor server');
|
193
|
+
updateIndicatorStatus('connected', `Reconnected: ${BROWSER_ID}`);
|
194
|
+
}
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Intercept console methods and capture events
|
198
|
+
*/
|
199
|
+
function interceptConsoleMethods() {
|
200
|
+
CONSOLE_LEVELS.forEach(level => {
|
201
|
+
if (originalConsole[level]) {
|
202
|
+
console[level] = function(...args) {
|
203
|
+
// Capture the console event
|
204
|
+
captureConsoleEvent(level, args);
|
205
|
+
|
206
|
+
// Call original console method
|
207
|
+
return originalConsole[level].apply(console, args);
|
208
|
+
};
|
209
|
+
}
|
210
|
+
});
|
211
|
+
}
|
212
|
+
|
213
|
+
/**
|
214
|
+
* Capture console event and send to monitor server
|
215
|
+
*/
|
216
|
+
function captureConsoleEvent(level, args) {
|
217
|
+
try {
|
218
|
+
const timestamp = new Date().toISOString();
|
219
|
+
const stack = (new Error()).stack;
|
220
|
+
|
221
|
+
// Serialize arguments safely
|
222
|
+
const serializedArgs = args.map(arg => {
|
223
|
+
if (arg === null) return 'null';
|
224
|
+
if (arg === undefined) return 'undefined';
|
225
|
+
|
226
|
+
if (typeof arg === 'object') {
|
227
|
+
try {
|
228
|
+
return JSON.stringify(arg, null, 2);
|
229
|
+
} catch (e) {
|
230
|
+
return '[Circular Object]';
|
231
|
+
}
|
232
|
+
} else if (typeof arg === 'function') {
|
233
|
+
return `[Function: ${arg.name || 'anonymous'}]`;
|
234
|
+
} else {
|
235
|
+
return String(arg);
|
236
|
+
}
|
237
|
+
});
|
238
|
+
|
239
|
+
const message = serializedArgs.join(' ');
|
240
|
+
|
241
|
+
// Create event data
|
242
|
+
const eventData = {
|
243
|
+
browser_id: BROWSER_ID,
|
244
|
+
level: level.toUpperCase(),
|
245
|
+
timestamp: timestamp,
|
246
|
+
message: message,
|
247
|
+
args: serializedArgs,
|
248
|
+
stack: stack,
|
249
|
+
url: window.location.href,
|
250
|
+
line_info: extractLineInfo(stack)
|
251
|
+
};
|
252
|
+
|
253
|
+
// Send to monitor server
|
254
|
+
sendBrowserEvent('console', eventData);
|
255
|
+
|
256
|
+
} catch (error) {
|
257
|
+
// Use original console to avoid infinite recursion
|
258
|
+
originalConsole.error('[Browser Monitor] Error capturing console event:', error);
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
262
|
+
/**
|
263
|
+
* Extract line information from stack trace
|
264
|
+
*/
|
265
|
+
function extractLineInfo(stack) {
|
266
|
+
if (!stack) return null;
|
267
|
+
|
268
|
+
try {
|
269
|
+
const lines = stack.split('\n');
|
270
|
+
// Find the first line that's not from this monitoring script
|
271
|
+
for (let i = 1; i < lines.length; i++) {
|
272
|
+
const line = lines[i].trim();
|
273
|
+
if (line && !line.includes('browser-console-monitor.js')) {
|
274
|
+
return line;
|
275
|
+
}
|
276
|
+
}
|
277
|
+
return lines[1] || null;
|
278
|
+
} catch (error) {
|
279
|
+
return null;
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
/**
|
284
|
+
* Send browser event to monitor server
|
285
|
+
*/
|
286
|
+
function sendBrowserEvent(eventType, data) {
|
287
|
+
const event = {
|
288
|
+
type: eventType,
|
289
|
+
data: data,
|
290
|
+
timestamp: new Date().toISOString()
|
291
|
+
};
|
292
|
+
|
293
|
+
if (isConnected && socket) {
|
294
|
+
socket.emit('browser:' + eventType, data);
|
295
|
+
} else {
|
296
|
+
// Queue event for later sending
|
297
|
+
eventQueue.push(event);
|
298
|
+
|
299
|
+
// Limit queue size to prevent memory issues
|
300
|
+
if (eventQueue.length > 1000) {
|
301
|
+
eventQueue = eventQueue.slice(-500); // Keep last 500 events
|
302
|
+
}
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
/**
|
307
|
+
* Flush queued events when connection is restored
|
308
|
+
*/
|
309
|
+
function flushEventQueue() {
|
310
|
+
if (eventQueue.length > 0 && isConnected && socket) {
|
311
|
+
console.log(`[Browser Monitor] Sending ${eventQueue.length} queued events`);
|
312
|
+
|
313
|
+
eventQueue.forEach(event => {
|
314
|
+
socket.emit('browser:' + event.type, event.data);
|
315
|
+
});
|
316
|
+
|
317
|
+
eventQueue = [];
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
/**
|
322
|
+
* Create visual status indicator
|
323
|
+
*/
|
324
|
+
function createStatusIndicator() {
|
325
|
+
try {
|
326
|
+
indicator = document.createElement('div');
|
327
|
+
indicator.id = 'browser-console-monitor-indicator';
|
328
|
+
indicator.style.cssText = `
|
329
|
+
position: fixed !important;
|
330
|
+
bottom: 10px !important;
|
331
|
+
right: 10px !important;
|
332
|
+
background: #2d3748 !important;
|
333
|
+
color: #e2e8f0 !important;
|
334
|
+
padding: 8px 12px !important;
|
335
|
+
border-radius: 6px !important;
|
336
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace !important;
|
337
|
+
font-size: 11px !important;
|
338
|
+
z-index: 999999 !important;
|
339
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
|
340
|
+
border: 1px solid #4a5568 !important;
|
341
|
+
cursor: pointer !important;
|
342
|
+
transition: all 0.2s ease !important;
|
343
|
+
min-width: 180px !important;
|
344
|
+
text-align: left !important;
|
345
|
+
`;
|
346
|
+
|
347
|
+
// Add hover effect
|
348
|
+
indicator.onmouseover = () => {
|
349
|
+
indicator.style.transform = 'scale(1.05)';
|
350
|
+
indicator.style.boxShadow = '0 6px 20px rgba(0,0,0,0.25)';
|
351
|
+
};
|
352
|
+
|
353
|
+
indicator.onmouseout = () => {
|
354
|
+
indicator.style.transform = 'scale(1)';
|
355
|
+
indicator.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
356
|
+
};
|
357
|
+
|
358
|
+
// Add click to toggle detailed info
|
359
|
+
indicator.onclick = () => {
|
360
|
+
showMonitorInfo();
|
361
|
+
};
|
362
|
+
|
363
|
+
updateIndicatorStatus('connecting', 'Connecting...');
|
364
|
+
|
365
|
+
// Wait for DOM to be ready
|
366
|
+
if (document.readyState === 'loading') {
|
367
|
+
document.addEventListener('DOMContentLoaded', () => {
|
368
|
+
document.body.appendChild(indicator);
|
369
|
+
});
|
370
|
+
} else {
|
371
|
+
document.body.appendChild(indicator);
|
372
|
+
}
|
373
|
+
|
374
|
+
} catch (error) {
|
375
|
+
console.error('[Browser Monitor] Error creating indicator:', error);
|
376
|
+
}
|
377
|
+
}
|
378
|
+
|
379
|
+
/**
|
380
|
+
* Update status indicator
|
381
|
+
*/
|
382
|
+
function updateIndicatorStatus(status, message) {
|
383
|
+
if (!indicator) return;
|
384
|
+
|
385
|
+
const statusColors = {
|
386
|
+
connecting: '#f6ad55', // orange
|
387
|
+
connected: '#48bb78', // green
|
388
|
+
disconnected: '#f56565', // red
|
389
|
+
error: '#e53e3e' // dark red
|
390
|
+
};
|
391
|
+
|
392
|
+
const statusIcons = {
|
393
|
+
connecting: '🔄',
|
394
|
+
connected: '✅',
|
395
|
+
disconnected: '❌',
|
396
|
+
error: '⚠️'
|
397
|
+
};
|
398
|
+
|
399
|
+
indicator.style.backgroundColor = statusColors[status] || '#2d3748';
|
400
|
+
indicator.innerHTML = `
|
401
|
+
<div style="display: flex; align-items: center; gap: 6px;">
|
402
|
+
<span style="font-size: 12px;">${statusIcons[status] || '📡'}</span>
|
403
|
+
<span style="font-weight: 500;">Console Monitor</span>
|
404
|
+
</div>
|
405
|
+
<div style="font-size: 10px; opacity: 0.9; margin-top: 2px;">
|
406
|
+
${message || status}
|
407
|
+
</div>
|
408
|
+
`;
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* Show detailed monitor information
|
413
|
+
*/
|
414
|
+
function showMonitorInfo() {
|
415
|
+
const info = {
|
416
|
+
browserID: BROWSER_ID,
|
417
|
+
status: isConnected ? 'Connected' : 'Disconnected',
|
418
|
+
server: `${MONITOR_HOST}:${MONITOR_PORT}`,
|
419
|
+
queuedEvents: eventQueue.length,
|
420
|
+
url: window.location.href,
|
421
|
+
userAgent: navigator.userAgent.substring(0, 50) + '...'
|
422
|
+
};
|
423
|
+
|
424
|
+
console.group('📡 Browser Console Monitor Info');
|
425
|
+
Object.entries(info).forEach(([key, value]) => {
|
426
|
+
console.log(`${key}:`, value);
|
427
|
+
});
|
428
|
+
console.groupEnd();
|
429
|
+
}
|
430
|
+
|
431
|
+
/**
|
432
|
+
* Setup cleanup handlers for page unload
|
433
|
+
*/
|
434
|
+
function setupCleanupHandlers() {
|
435
|
+
window.addEventListener('beforeunload', () => {
|
436
|
+
if (socket && isConnected) {
|
437
|
+
sendBrowserEvent('disconnect', {
|
438
|
+
browser_id: BROWSER_ID,
|
439
|
+
timestamp: new Date().toISOString()
|
440
|
+
});
|
441
|
+
}
|
442
|
+
});
|
443
|
+
|
444
|
+
// Cleanup on page hide (mobile support)
|
445
|
+
document.addEventListener('visibilitychange', () => {
|
446
|
+
if (document.hidden && socket && isConnected) {
|
447
|
+
sendBrowserEvent('hide', {
|
448
|
+
browser_id: BROWSER_ID,
|
449
|
+
timestamp: new Date().toISOString()
|
450
|
+
});
|
451
|
+
}
|
452
|
+
});
|
453
|
+
}
|
454
|
+
|
455
|
+
/**
|
456
|
+
* Restore original console methods (for cleanup)
|
457
|
+
*/
|
458
|
+
function restoreConsole() {
|
459
|
+
CONSOLE_LEVELS.forEach(level => {
|
460
|
+
if (originalConsole[level]) {
|
461
|
+
console[level] = originalConsole[level];
|
462
|
+
}
|
463
|
+
});
|
464
|
+
}
|
465
|
+
|
466
|
+
// Expose cleanup function globally for manual cleanup if needed
|
467
|
+
window.browserConsoleMonitor = {
|
468
|
+
disconnect: () => {
|
469
|
+
if (socket) {
|
470
|
+
socket.disconnect();
|
471
|
+
}
|
472
|
+
restoreConsole();
|
473
|
+
if (indicator && indicator.parentNode) {
|
474
|
+
indicator.parentNode.removeChild(indicator);
|
475
|
+
}
|
476
|
+
},
|
477
|
+
getInfo: () => ({
|
478
|
+
browserID: BROWSER_ID,
|
479
|
+
isConnected: isConnected,
|
480
|
+
queuedEvents: eventQueue.length
|
481
|
+
})
|
482
|
+
};
|
483
|
+
|
484
|
+
// Initialize when DOM is ready
|
485
|
+
if (document.readyState === 'loading') {
|
486
|
+
document.addEventListener('DOMContentLoaded', initializeMonitor);
|
487
|
+
} else {
|
488
|
+
// DOM already loaded
|
489
|
+
initializeMonitor();
|
490
|
+
}
|
491
|
+
|
492
|
+
// Also initialize immediately in case DOMContentLoaded has already fired
|
493
|
+
setTimeout(initializeMonitor, 100);
|
494
|
+
|
495
|
+
})();
|