@dynamicu/chromedebug-mcp 2.2.0
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.
- package/CLAUDE.md +344 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/chrome-extension/README.md +41 -0
- package/chrome-extension/background.js +3917 -0
- package/chrome-extension/chrome-session-manager.js +706 -0
- package/chrome-extension/content.css +181 -0
- package/chrome-extension/content.js +3022 -0
- package/chrome-extension/data-buffer.js +435 -0
- package/chrome-extension/dom-tracker.js +411 -0
- package/chrome-extension/extension-config.js +78 -0
- package/chrome-extension/firebase-client.js +278 -0
- package/chrome-extension/firebase-config.js +32 -0
- package/chrome-extension/firebase-config.module.js +22 -0
- package/chrome-extension/firebase-config.module.template.js +27 -0
- package/chrome-extension/firebase-config.template.js +36 -0
- package/chrome-extension/frame-capture.js +407 -0
- package/chrome-extension/icon128.png +1 -0
- package/chrome-extension/icon16.png +1 -0
- package/chrome-extension/icon48.png +1 -0
- package/chrome-extension/license-helper.js +181 -0
- package/chrome-extension/logger.js +23 -0
- package/chrome-extension/manifest.json +73 -0
- package/chrome-extension/network-tracker.js +510 -0
- package/chrome-extension/offscreen.html +10 -0
- package/chrome-extension/options.html +203 -0
- package/chrome-extension/options.js +282 -0
- package/chrome-extension/pako.min.js +2 -0
- package/chrome-extension/performance-monitor.js +533 -0
- package/chrome-extension/pii-redactor.js +405 -0
- package/chrome-extension/popup.html +532 -0
- package/chrome-extension/popup.js +2446 -0
- package/chrome-extension/upload-manager.js +323 -0
- package/chrome-extension/web-vitals.iife.js +1 -0
- package/config/api-keys.json +11 -0
- package/config/chrome-pilot-config.json +45 -0
- package/package.json +126 -0
- package/scripts/cleanup-processes.js +109 -0
- package/scripts/config-manager.js +280 -0
- package/scripts/generate-extension-config.js +53 -0
- package/scripts/setup-security.js +64 -0
- package/src/capture/architecture.js +426 -0
- package/src/capture/error-handling-tests.md +38 -0
- package/src/capture/error-handling-types.ts +360 -0
- package/src/capture/index.js +508 -0
- package/src/capture/interfaces.js +625 -0
- package/src/capture/memory-manager.js +713 -0
- package/src/capture/types.js +342 -0
- package/src/chrome-controller.js +2658 -0
- package/src/cli.js +19 -0
- package/src/config-loader.js +303 -0
- package/src/database.js +2178 -0
- package/src/firebase-license-manager.js +462 -0
- package/src/firebase-privacy-guard.js +397 -0
- package/src/http-server.js +1516 -0
- package/src/index-direct.js +157 -0
- package/src/index-modular.js +219 -0
- package/src/index-monolithic-backup.js +2230 -0
- package/src/index.js +305 -0
- package/src/legacy/chrome-controller-old.js +1406 -0
- package/src/legacy/index-express.js +625 -0
- package/src/legacy/index-old.js +977 -0
- package/src/legacy/routes.js +260 -0
- package/src/legacy/shared-storage.js +101 -0
- package/src/logger.js +10 -0
- package/src/mcp/handlers/chrome-tool-handler.js +306 -0
- package/src/mcp/handlers/element-tool-handler.js +51 -0
- package/src/mcp/handlers/frame-tool-handler.js +957 -0
- package/src/mcp/handlers/request-handler.js +104 -0
- package/src/mcp/handlers/workflow-tool-handler.js +636 -0
- package/src/mcp/server.js +68 -0
- package/src/mcp/tools/index.js +701 -0
- package/src/middleware/auth.js +371 -0
- package/src/middleware/security.js +267 -0
- package/src/port-discovery.js +258 -0
- package/src/routes/admin.js +182 -0
- package/src/services/browser-daemon.js +494 -0
- package/src/services/chrome-service.js +375 -0
- package/src/services/failover-manager.js +412 -0
- package/src/services/git-safety-service.js +675 -0
- package/src/services/heartbeat-manager.js +200 -0
- package/src/services/http-client.js +195 -0
- package/src/services/process-manager.js +318 -0
- package/src/services/process-tracker.js +574 -0
- package/src/services/profile-manager.js +449 -0
- package/src/services/project-manager.js +415 -0
- package/src/services/session-manager.js +497 -0
- package/src/services/session-registry.js +491 -0
- package/src/services/unified-session-manager.js +678 -0
- package/src/shared-storage-old.js +267 -0
- package/src/standalone-server.js +53 -0
- package/src/utils/extension-path.js +145 -0
- package/src/utils.js +187 -0
- package/src/validation/log-transformer.js +125 -0
- package/src/validation/schemas.js +391 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process Tracker - Safe process management for ChromeDebug MCP
|
|
3
|
+
* Only tracks and manages processes explicitly registered by this application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import crypto from 'crypto';
|
|
10
|
+
|
|
11
|
+
// Global session ID for this process
|
|
12
|
+
let currentSessionId = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get or generate a session ID for this process
|
|
16
|
+
*/
|
|
17
|
+
function getSessionId() {
|
|
18
|
+
if (!currentSessionId) {
|
|
19
|
+
currentSessionId = crypto.randomBytes(8).toString('hex');
|
|
20
|
+
}
|
|
21
|
+
return currentSessionId;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set a custom session ID (used for coordinated multi-session environments)
|
|
26
|
+
*/
|
|
27
|
+
export function setSessionId(sessionId) {
|
|
28
|
+
if (typeof sessionId === 'string' && sessionId.match(/^[a-zA-Z0-9_-]+$/)) {
|
|
29
|
+
currentSessionId = sessionId;
|
|
30
|
+
console.log(`ChromeDebug session ID set to: ${sessionId}`);
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error('Session ID must be alphanumeric with dashes/underscores only');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the session-specific PID file path
|
|
38
|
+
*/
|
|
39
|
+
function getPidFilePath() {
|
|
40
|
+
const sessionId = getSessionId();
|
|
41
|
+
return path.join(os.tmpdir(), `.chromedebug-pids-${sessionId}.json`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Safely validates a process ID to prevent injection attacks
|
|
46
|
+
*/
|
|
47
|
+
function validateProcessId(pid) {
|
|
48
|
+
const pidStr = String(pid).trim();
|
|
49
|
+
if (!/^\d+$/.test(pidStr)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const numericPid = parseInt(pidStr, 10);
|
|
54
|
+
if (isNaN(numericPid) || numericPid <= 0 || numericPid > 4194304) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return numericPid;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Read the PID tracking file for this session
|
|
62
|
+
*/
|
|
63
|
+
function readPidFile() {
|
|
64
|
+
const pidFilePath = getPidFilePath();
|
|
65
|
+
try {
|
|
66
|
+
if (fs.existsSync(pidFilePath)) {
|
|
67
|
+
const data = fs.readFileSync(pidFilePath, 'utf8');
|
|
68
|
+
const parsed = JSON.parse(data);
|
|
69
|
+
return {
|
|
70
|
+
processes: parsed.processes || [],
|
|
71
|
+
sessionId: parsed.sessionId || getSessionId(),
|
|
72
|
+
lastUpdated: parsed.lastUpdated || new Date().toISOString()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(`Failed to read PID file ${pidFilePath}:`, error);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
processes: [],
|
|
80
|
+
sessionId: getSessionId(),
|
|
81
|
+
lastUpdated: new Date().toISOString()
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Write the PID tracking file for this session
|
|
87
|
+
*/
|
|
88
|
+
function writePidFile(data) {
|
|
89
|
+
const pidFilePath = getPidFilePath();
|
|
90
|
+
try {
|
|
91
|
+
const content = {
|
|
92
|
+
...data,
|
|
93
|
+
sessionId: getSessionId(),
|
|
94
|
+
lastUpdated: new Date().toISOString()
|
|
95
|
+
};
|
|
96
|
+
fs.writeFileSync(pidFilePath, JSON.stringify(content, null, 2));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(`Failed to write PID file ${pidFilePath}:`, error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Register a process for tracking
|
|
104
|
+
*/
|
|
105
|
+
export function registerProcess(pid, type = 'unknown') {
|
|
106
|
+
const validatedPid = validateProcessId(pid);
|
|
107
|
+
if (!validatedPid) {
|
|
108
|
+
console.error(`Cannot register invalid process ID: ${pid}`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const data = readPidFile();
|
|
113
|
+
|
|
114
|
+
// Check if already registered
|
|
115
|
+
const existing = data.processes.find(p => p.pid === validatedPid);
|
|
116
|
+
if (existing) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Add new process
|
|
121
|
+
data.processes.push({
|
|
122
|
+
pid: validatedPid,
|
|
123
|
+
type,
|
|
124
|
+
startTime: new Date().toISOString(),
|
|
125
|
+
command: process.argv.join(' ')
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
writePidFile(data);
|
|
129
|
+
console.log(`Registered ChromeDebug process: PID ${validatedPid} (${type})`);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Unregister a process from tracking
|
|
135
|
+
*/
|
|
136
|
+
export function unregisterProcess(pid) {
|
|
137
|
+
const validatedPid = validateProcessId(pid);
|
|
138
|
+
if (!validatedPid) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const data = readPidFile();
|
|
143
|
+
const initialLength = data.processes.length;
|
|
144
|
+
data.processes = data.processes.filter(p => p.pid !== validatedPid);
|
|
145
|
+
|
|
146
|
+
if (data.processes.length < initialLength) {
|
|
147
|
+
writePidFile(data);
|
|
148
|
+
console.log(`Unregistered ChromeDebug process: PID ${validatedPid}`);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get all registered processes
|
|
157
|
+
*/
|
|
158
|
+
export function getRegisteredProcesses() {
|
|
159
|
+
return readPidFile().processes;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check if a process is still running
|
|
164
|
+
*/
|
|
165
|
+
function isProcessRunning(pid) {
|
|
166
|
+
try {
|
|
167
|
+
// process.kill with signal 0 checks if process exists without killing it
|
|
168
|
+
process.kill(pid, 0);
|
|
169
|
+
return true;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Clean up dead processes from tracking file
|
|
177
|
+
*/
|
|
178
|
+
export function cleanupDeadProcesses() {
|
|
179
|
+
const data = readPidFile();
|
|
180
|
+
const initialCount = data.processes.length;
|
|
181
|
+
|
|
182
|
+
if (initialCount === 0) {
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const aliveProcesses = [];
|
|
187
|
+
let removedCount = 0;
|
|
188
|
+
|
|
189
|
+
for (const proc of data.processes) {
|
|
190
|
+
if (isProcessRunning(proc.pid)) {
|
|
191
|
+
aliveProcesses.push(proc);
|
|
192
|
+
} else {
|
|
193
|
+
console.log(`Removing dead process from tracking: PID ${proc.pid} (${proc.type})`);
|
|
194
|
+
removedCount++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (removedCount > 0) {
|
|
199
|
+
data.processes = aliveProcesses;
|
|
200
|
+
writePidFile(data);
|
|
201
|
+
console.log(`Cleaned up ${removedCount} dead processes from tracking`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return removedCount;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Kill a registered process safely
|
|
209
|
+
*/
|
|
210
|
+
export async function killRegisteredProcess(pid, signal = 'SIGTERM') {
|
|
211
|
+
const validatedPid = validateProcessId(pid);
|
|
212
|
+
if (!validatedPid) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Check if process is registered
|
|
217
|
+
const data = readPidFile();
|
|
218
|
+
const registered = data.processes.find(p => p.pid === validatedPid);
|
|
219
|
+
if (!registered) {
|
|
220
|
+
console.error(`Process ${validatedPid} is not registered with ChromeDebug`);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
process.kill(validatedPid, signal);
|
|
226
|
+
console.log(`Sent ${signal} to registered ChromeDebug process: PID ${validatedPid}`);
|
|
227
|
+
|
|
228
|
+
// If successful and using SIGKILL, unregister immediately
|
|
229
|
+
if (signal === 'SIGKILL') {
|
|
230
|
+
unregisterProcess(validatedPid);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return true;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (error.code === 'ESRCH') {
|
|
236
|
+
// Process doesn't exist, remove from tracking
|
|
237
|
+
unregisterProcess(validatedPid);
|
|
238
|
+
return true;
|
|
239
|
+
} else if (error.code === 'EPERM') {
|
|
240
|
+
console.error(`Permission denied killing process ${validatedPid}`);
|
|
241
|
+
return false;
|
|
242
|
+
} else {
|
|
243
|
+
console.error(`Error killing process ${validatedPid}: ${error.message}`);
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Kill all registered ChromeDebug processes
|
|
251
|
+
*/
|
|
252
|
+
export async function killAllRegisteredProcesses() {
|
|
253
|
+
const processes = getRegisteredProcesses();
|
|
254
|
+
const results = {
|
|
255
|
+
killed: [],
|
|
256
|
+
failed: [],
|
|
257
|
+
total: processes.length
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if (processes.length === 0) {
|
|
261
|
+
console.log('No registered ChromeDebug processes to kill');
|
|
262
|
+
return results;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
console.log(`Found ${processes.length} registered ChromeDebug processes, cleaning up...`);
|
|
266
|
+
|
|
267
|
+
// First try SIGTERM for graceful shutdown
|
|
268
|
+
for (const proc of processes) {
|
|
269
|
+
const success = await killRegisteredProcess(proc.pid, 'SIGTERM');
|
|
270
|
+
if (success) {
|
|
271
|
+
results.killed.push(proc.pid);
|
|
272
|
+
} else {
|
|
273
|
+
results.failed.push(proc.pid);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Wait for graceful shutdown
|
|
278
|
+
if (results.failed.length > 0) {
|
|
279
|
+
console.log('Waiting 2 seconds for graceful shutdown...');
|
|
280
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
281
|
+
|
|
282
|
+
// Try SIGKILL for remaining processes
|
|
283
|
+
const stillRunning = results.failed.slice();
|
|
284
|
+
results.failed = [];
|
|
285
|
+
|
|
286
|
+
for (const pid of stillRunning) {
|
|
287
|
+
const success = await killRegisteredProcess(pid, 'SIGKILL');
|
|
288
|
+
if (success) {
|
|
289
|
+
const index = results.killed.indexOf(pid);
|
|
290
|
+
if (index === -1) {
|
|
291
|
+
results.killed.push(pid);
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
results.failed.push(pid);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Clean up the tracking file
|
|
300
|
+
cleanupDeadProcesses();
|
|
301
|
+
|
|
302
|
+
return results;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Find ChromeDebug processes using pattern matching (for untracked processes)
|
|
307
|
+
* This is a fallback for processes started before tracking was implemented
|
|
308
|
+
*/
|
|
309
|
+
export async function findUntrackedChromePilotProcesses() {
|
|
310
|
+
const currentPid = process.pid;
|
|
311
|
+
const foundProcesses = [];
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const { spawn } = await import('child_process');
|
|
315
|
+
|
|
316
|
+
return new Promise((resolve) => {
|
|
317
|
+
const ps = spawn('ps', ['aux'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
318
|
+
let stdout = '';
|
|
319
|
+
|
|
320
|
+
ps.stdout.on('data', (data) => {
|
|
321
|
+
stdout += data.toString();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
ps.on('close', (code) => {
|
|
325
|
+
if (code !== 0) {
|
|
326
|
+
console.error('Failed to get process list');
|
|
327
|
+
resolve([]);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const lines = stdout.trim().split('\n');
|
|
332
|
+
const registeredPids = new Set(getRegisteredProcesses().map(p => p.pid));
|
|
333
|
+
|
|
334
|
+
for (const line of lines) {
|
|
335
|
+
const parts = line.trim().split(/\s+/);
|
|
336
|
+
if (parts.length < 2) continue;
|
|
337
|
+
|
|
338
|
+
const pid = validateProcessId(parts[1]);
|
|
339
|
+
if (!pid || pid === currentPid) continue;
|
|
340
|
+
|
|
341
|
+
// Skip processes that are already registered
|
|
342
|
+
if (registeredPids.has(pid)) continue;
|
|
343
|
+
|
|
344
|
+
// Check if it's a ChromeDebug/ChromePilot process
|
|
345
|
+
let processType = '';
|
|
346
|
+
if (line.includes('src/index.js')) {
|
|
347
|
+
processType = 'untracked-mcp-server';
|
|
348
|
+
} else if (line.includes('src/http-server.js')) {
|
|
349
|
+
processType = 'untracked-http-server';
|
|
350
|
+
} else if (line.includes('standalone-server.js')) {
|
|
351
|
+
processType = 'untracked-standalone-server';
|
|
352
|
+
} else if (line.includes('chromedebug-mcp') && line.includes('node')) {
|
|
353
|
+
processType = 'untracked-chromedebug-process';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (processType) {
|
|
357
|
+
foundProcesses.push({
|
|
358
|
+
pid,
|
|
359
|
+
type: processType,
|
|
360
|
+
description: `${processType} (PID: ${pid})`,
|
|
361
|
+
commandLine: line
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
resolve(foundProcesses);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
ps.on('error', (error) => {
|
|
370
|
+
console.error('Error running ps command:', error);
|
|
371
|
+
resolve([]);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error('Error finding untracked ChromePilot processes:', error);
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Kill untracked ChromeDebug processes (emergency cleanup)
|
|
382
|
+
*/
|
|
383
|
+
export async function killUntrackedChromePilotProcesses() {
|
|
384
|
+
const untrackedProcesses = await findUntrackedChromePilotProcesses();
|
|
385
|
+
const results = {
|
|
386
|
+
found: untrackedProcesses,
|
|
387
|
+
killed: [],
|
|
388
|
+
failed: [],
|
|
389
|
+
total: untrackedProcesses.length
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (untrackedProcesses.length === 0) {
|
|
393
|
+
console.log('No untracked ChromeDebug processes found');
|
|
394
|
+
return results;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
console.log(`Found ${untrackedProcesses.length} untracked ChromeDebug processes:`);
|
|
398
|
+
untrackedProcesses.forEach(proc => {
|
|
399
|
+
console.log(` - ${proc.description}`);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Try SIGTERM first
|
|
403
|
+
for (const proc of untrackedProcesses) {
|
|
404
|
+
try {
|
|
405
|
+
process.kill(proc.pid, 'SIGTERM');
|
|
406
|
+
console.log(`Sent SIGTERM to untracked process: PID ${proc.pid}`);
|
|
407
|
+
results.killed.push(proc.pid);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (error.code === 'ESRCH') {
|
|
410
|
+
// Process doesn't exist, count as success
|
|
411
|
+
results.killed.push(proc.pid);
|
|
412
|
+
} else {
|
|
413
|
+
console.error(`Failed to kill process ${proc.pid}: ${error.message}`);
|
|
414
|
+
results.failed.push(proc.pid);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Wait for graceful shutdown
|
|
420
|
+
if (results.failed.length > 0) {
|
|
421
|
+
console.log('Waiting 2 seconds for graceful shutdown...');
|
|
422
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
423
|
+
|
|
424
|
+
// Try SIGKILL for remaining processes
|
|
425
|
+
const stillFailed = [];
|
|
426
|
+
for (const pid of results.failed) {
|
|
427
|
+
try {
|
|
428
|
+
process.kill(pid, 'SIGKILL');
|
|
429
|
+
console.log(`Sent SIGKILL to stubborn process: PID ${pid}`);
|
|
430
|
+
// Move from failed to killed
|
|
431
|
+
const index = results.failed.indexOf(pid);
|
|
432
|
+
results.failed.splice(index, 1);
|
|
433
|
+
if (!results.killed.includes(pid)) {
|
|
434
|
+
results.killed.push(pid);
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
if (error.code !== 'ESRCH') {
|
|
438
|
+
stillFailed.push(pid);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
results.failed = stillFailed;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return results;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Register current process and set up cleanup on exit
|
|
450
|
+
*/
|
|
451
|
+
export function setupProcessTracking(type = 'mcp-server') {
|
|
452
|
+
const currentPid = process.pid;
|
|
453
|
+
|
|
454
|
+
// Clean up any dead processes first
|
|
455
|
+
cleanupDeadProcesses();
|
|
456
|
+
|
|
457
|
+
// Register this process
|
|
458
|
+
const registered = registerProcess(currentPid, type);
|
|
459
|
+
if (!registered) {
|
|
460
|
+
console.error(`Failed to register process ${currentPid} for tracking`);
|
|
461
|
+
return currentPid;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Set up robust cleanup on process exit
|
|
465
|
+
const cleanup = (reason = 'unknown') => {
|
|
466
|
+
try {
|
|
467
|
+
console.log(`Process ${currentPid} exiting (${reason}), cleaning up...`);
|
|
468
|
+
unregisterProcess(currentPid);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error(`Error during process cleanup: ${error.message}`);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// Handle various exit scenarios
|
|
475
|
+
process.on('exit', () => cleanup('exit'));
|
|
476
|
+
process.on('SIGINT', () => {
|
|
477
|
+
cleanup('SIGINT');
|
|
478
|
+
process.exit(0);
|
|
479
|
+
});
|
|
480
|
+
process.on('SIGTERM', () => {
|
|
481
|
+
cleanup('SIGTERM');
|
|
482
|
+
process.exit(0);
|
|
483
|
+
});
|
|
484
|
+
process.on('SIGHUP', () => {
|
|
485
|
+
cleanup('SIGHUP');
|
|
486
|
+
process.exit(0);
|
|
487
|
+
});
|
|
488
|
+
process.on('uncaughtException', (error) => {
|
|
489
|
+
console.error('Uncaught exception:', error);
|
|
490
|
+
cleanup('uncaughtException');
|
|
491
|
+
process.exit(1);
|
|
492
|
+
});
|
|
493
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
494
|
+
console.error('Unhandled rejection at:', promise, 'reason:', reason);
|
|
495
|
+
cleanup('unhandledRejection');
|
|
496
|
+
process.exit(1);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
console.log(`Process tracking setup complete for PID ${currentPid} (${type}) [Session: ${getSessionId()}]`);
|
|
500
|
+
return currentPid;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Setup process tracking with enhanced session isolation options
|
|
505
|
+
*/
|
|
506
|
+
export function setupProcessTrackingWithOptions(type = 'mcp-server', options = {}) {
|
|
507
|
+
const {
|
|
508
|
+
sessionId,
|
|
509
|
+
skipCleanup = false,
|
|
510
|
+
verbose = false
|
|
511
|
+
} = options;
|
|
512
|
+
|
|
513
|
+
// Set session ID if provided
|
|
514
|
+
if (sessionId) {
|
|
515
|
+
setSessionId(sessionId);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const currentSessionId = getSessionId();
|
|
519
|
+
|
|
520
|
+
if (verbose) {
|
|
521
|
+
console.log(`ChromeDebug MCP starting with session ID: ${currentSessionId}`);
|
|
522
|
+
console.log(`Skip cleanup: ${skipCleanup}`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Optionally skip cleanup of dead processes
|
|
526
|
+
if (!skipCleanup) {
|
|
527
|
+
const removedCount = cleanupDeadProcesses();
|
|
528
|
+
if (removedCount > 0 && verbose) {
|
|
529
|
+
console.log(`Cleaned up ${removedCount} dead processes from session ${currentSessionId}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Setup tracking for this process
|
|
534
|
+
return setupProcessTracking(type);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get current session information
|
|
539
|
+
*/
|
|
540
|
+
export function getSessionInfo() {
|
|
541
|
+
const sessionId = getSessionId();
|
|
542
|
+
const data = readPidFile();
|
|
543
|
+
return {
|
|
544
|
+
sessionId,
|
|
545
|
+
pidFile: getPidFilePath(),
|
|
546
|
+
processes: data.processes,
|
|
547
|
+
processCount: data.processes.length,
|
|
548
|
+
lastUpdated: data.lastUpdated
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Clean up all session files and processes for this session
|
|
554
|
+
*/
|
|
555
|
+
export async function cleanupCurrentSession() {
|
|
556
|
+
const sessionId = getSessionId();
|
|
557
|
+
console.log(`Cleaning up session: ${sessionId}`);
|
|
558
|
+
|
|
559
|
+
// Kill all processes in this session
|
|
560
|
+
const results = await killAllRegisteredProcesses();
|
|
561
|
+
|
|
562
|
+
// Remove the PID file
|
|
563
|
+
const pidFilePath = getPidFilePath();
|
|
564
|
+
try {
|
|
565
|
+
if (fs.existsSync(pidFilePath)) {
|
|
566
|
+
fs.unlinkSync(pidFilePath);
|
|
567
|
+
console.log(`Removed session PID file: ${pidFilePath}`);
|
|
568
|
+
}
|
|
569
|
+
} catch (error) {
|
|
570
|
+
console.error(`Failed to remove PID file: ${error.message}`);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return results;
|
|
574
|
+
}
|