@dynamicu/chromedebug-mcp 2.5.14 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -141,7 +141,7 @@ class ChromePilotApp {
141
141
  * Shows help information
142
142
  */
143
143
  showHelp() {
144
- console.log(`
144
+ console.error(`
145
145
  Chrome Debug MCP Server - Modular Architecture
146
146
 
147
147
  Usage: node src/index.js [options]
package/src/index.js CHANGED
@@ -17,6 +17,11 @@ import {
17
17
  findActiveSessions
18
18
  } from './services/unified-session-manager.js';
19
19
  import logger from './utils/logger.js';
20
+ import { interceptStdout } from './logger.js';
21
+
22
+ // CRITICAL: Intercept stdout FIRST to prevent MCP JSON-RPC pollution
23
+ // This must happen before any other code runs
24
+ interceptStdout();
20
25
 
21
26
  /**
22
27
  * Main application class that orchestrates all components
@@ -201,7 +206,7 @@ class ChromePilotApp {
201
206
  * Shows help information
202
207
  */
203
208
  showHelp() {
204
- console.log(`
209
+ console.error(`
205
210
  Chrome Debug MCP Server - Modular Architecture with Session Isolation
206
211
 
207
212
  Usage: node src/index.js [options]
package/src/logger.js CHANGED
@@ -1,11 +1,102 @@
1
- // Simple conditional logger for Chrome Debug
2
- // All logs go to stderr to avoid interfering with MCP stdio communication
3
- const isDev = process.env.NODE_ENV === 'development';
4
-
5
- module.exports = {
6
- log: (...args) => { if (isDev) console.error(...args); },
7
- error: (...args) => console.error(...args),
8
- warn: (...args) => { if (isDev) console.error(...args); },
9
- info: (...args) => { if (isDev) console.error(...args); },
10
- debug: (...args) => { if (isDev) console.error(...args); }
11
- };
1
+ /**
2
+ * Structured logging for ChromeDebug MCP
3
+ * Uses pino for performance and structured output
4
+ * All logs go to stderr to preserve MCP stdout for JSON-RPC
5
+ */
6
+
7
+ import pino from 'pino';
8
+
9
+ // Determine log level based on environment
10
+ const getLogLevel = () => {
11
+ if (process.env.LOG_LEVEL) {
12
+ return process.env.LOG_LEVEL;
13
+ }
14
+ if (process.env.DEBUG === 'true' || process.env.DEBUG === '1') {
15
+ return 'debug';
16
+ }
17
+ if (process.env.NODE_ENV === 'production') {
18
+ return 'warn';
19
+ }
20
+ return 'info'; // Default for development
21
+ };
22
+
23
+ // Create base pino instance
24
+ const baseLogger = pino({
25
+ level: getLogLevel(),
26
+ transport: {
27
+ target: 'pino-pretty',
28
+ options: {
29
+ destination: 2, // stderr
30
+ colorize: true,
31
+ ignore: 'pid,hostname',
32
+ translateTime: 'HH:MM:ss',
33
+ singleLine: false
34
+ }
35
+ }
36
+ });
37
+
38
+ /**
39
+ * Create a child logger with component context
40
+ * @param {string} component - Component name for log context
41
+ * @returns {pino.Logger} Configured pino logger
42
+ */
43
+ export function createLogger(component) {
44
+ return baseLogger.child({ component });
45
+ }
46
+
47
+ /**
48
+ * HTTP request logger helper
49
+ * Only logs if DEBUG_HTTP is enabled or in debug level
50
+ * @param {pino.Logger} logger - Pino logger instance
51
+ * @param {object} req - Express request object
52
+ * @param {object} options - Logging options
53
+ */
54
+ export function logHttpRequest(logger, req, options = {}) {
55
+ const shouldLog = process.env.DEBUG_HTTP === 'true' ||
56
+ process.env.DEBUG === 'true' ||
57
+ logger.level === 'debug';
58
+
59
+ if (!shouldLog) return;
60
+
61
+ const requestData = {
62
+ method: req.method,
63
+ url: req.originalUrl || req.url,
64
+ ip: req.ip,
65
+ userAgent: req.get('user-agent')
66
+ };
67
+
68
+ if (options.verbose) {
69
+ requestData.headers = req.headers;
70
+ requestData.contentType = req.get('content-type');
71
+ requestData.contentLength = req.get('content-length');
72
+ }
73
+
74
+ logger.debug(requestData, 'HTTP Request');
75
+ }
76
+
77
+ /**
78
+ * Intercept stdout to prevent MCP JSON-RPC pollution
79
+ * Call this at application startup for MCP servers
80
+ */
81
+ export function interceptStdout() {
82
+ const originalWrite = process.stdout.write;
83
+
84
+ process.stdout.write = function(chunk, encoding, callback) {
85
+ // Redirect all stdout to stderr with a warning prefix
86
+ const message = chunk.toString();
87
+ const redirectedMessage = `[STDOUT-REDIRECT] ${message}`;
88
+
89
+ return process.stderr.write(redirectedMessage, encoding, callback);
90
+ };
91
+
92
+ baseLogger.info('Stdout interception enabled - all output redirected to stderr for MCP safety');
93
+ }
94
+
95
+ // Default export for backward compatibility
96
+ export default {
97
+ error: (...args) => baseLogger.error(...args),
98
+ warn: (...args) => baseLogger.warn(...args),
99
+ info: (...args) => baseLogger.info(...args),
100
+ debug: (...args) => baseLogger.debug(...args),
101
+ log: (...args) => baseLogger.info(...args)
102
+ };
@@ -229,7 +229,7 @@ export class ChromeToolHandler {
229
229
 
230
230
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
231
231
  try {
232
- console.log(`[ChromeToolHandler] Attempt ${attempt}/${maxRetries} for ${name} (Session: ${this.chromeController.sessionId})`);
232
+ console.error(`[ChromeToolHandler] Attempt ${attempt}/${maxRetries} for ${name} (Session: ${this.chromeController.sessionId})`);
233
233
 
234
234
  // Execute the operation based on name
235
235
  switch (name) {
@@ -254,7 +254,7 @@ export class ChromeToolHandler {
254
254
  const isSessionConflict = this.isSessionConflictError(error);
255
255
 
256
256
  if (isSessionConflict && attempt < maxRetries) {
257
- console.log(`[ChromeToolHandler] Detected session conflict, attempting recovery...`);
257
+ console.error(`[ChromeToolHandler] Detected session conflict, attempting recovery...`);
258
258
 
259
259
  try {
260
260
  // Force cleanup current session and create new one
@@ -263,7 +263,7 @@ export class ChromeToolHandler {
263
263
  // Wait a bit before retrying to let system stabilize
264
264
  await new Promise(resolve => setTimeout(resolve, 1000 + (attempt * 500)));
265
265
 
266
- console.log(`[ChromeToolHandler] Session recovery completed, retrying...`);
266
+ console.error(`[ChromeToolHandler] Session recovery completed, retrying...`);
267
267
  } catch (recoveryError) {
268
268
  console.error(`[ChromeToolHandler] Session recovery failed:`, recoveryError.message);
269
269
  }
@@ -96,7 +96,7 @@ export class FrameToolHandler {
96
96
  }
97
97
  } catch (error) {
98
98
  // Fall back to direct database access if HTTP client fails
99
- console.warn('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
99
+ console.error('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
100
100
  sessionInfo = await this.chromeController.getFrameSessionInfo(args.sessionId);
101
101
  if (!sessionInfo) {
102
102
  availableSessions = await this.chromeController.listFrameSessions();
@@ -167,7 +167,7 @@ export class FrameToolHandler {
167
167
  result.note = 'No frame data or interactions found. This may indicate the recording was started but stopped immediately.';
168
168
  }
169
169
  } catch (error) {
170
- console.warn('[FrameToolHandler] Failed to check interactions:', error.message);
170
+ console.error('[FrameToolHandler] Failed to check interactions:', error.message);
171
171
  result.note = 'No frame data found. Unable to check for interactions.';
172
172
  }
173
173
 
@@ -285,7 +285,7 @@ export class FrameToolHandler {
285
285
  frameData.interactionMetadata = { totalFound: 0, displayed: 0, timeWindow: 500 };
286
286
  }
287
287
  } catch (error) {
288
- console.warn('[FrameToolHandler] Failed to get frame interactions:', error.message);
288
+ console.error('[FrameToolHandler] Failed to get frame interactions:', error.message);
289
289
  frameData.interactions = [];
290
290
  frameData.associatedInteractions = [];
291
291
  frameData.interactionMetadata = { totalFound: 0, displayed: 0, timeWindow: 500 };
@@ -329,7 +329,7 @@ export class FrameToolHandler {
329
329
  }
330
330
  }
331
331
  } catch (error) {
332
- console.warn('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
332
+ console.error('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
333
333
  info = await this.chromeController.getFrameSessionInfo(args.sessionId);
334
334
  if (!info) {
335
335
  availableSessions = await this.chromeController.listFrameSessions();
@@ -386,7 +386,7 @@ export class FrameToolHandler {
386
386
  interactionSummary.interactionTypes = Object.entries(typeCount).map(([type, count]) => `${type}: ${count}`);
387
387
  }
388
388
  } catch (error) {
389
- console.warn('[FrameToolHandler] Failed to get interaction summary:', error.message);
389
+ console.error('[FrameToolHandler] Failed to get interaction summary:', error.message);
390
390
  // Keep default empty summary
391
391
  }
392
392
 
@@ -424,7 +424,7 @@ export class FrameToolHandler {
424
424
  frame = await this.chromeController.getFrame(args.sessionId, args.frameIndex);
425
425
  }
426
426
  } catch (error) {
427
- console.warn('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
427
+ console.error('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
428
428
  frame = await this.chromeController.getFrame(args.sessionId, args.frameIndex);
429
429
  }
430
430
 
@@ -534,7 +534,7 @@ export class FrameToolHandler {
534
534
  results = await this.chromeController.searchFrameLogs(args.sessionId, args.searchText, logLevel, maxResults);
535
535
  }
536
536
  } catch (error) {
537
- console.warn('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
537
+ console.error('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
538
538
  results = await this.chromeController.searchFrameLogs(args.sessionId, args.searchText, logLevel, maxResults);
539
539
  }
540
540
 
@@ -577,7 +577,7 @@ export class FrameToolHandler {
577
577
  results = await this.chromeController.getFrameLogsPaginated(args.sessionId, args.frameIndex, offset, limit, logLevel, searchText);
578
578
  }
579
579
  } catch (error) {
580
- console.warn('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
580
+ console.error('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
581
581
  results = await this.chromeController.getFrameLogsPaginated(args.sessionId, args.frameIndex, offset, limit, logLevel, searchText);
582
582
  }
583
583
 
@@ -698,7 +698,7 @@ export class FrameToolHandler {
698
698
  }
699
699
  }
700
700
  } catch (error) {
701
- console.warn('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
701
+ console.error('[FrameToolHandler] HTTP client failed, falling back to direct access:', error.message);
702
702
  if (args.frameIndex !== undefined) {
703
703
  interactions = await this.chromeController.getFrameInteractions(args.sessionId, args.frameIndex);
704
704
  } else {
@@ -936,7 +936,7 @@ export class FrameToolHandler {
936
936
  }
937
937
  }
938
938
  } catch (error) {
939
- console.warn(`Error processing snapshot ${snapshot.id}:`, error.message);
939
+ console.error(`Error processing snapshot ${snapshot.id}:`, error.message);
940
940
  }
941
941
  }
942
942
 
@@ -77,7 +77,7 @@ export class WorkflowToolHandler {
77
77
  recording = await this.chromeController.getWorkflowRecording(args.sessionId);
78
78
  }
79
79
  } catch (error) {
80
- console.warn('[WorkflowToolHandler] HTTP client failed, falling back to direct access:', error.message);
80
+ console.error('[WorkflowToolHandler] HTTP client failed, falling back to direct access:', error.message);
81
81
  recording = await this.chromeController.getWorkflowRecording(args.sessionId);
82
82
  }
83
83
 
@@ -112,7 +112,7 @@ export class WorkflowToolHandler {
112
112
  result = await this.chromeController.listWorkflowRecordings();
113
113
  }
114
114
  } catch (error) {
115
- console.warn('[WorkflowToolHandler] HTTP client failed, falling back to direct access:', error.message);
115
+ console.error('[WorkflowToolHandler] HTTP client failed, falling back to direct access:', error.message);
116
116
  result = await this.chromeController.listWorkflowRecordings();
117
117
  }
118
118
 
@@ -38,7 +38,7 @@ export const corsOptions = {
38
38
  if (isAllowed) {
39
39
  callback(null, true);
40
40
  } else {
41
- console.warn(`CORS: Blocked origin ${origin}`);
41
+ console.error(`CORS: Blocked origin ${origin}`);
42
42
  callback(new Error('Not allowed by CORS policy'));
43
43
  }
44
44
  },
@@ -165,14 +165,14 @@ export function securityLogger(req, res, next) {
165
165
  );
166
166
 
167
167
  if (isSensitive) {
168
- console.log(`[Security] ${req.method} ${req.path} - User: ${req.user?.name || 'unauthenticated'} - IP: ${req.ip}`);
168
+ console.error(`[Security] ${req.method} ${req.path} - User: ${req.user?.name || 'unauthenticated'} - IP: ${req.ip}`);
169
169
  }
170
170
 
171
171
  // Track response time for monitoring
172
172
  res.on('finish', () => {
173
173
  const duration = Date.now() - start;
174
174
  if (duration > 5000) { // Log slow requests
175
- console.warn(`[Performance] Slow request: ${req.method} ${req.path} - ${duration}ms`);
175
+ console.error(`[Performance] Slow request: ${req.method} ${req.path} - ${duration}ms`);
176
176
  }
177
177
  });
178
178
 
@@ -245,7 +245,7 @@ export function createIPWhitelist(allowedIPs = []) {
245
245
  const clientIP = req.ip || req.connection.remoteAddress;
246
246
 
247
247
  if (!allowedIPs.includes(clientIP)) {
248
- console.warn(`[Security] Blocked IP: ${clientIP}`);
248
+ console.error(`[Security] Blocked IP: ${clientIP}`);
249
249
  return res.status(403).json({
250
250
  error: 'IP not whitelisted'
251
251
  });
@@ -67,7 +67,7 @@ export class BrowserDaemon {
67
67
  const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
68
68
  signals.forEach(signal => {
69
69
  process.on(signal, async () => {
70
- console.log(`[BrowserDaemon] Received ${signal}, initiating graceful shutdown...`);
70
+ console.error(`[BrowserDaemon] Received ${signal}, initiating graceful shutdown...`);
71
71
  await this.gracefulShutdown();
72
72
  process.exit(0);
73
73
  });
@@ -96,7 +96,7 @@ export class BrowserDaemon {
96
96
  }
97
97
 
98
98
  try {
99
- console.log(`[BrowserDaemon] Starting daemon ${this.daemonId}...`);
99
+ console.error(`[BrowserDaemon] Starting daemon ${this.daemonId}...`);
100
100
 
101
101
  // Initialize core managers
102
102
  await this.profileManager.initialize();
@@ -116,7 +116,7 @@ export class BrowserDaemon {
116
116
  // Start health monitoring
117
117
  this.startHealthCheck();
118
118
 
119
- console.log(`[BrowserDaemon] Daemon started successfully on port ${this.config.daemonPort}`);
119
+ console.error(`[BrowserDaemon] Daemon started successfully on port ${this.config.daemonPort}`);
120
120
 
121
121
  return {
122
122
  success: true,
@@ -167,9 +167,9 @@ export class BrowserDaemon {
167
167
  `--disable-extensions-except=${extensionPath}`,
168
168
  `--load-extension=${extensionPath}`
169
169
  );
170
- console.log(`[BrowserDaemon] Loading ChromeDebug extension from: ${extensionPath}`);
170
+ console.error(`[BrowserDaemon] Loading ChromeDebug extension from: ${extensionPath}`);
171
171
  } else {
172
- console.warn('[BrowserDaemon] ChromeDebug extension not found. Visual selection features will be unavailable.');
172
+ console.error('[BrowserDaemon] ChromeDebug extension not found. Visual selection features will be unavailable.');
173
173
  }
174
174
 
175
175
  const launchOptions = {
@@ -182,7 +182,7 @@ export class BrowserDaemon {
182
182
  userDataDir: await this.createSharedUserDataDir()
183
183
  };
184
184
 
185
- console.log(`[BrowserDaemon] Launching Chrome with debugging port ${this.config.daemonPort}`);
185
+ console.error(`[BrowserDaemon] Launching Chrome with debugging port ${this.config.daemonPort}`);
186
186
  return await puppeteer.launch(launchOptions);
187
187
  }
188
188
 
@@ -196,7 +196,7 @@ export class BrowserDaemon {
196
196
  const sharedUserDataDir = path.join(tmpDir, sharedDirName);
197
197
 
198
198
  await fs.mkdir(sharedUserDataDir, { recursive: true });
199
- console.log(`[BrowserDaemon] Created shared user data directory: ${sharedUserDataDir}`);
199
+ console.error(`[BrowserDaemon] Created shared user data directory: ${sharedUserDataDir}`);
200
200
 
201
201
  return sharedUserDataDir;
202
202
  }
@@ -262,7 +262,7 @@ export class BrowserDaemon {
262
262
  lastActivity: sessionData.lastActivity
263
263
  });
264
264
 
265
- console.log(`[BrowserDaemon] Allocated session ${sessionId} for Claude instance ${claudeInstanceId}`);
265
+ console.error(`[BrowserDaemon] Allocated session ${sessionId} for Claude instance ${claudeInstanceId}`);
266
266
 
267
267
  return {
268
268
  success: true,
@@ -290,7 +290,7 @@ export class BrowserDaemon {
290
290
  */
291
291
  async deallocateSession(sessionId) {
292
292
  if (!sessionId || !this.activeSessions.has(sessionId)) {
293
- console.warn(`[BrowserDaemon] Attempted to deallocate non-existent session: ${sessionId}`);
293
+ console.error(`[BrowserDaemon] Attempted to deallocate non-existent session: ${sessionId}`);
294
294
  return false;
295
295
  }
296
296
 
@@ -311,7 +311,7 @@ export class BrowserDaemon {
311
311
  // Remove from persistent registry
312
312
  await this.sessionRegistry.removeSession(sessionId);
313
313
 
314
- console.log(`[BrowserDaemon] Deallocated session ${sessionId}`);
314
+ console.error(`[BrowserDaemon] Deallocated session ${sessionId}`);
315
315
  return true;
316
316
  } catch (error) {
317
317
  console.error(`[BrowserDaemon] Error deallocating session ${sessionId}:`, error);
@@ -369,7 +369,7 @@ export class BrowserDaemon {
369
369
  }
370
370
 
371
371
  this.healthCheckTimer = setInterval(this.performHealthCheck, this.config.healthCheckInterval);
372
- console.log(`[BrowserDaemon] Started health check with ${this.config.healthCheckInterval}ms interval`);
372
+ console.error(`[BrowserDaemon] Started health check with ${this.config.healthCheckInterval}ms interval`);
373
373
  }
374
374
 
375
375
  /**
@@ -381,7 +381,7 @@ export class BrowserDaemon {
381
381
  try {
382
382
  // Check browser health - log status but don't restart
383
383
  if (!this.browser || this.browser.process()?.killed) {
384
- console.warn('[BrowserDaemon] Browser process is down. Claude can launch a new browser when needed.');
384
+ console.error('[BrowserDaemon] Browser process is down. Claude can launch a new browser when needed.');
385
385
  // Don't restart - let Claude handle recovery via MCP tools
386
386
  return;
387
387
  }
@@ -399,12 +399,12 @@ export class BrowserDaemon {
399
399
 
400
400
  // Clean up expired sessions
401
401
  for (const sessionId of expiredSessions) {
402
- console.log(`[BrowserDaemon] Cleaning up expired session: ${sessionId}`);
402
+ console.error(`[BrowserDaemon] Cleaning up expired session: ${sessionId}`);
403
403
  await this.deallocateSession(sessionId);
404
404
  }
405
405
 
406
406
  if (expiredSessions.length > 0) {
407
- console.log(`[BrowserDaemon] Cleaned up ${expiredSessions.length} expired sessions`);
407
+ console.error(`[BrowserDaemon] Cleaned up ${expiredSessions.length} expired sessions`);
408
408
  }
409
409
 
410
410
  } catch (error) {
@@ -437,7 +437,7 @@ export class BrowserDaemon {
437
437
  if (this.isShuttingDown) return;
438
438
 
439
439
  this.isShuttingDown = true;
440
- console.log('[BrowserDaemon] Initiating graceful shutdown...');
440
+ console.error('[BrowserDaemon] Initiating graceful shutdown...');
441
441
 
442
442
  try {
443
443
  // Stop health checks
@@ -447,7 +447,7 @@ export class BrowserDaemon {
447
447
  }
448
448
 
449
449
  // Clean up all active sessions
450
- console.log(`[BrowserDaemon] Cleaning up ${this.activeSessions.size} active sessions...`);
450
+ console.error(`[BrowserDaemon] Cleaning up ${this.activeSessions.size} active sessions...`);
451
451
  for (const sessionId of this.activeSessions.keys()) {
452
452
  await this.deallocateSession(sessionId);
453
453
  }
@@ -462,7 +462,7 @@ export class BrowserDaemon {
462
462
  await this.cleanup();
463
463
 
464
464
  this.isRunning = false;
465
- console.log('[BrowserDaemon] Graceful shutdown completed');
465
+ console.error('[BrowserDaemon] Graceful shutdown completed');
466
466
  } catch (error) {
467
467
  console.error('[BrowserDaemon] Error during graceful shutdown:', error);
468
468
  }
@@ -157,7 +157,7 @@ export class GitSafetyService {
157
157
  backupInfo.created = true;
158
158
  } else {
159
159
  // Fallback to file copy if git stash fails
160
- console.warn('Git stash failed, falling back to file copy:', gitBackup.error);
160
+ console.error('Git stash failed, falling back to file copy:', gitBackup.error);
161
161
  const fileBackup = await this._createFileCopyBackup(projectPath, operationId);
162
162
  backupInfo.strategy = 'file-copy';
163
163
  backupInfo.backupPath = fileBackup.backupPath;
@@ -502,7 +502,7 @@ export class GitSafetyService {
502
502
  mkdirSync(destDir, { recursive: true });
503
503
  copyFileSync(file, destPath);
504
504
  } catch (copyError) {
505
- console.warn(`Failed to copy file ${file}:`, copyError.message);
505
+ console.error(`Failed to copy file ${file}:`, copyError.message);
506
506
  }
507
507
  }
508
508
 
@@ -670,6 +670,6 @@ export class GitSafetyService {
670
670
  // Clean up any temporary resources
671
671
  this.activeBackups.clear();
672
672
  this.operationHistory = [];
673
- console.log('GitSafetyService cleanup completed');
673
+ console.error('GitSafetyService cleanup completed');
674
674
  }
675
675
  }
@@ -41,7 +41,7 @@ export class HeartbeatManager {
41
41
  };
42
42
 
43
43
  this.heartbeats.set(sessionId, heartbeatData);
44
- console.log(`[HeartbeatManager] Started heartbeat for session ${sessionId}`);
44
+ console.error(`[HeartbeatManager] Started heartbeat for session ${sessionId}`);
45
45
  }
46
46
 
47
47
  /**
@@ -53,7 +53,7 @@ export class HeartbeatManager {
53
53
  if (heartbeatData) {
54
54
  clearInterval(heartbeatData.interval);
55
55
  this.heartbeats.delete(sessionId);
56
- console.log(`[HeartbeatManager] Stopped heartbeat for session ${sessionId}`);
56
+ console.error(`[HeartbeatManager] Stopped heartbeat for session ${sessionId}`);
57
57
  }
58
58
  }
59
59
 
@@ -75,9 +75,9 @@ export class HeartbeatManager {
75
75
  const heartbeatData = this.heartbeats.get(sessionId);
76
76
  if (heartbeatData) {
77
77
  heartbeatData.lastUpdate = new Date();
78
- console.log(`[HeartbeatManager] Manual heartbeat update for session ${sessionId}`);
78
+ console.error(`[HeartbeatManager] Manual heartbeat update for session ${sessionId}`);
79
79
  } else {
80
- console.warn(`[HeartbeatManager] Cannot update heartbeat for unknown session ${sessionId}`);
80
+ console.error(`[HeartbeatManager] Cannot update heartbeat for unknown session ${sessionId}`);
81
81
  }
82
82
  }
83
83
 
@@ -108,7 +108,7 @@ export class HeartbeatManager {
108
108
  }
109
109
 
110
110
  if (staleSessionIds.length > 0) {
111
- console.log(`[HeartbeatManager] Found ${staleSessionIds.length} stale heartbeats:`, staleSessionIds);
111
+ console.error(`[HeartbeatManager] Found ${staleSessionIds.length} stale heartbeats:`, staleSessionIds);
112
112
  }
113
113
 
114
114
  return staleSessionIds;
@@ -152,7 +152,7 @@ export class HeartbeatManager {
152
152
  * Stops all heartbeats (cleanup method)
153
153
  */
154
154
  stopAllHeartbeats() {
155
- console.log(`[HeartbeatManager] Stopping all heartbeats (${this.heartbeats.size} sessions)`);
155
+ console.error(`[HeartbeatManager] Stopping all heartbeats (${this.heartbeats.size} sessions)`);
156
156
 
157
157
  for (const [sessionId, heartbeatData] of this.heartbeats.entries()) {
158
158
  clearInterval(heartbeatData.interval);
@@ -173,7 +173,7 @@ export class HeartbeatManager {
173
173
  }
174
174
 
175
175
  if (staleSessionIds.length > 0) {
176
- console.log(`[HeartbeatManager] Auto-cleaned ${staleSessionIds.length} stale heartbeats`);
176
+ console.error(`[HeartbeatManager] Auto-cleaned ${staleSessionIds.length} stale heartbeats`);
177
177
  }
178
178
 
179
179
  return staleSessionIds;
@@ -67,7 +67,7 @@ async function findChromePilotProcesses() {
67
67
  // This allows the server to start on Windows without crashing
68
68
  // TODO: Implement proper Windows process management using tasklist or Node.js process handles
69
69
  if (process.platform === 'win32') {
70
- console.log('[ChromeDebug] Process cleanup skipped on Windows (not yet implemented)');
70
+ console.error('[ChromeDebug] Process cleanup skipped on Windows (not yet implemented)');
71
71
  return { pidsToKill: [], processDescriptions: [] };
72
72
  }
73
73