@dynamicu/chromedebug-mcp 2.6.7 → 2.7.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.
Files changed (39) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +1 -1
  3. package/chrome-extension/background.js +611 -505
  4. package/chrome-extension/browser-recording-manager.js +1 -1
  5. package/chrome-extension/chrome-debug-logger.js +168 -0
  6. package/chrome-extension/console-interception-library.js +430 -0
  7. package/chrome-extension/content.css +16 -16
  8. package/chrome-extension/content.js +458 -126
  9. package/chrome-extension/extension-config.js +1 -1
  10. package/chrome-extension/license-helper.js +26 -0
  11. package/chrome-extension/manifest.free.json +0 -3
  12. package/chrome-extension/options.js +1 -1
  13. package/chrome-extension/popup.html +221 -191
  14. package/chrome-extension/popup.js +88 -379
  15. package/chrome-extension/pro/enhanced-capture.js +406 -0
  16. package/chrome-extension/pro/frame-editor.html +410 -0
  17. package/chrome-extension/pro/frame-editor.js +1496 -0
  18. package/chrome-extension/pro/function-tracker.js +843 -0
  19. package/chrome-extension/pro/jszip.min.js +13 -0
  20. package/dist/chromedebug-extension-free.zip +0 -0
  21. package/package.json +3 -1
  22. package/scripts/webpack.config.free.cjs +8 -8
  23. package/scripts/webpack.config.pro.cjs +2 -0
  24. package/src/cli.js +2 -2
  25. package/src/database.js +55 -7
  26. package/src/index.js +9 -6
  27. package/src/mcp/server.js +2 -2
  28. package/src/services/process-manager.js +10 -6
  29. package/src/services/process-tracker.js +10 -5
  30. package/src/services/profile-manager.js +17 -2
  31. package/src/validation/schemas.js +2 -2
  32. package/src/index-direct.js +0 -157
  33. package/src/index-modular.js +0 -219
  34. package/src/index-monolithic-backup.js +0 -2230
  35. package/src/legacy/chrome-controller-old.js +0 -1406
  36. package/src/legacy/index-express.js +0 -625
  37. package/src/legacy/index-old.js +0 -977
  38. package/src/legacy/routes.js +0 -260
  39. package/src/legacy/shared-storage.js +0 -101
@@ -190,7 +190,7 @@ class BrowserRecordingManager {
190
190
 
191
191
  // Generate filename
192
192
  const date = new Date(recording.startTime).toISOString().split('T')[0];
193
- const filename = `chrome-pilot-recording-${date}-${sessionId.slice(-8)}.json`;
193
+ const filename = `chrome-debug-recording-${date}-${sessionId.slice(-8)}.json`;
194
194
 
195
195
  return {
196
196
  blob,
@@ -0,0 +1,168 @@
1
+ /**
2
+ * ChromeDebugLogger - Dedicated logging system for Chrome Debug internal diagnostics
3
+ * Bypasses console interception to prevent polluting user workflow recordings
4
+ *
5
+ * Key Features:
6
+ * - Preserves original console before interception
7
+ * - Auto-enables in development mode
8
+ * - Works in service workers (chrome.storage.local) and content scripts (localStorage)
9
+ * - Includes source location tracking
10
+ * - Default disabled in production
11
+ * - Consistent prefix: [🔧 ChromeDebug:ComponentName]
12
+ */
13
+ class ChromeDebugLogger {
14
+ constructor() {
15
+ // CRITICAL: Save original console IMMEDIATELY (before interception occurs)
16
+ this.originalConsole = {
17
+ log: console.log.bind(console),
18
+ error: console.error.bind(console),
19
+ warn: console.warn.bind(console),
20
+ debug: console.debug.bind(console),
21
+ info: console.info.bind(console)
22
+ };
23
+
24
+ // Auto-enable in development mode
25
+ const isDev = typeof chrome !== 'undefined' &&
26
+ chrome.runtime?.getManifest?.()?.version_name?.includes('dev');
27
+ this.enabled = isDev || false;
28
+
29
+ this.prefix = '🔧 ChromeDebug';
30
+
31
+ // Load saved state (async, but don't block constructor)
32
+ this._loadState();
33
+ }
34
+
35
+ /**
36
+ * Enable Chrome Debug internal logging
37
+ */
38
+ enable() {
39
+ this.enabled = true;
40
+ this._saveState();
41
+ this._log('Logger', 'Chrome Debug logger enabled', null, 'info');
42
+ }
43
+
44
+ /**
45
+ * Disable Chrome Debug internal logging
46
+ */
47
+ disable() {
48
+ this._log('Logger', 'Chrome Debug logger disabled', null, 'info');
49
+ this.enabled = false;
50
+ this._saveState();
51
+ }
52
+
53
+ /**
54
+ * Check if logging is enabled
55
+ */
56
+ isEnabled() {
57
+ return this.enabled;
58
+ }
59
+
60
+ /**
61
+ * Log informational message
62
+ */
63
+ log(component, message, data = null) {
64
+ if (!this.enabled) return;
65
+ this._log(component, message, data, 'log');
66
+ }
67
+
68
+ /**
69
+ * Log error message
70
+ */
71
+ error(component, message, data = null) {
72
+ if (!this.enabled) return;
73
+ this._log(component, message, data, 'error');
74
+ }
75
+
76
+ /**
77
+ * Log warning message
78
+ */
79
+ warn(component, message, data = null) {
80
+ if (!this.enabled) return;
81
+ this._log(component, message, data, 'warn');
82
+ }
83
+
84
+ /**
85
+ * Log debug message
86
+ */
87
+ debug(component, message, data = null) {
88
+ if (!this.enabled) return;
89
+ this._log(component, message, data, 'debug');
90
+ }
91
+
92
+ /**
93
+ * Internal logging implementation
94
+ */
95
+ _log(component, message, data, level = 'log') {
96
+ try {
97
+ // Capture source location for debugging
98
+ const error = new Error();
99
+ const stack = error.stack?.split('\n')[3]; // Caller's location
100
+ const location = stack?.match(/\((.*?)\)/)?.[1] || '';
101
+
102
+ const prefix = `[${this.prefix}:${component}]`;
103
+ const suffix = location ? ` @${location.split('/').pop()}` : '';
104
+
105
+ if (data !== null && data !== undefined) {
106
+ this.originalConsole[level](prefix, message + suffix, data);
107
+ } else {
108
+ this.originalConsole[level](prefix, message + suffix);
109
+ }
110
+ } catch (e) {
111
+ // Fallback if logging fails
112
+ this.originalConsole.error('ChromeDebugLogger error:', e);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Save enabled state
118
+ */
119
+ async _saveState() {
120
+ try {
121
+ if (typeof chrome !== 'undefined' && chrome.storage) {
122
+ // Service worker context
123
+ await chrome.storage.local.set({ chromedebug_logger_enabled: this.enabled });
124
+ } else if (typeof localStorage !== 'undefined') {
125
+ // Content/popup context
126
+ localStorage.setItem('chromedebug_logger_enabled', this.enabled.toString());
127
+ }
128
+ } catch (e) {
129
+ // Storage might not be available
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Load enabled state
135
+ */
136
+ async _loadState() {
137
+ try {
138
+ if (typeof chrome !== 'undefined' && chrome.storage) {
139
+ // Service worker context
140
+ const result = await chrome.storage.local.get('chromedebug_logger_enabled');
141
+ if (result.chromedebug_logger_enabled !== undefined) {
142
+ this.enabled = result.chromedebug_logger_enabled;
143
+ }
144
+ } else if (typeof localStorage !== 'undefined') {
145
+ // Content/popup context
146
+ const saved = localStorage.getItem('chromedebug_logger_enabled');
147
+ if (saved !== null) {
148
+ this.enabled = saved === 'true';
149
+ }
150
+ }
151
+ } catch (e) {
152
+ // Storage might not be available
153
+ }
154
+ }
155
+ }
156
+
157
+ // Create global singleton instance
158
+ const chromeDebugLogger = new ChromeDebugLogger();
159
+
160
+ // Export for different contexts
161
+ if (typeof window !== 'undefined') {
162
+ window.chromeDebugLogger = chromeDebugLogger;
163
+ window.ChromeDebugLogger = ChromeDebugLogger;
164
+ }
165
+ if (typeof self !== 'undefined') {
166
+ self.chromeDebugLogger = chromeDebugLogger;
167
+ self.ChromeDebugLogger = ChromeDebugLogger;
168
+ }
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Console Interception Library
3
+ * Shared code for both Screen Recording and Workflow Recording
4
+ *
5
+ * This library provides console log interception and cleanup functionality
6
+ * that is used identically by both recording systems.
7
+ *
8
+ * DRY Principle: Don't Repeat Yourself
9
+ * - Reduces code duplication from 600+ lines to reusable functions
10
+ * - Single source of truth for console interception logic
11
+ * - Easier to maintain and update
12
+ *
13
+ * Usage Example:
14
+ * ```javascript
15
+ * // Screen recording config
16
+ * const screenConfig = {
17
+ * overrideFlagName: '__chromePilotConsoleOverridden',
18
+ * originalConsoleName: '__chromePilotOriginalConsole',
19
+ * relayFlagName: '__chromePilotConsoleRelay',
20
+ * messageType: 'chrome-debug-console-log',
21
+ * backgroundAction: 'consoleLog'
22
+ * };
23
+ *
24
+ * // Start interception
25
+ * await startConsoleInterception(tabId, screenConfig);
26
+ *
27
+ * // Stop interception
28
+ * await stopConsoleInterception(tabId, screenConfig);
29
+ * ```
30
+ */
31
+
32
+ // Console Interception Library - Direct function definitions for importScripts compatibility
33
+
34
+ /**
35
+ * Injectable function for MAIN world console interception
36
+ * This function will be serialized and injected into the page's MAIN execution context
37
+ *
38
+ * @param {string} overrideFlagName - Window property to track if already overridden
39
+ * @param {string} originalConsoleName - Window property to store original console methods
40
+ * @param {string} messageType - The postMessage type for log relay
41
+ * @returns {string} Status message
42
+ */
43
+ function injectConsoleInterceptor(overrideFlagName, originalConsoleName, messageType) {
44
+ // Check if we've already overridden console methods
45
+ if (window[overrideFlagName]) {
46
+ console.log('[Chrome Debug] Console already overridden, skipping');
47
+ return 'already_installed';
48
+ }
49
+ window[overrideFlagName] = true;
50
+ console.log('[Chrome Debug] Installing console interceptor');
51
+
52
+ // CRITICAL: Store original console methods in window for later restoration
53
+ window[originalConsoleName] = {
54
+ log: console.log,
55
+ error: console.error,
56
+ warn: console.warn,
57
+ info: console.info,
58
+ debug: console.debug,
59
+ trace: console.trace,
60
+ table: console.table,
61
+ dir: console.dir,
62
+ group: console.group,
63
+ groupEnd: console.groupEnd,
64
+ time: console.time,
65
+ timeEnd: console.timeEnd,
66
+ count: console.count
67
+ };
68
+
69
+ // Duck-type check for Error-like objects (handles cross-realm errors)
70
+ const isErrorLike = (obj) => {
71
+ if (obj instanceof Error) return true;
72
+ // Duck-type check for cross-realm errors (e.g., from iframes, web workers)
73
+ return obj !== null &&
74
+ typeof obj === 'object' &&
75
+ typeof obj.message === 'string' &&
76
+ (typeof obj.stack === 'string' || typeof obj.name === 'string');
77
+ };
78
+
79
+ // Serialize Error objects properly (message, stack, name are non-enumerable)
80
+ const serializeError = (err, depth = 0) => {
81
+ if (depth > 3) return '[Error: max depth reached]';
82
+
83
+ const result = {
84
+ name: err.name || 'Error',
85
+ message: err.message || '',
86
+ };
87
+
88
+ // Capture stack trace with intelligent truncation
89
+ if (err.stack) {
90
+ const stackLines = err.stack.split('\n');
91
+ let truncatedStack = [];
92
+ for (let i = 0; i < stackLines.length && i < 10; i++) {
93
+ truncatedStack.push(stackLines[i]);
94
+ // Stop at node_modules to reduce noise
95
+ if (stackLines[i].includes('node_modules')) {
96
+ truncatedStack.push(' ... (truncated at node_modules)');
97
+ break;
98
+ }
99
+ }
100
+ result.stack = truncatedStack.join('\n');
101
+ }
102
+
103
+ // Capture custom enumerable properties (e.g., Firebase error.code)
104
+ for (const key of Object.keys(err)) {
105
+ if (!['name', 'message', 'stack'].includes(key)) {
106
+ try {
107
+ if (err[key] instanceof Error || isErrorLike(err[key])) {
108
+ result[key] = serializeError(err[key], depth + 1);
109
+ } else {
110
+ result[key] = JSON.parse(JSON.stringify(err[key]));
111
+ }
112
+ } catch (e) {
113
+ result[key] = '[Could not serialize]';
114
+ }
115
+ }
116
+ }
117
+
118
+ // ES2022 error.cause support
119
+ if (err.cause) {
120
+ if (err.cause instanceof Error || isErrorLike(err.cause)) {
121
+ result.cause = serializeError(err.cause, depth + 1);
122
+ } else {
123
+ try {
124
+ result.cause = JSON.parse(JSON.stringify(err.cause));
125
+ } catch (e) {
126
+ result.cause = '[Could not serialize cause]';
127
+ }
128
+ }
129
+ }
130
+
131
+ return result;
132
+ };
133
+
134
+ // Create sendLog function with truncation logic
135
+ const sendLog = (level, args) => {
136
+ try {
137
+ // Pre-serialize arguments to strings to avoid structured clone errors
138
+ const serializedArgs = args.map(arg => {
139
+ try {
140
+ if (arg === null) return 'null';
141
+ if (arg === undefined) return 'undefined';
142
+ if (typeof arg === 'function') return '[Function: ' + (arg.name || 'anonymous') + ']';
143
+
144
+ // IMPORTANT: Check for Error objects BEFORE generic object handler
145
+ // Error properties (message, stack, name) are non-enumerable and JSON.stringify returns "{}"
146
+ if (arg instanceof Error || isErrorLike(arg)) {
147
+ const errorObj = serializeError(arg);
148
+ return JSON.stringify(errorObj, null, 2);
149
+ }
150
+
151
+ // Note: Element check removed - service workers have no DOM access, so this will never trigger
152
+ if (typeof arg === 'object') {
153
+ // Custom replacer to handle nested errors in objects
154
+ const errorReplacer = (key, value) => {
155
+ if (value instanceof Error || (value && isErrorLike(value))) {
156
+ return serializeError(value);
157
+ }
158
+ return value;
159
+ };
160
+
161
+ // Try to stringify, but limit depth to avoid circular references
162
+ let stringified = JSON.stringify(arg, errorReplacer, 2);
163
+
164
+ // Check if this looks like a base64 image and truncate it
165
+ if (stringified.includes('data:image/') && stringified.length > 1000) {
166
+ const match = stringified.match(/data:image\/([^;]+);base64,(.{0,100})/);
167
+ if (match) {
168
+ return `[Base64 Image: ${match[1]}, ${stringified.length} bytes total, truncated...]`;
169
+ }
170
+ }
171
+
172
+ // Truncate any extremely large strings
173
+ const maxLength = 5000;
174
+ if (stringified.length > maxLength) {
175
+ return stringified.substring(0, maxLength) + `... [TRUNCATED: ${stringified.length} total bytes]`;
176
+ }
177
+
178
+ return stringified;
179
+ }
180
+
181
+ // Also check for base64 strings directly
182
+ const strValue = String(arg);
183
+ if (strValue.includes('data:image/') && strValue.length > 1000) {
184
+ const match = strValue.match(/data:image\/([^;]+);base64,(.{0,100})/);
185
+ if (match) {
186
+ return `[Base64 Image: ${match[1]}, ${strValue.length} bytes total, truncated...]`;
187
+ }
188
+ }
189
+
190
+ // Truncate any extremely large strings
191
+ if (strValue.length > 5000) {
192
+ return strValue.substring(0, 5000) + `... [TRUNCATED: ${strValue.length} total bytes]`;
193
+ }
194
+
195
+ return strValue;
196
+ } catch (e) {
197
+ return '[Object: could not serialize]';
198
+ }
199
+ });
200
+
201
+ // Build the message string
202
+ const messageString = serializedArgs.join(' ');
203
+
204
+ // Filter out internal Chrome Debug/ChromePilot logs
205
+ // These are internal debugging messages that should not be captured in recordings
206
+ if (messageString.startsWith('[Chrome Debug]') ||
207
+ messageString.startsWith('[ChromePilot]') ||
208
+ messageString.startsWith('[Console Injection]') ||
209
+ messageString.startsWith('[Console Cleanup]')) {
210
+ return; // Skip internal logs
211
+ }
212
+
213
+ // Post message to content script
214
+ window.postMessage({
215
+ type: messageType,
216
+ log: {
217
+ level,
218
+ message: messageString,
219
+ timestamp: Date.now()
220
+ }
221
+ }, '*');
222
+ } catch (e) {
223
+ // Ignore errors when sending logs
224
+ }
225
+ };
226
+
227
+ // Override console methods
228
+ const methods = ['log', 'error', 'warn', 'info', 'debug', 'trace', 'table', 'dir', 'group', 'groupEnd', 'time', 'timeEnd', 'count'];
229
+ methods.forEach(method => {
230
+ const original = window[originalConsoleName][method];
231
+ console[method] = (...args) => {
232
+ sendLog(method, args);
233
+ original.apply(console, args);
234
+ };
235
+ });
236
+
237
+ return 'console_installed';
238
+ }
239
+
240
+ /**
241
+ * Injectable function for content script console relay
242
+ * Forwards console logs from MAIN world to background script
243
+ *
244
+ * @param {string} relayFlagName - Window property to track if relay is active
245
+ * @param {string} messageType - The postMessage type to listen for
246
+ * @param {string} backgroundAction - The action name to send to background script
247
+ */
248
+ function injectConsoleRelay(relayFlagName, messageType, backgroundAction) {
249
+ // Check if relay is already active
250
+ if (window[relayFlagName]) {
251
+ console.log('[Chrome Debug] Console relay already active, skipping');
252
+ return;
253
+ }
254
+
255
+ // Create and store the message handler
256
+ const messageHandler = (event) => {
257
+ if (event.data && event.data.type === messageType) {
258
+ // Forward to background script
259
+ chrome.runtime.sendMessage({
260
+ action: backgroundAction,
261
+ log: event.data.log
262
+ }).catch(() => {
263
+ // Background might not be available - that's OK
264
+ });
265
+ }
266
+ };
267
+
268
+ // Store reference for later removal
269
+ window[relayFlagName] = messageHandler;
270
+
271
+ // Listen for messages from main world
272
+ window.addEventListener('message', messageHandler);
273
+ console.log('[Chrome Debug] Console relay installed');
274
+ }
275
+
276
+ /**
277
+ * Injectable function for MAIN world console cleanup
278
+ * Restores original console methods
279
+ *
280
+ * @param {string} overrideFlagName - Window property that tracks override status
281
+ * @param {string} originalConsoleName - Window property with stored original methods
282
+ * @returns {object} Status object
283
+ */
284
+ function cleanupConsoleInterceptor(overrideFlagName, originalConsoleName) {
285
+ console.log('[Chrome Debug] ===== CLEANUP SCRIPT EXECUTING IN MAIN WORLD =====');
286
+ console.log('[Chrome Debug] ' + originalConsoleName + ' exists:', !!window[originalConsoleName]);
287
+ console.log('[Chrome Debug] ' + overrideFlagName + ':', window[overrideFlagName]);
288
+
289
+ if (window[originalConsoleName] && window[overrideFlagName]) {
290
+ console.log('[Chrome Debug] RESTORING original console methods');
291
+
292
+ // Restore all original console methods
293
+ Object.assign(console, window[originalConsoleName]);
294
+
295
+ // Clean up stored references
296
+ delete window[originalConsoleName];
297
+ delete window[overrideFlagName];
298
+
299
+ console.log('[Chrome Debug] ✅ Console methods restored successfully');
300
+ return { success: true, message: 'Console restored' };
301
+ } else {
302
+ console.warn('[Chrome Debug] ⚠️ No console override to restore');
303
+ return { success: false, message: 'No override found' };
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Injectable function for content script relay cleanup
309
+ * Removes the message event listener
310
+ *
311
+ * @param {string} relayFlagName - Window property with stored message handler
312
+ * @returns {object} Status object
313
+ */
314
+ function cleanupConsoleRelay(relayFlagName) {
315
+ console.log('[Chrome Debug] ===== CLEANUP SCRIPT EXECUTING IN CONTENT SCRIPT =====');
316
+ console.log('[Chrome Debug] ' + relayFlagName + ' exists:', !!window[relayFlagName]);
317
+
318
+ if (window[relayFlagName]) {
319
+ console.log('[Chrome Debug] REMOVING console relay listener');
320
+ window.removeEventListener('message', window[relayFlagName]);
321
+ delete window[relayFlagName];
322
+ console.log('[Chrome Debug] ✅ Console relay removed successfully');
323
+ return { success: true, message: 'Relay removed' };
324
+ } else {
325
+ console.warn('[Chrome Debug] ⚠️ No relay to remove');
326
+ return { success: false, message: 'No relay found' };
327
+ }
328
+ }
329
+
330
+ /**
331
+ * High-level API for starting console interception
332
+ *
333
+ * @param {number} tabId - Chrome tab ID
334
+ * @param {object} config - Configuration object
335
+ * @param {string} config.overrideFlagName - Window property for override tracking
336
+ * @param {string} config.originalConsoleName - Window property for original methods
337
+ * @param {string} config.relayFlagName - Window property for relay handler
338
+ * @param {string} config.messageType - PostMessage type for log relay
339
+ * @param {string} config.backgroundAction - Background script action name
340
+ * @returns {Promise<boolean>} True if successful, false otherwise
341
+ */
342
+ async function startConsoleInterception(tabId, config) {
343
+ try {
344
+ // Check if this tab allows content script injection
345
+ const tab = await chrome.tabs.get(tabId);
346
+ if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
347
+ console.log('Cannot inject console logger into restricted URL:', tab.url);
348
+ console.warn('WARNING: Console logs cannot be captured on restricted pages');
349
+ return false;
350
+ }
351
+
352
+ // Inject MAIN world console interceptor
353
+ const results = await chrome.scripting.executeScript({
354
+ target: { tabId: tabId },
355
+ world: 'MAIN',
356
+ func: injectConsoleInterceptor,
357
+ args: [config.overrideFlagName, config.originalConsoleName, config.messageType]
358
+ });
359
+
360
+ console.log('[Console Injection] MAIN world script injected:', results);
361
+
362
+ // Inject content script relay
363
+ await chrome.scripting.executeScript({
364
+ target: { tabId: tabId },
365
+ func: injectConsoleRelay,
366
+ args: [config.relayFlagName, config.messageType, config.backgroundAction]
367
+ });
368
+
369
+ console.log('[Console Injection] Content script relay injected successfully');
370
+ return true;
371
+ } catch (error) {
372
+ console.error('[Console Injection] Failed:', error);
373
+ return false;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * High-level API for stopping console interception and cleanup
379
+ *
380
+ * @param {number} tabId - Chrome tab ID
381
+ * @param {object} config - Configuration object (same as startConsoleInterception)
382
+ * @returns {Promise<boolean>} True if successful, false otherwise
383
+ */
384
+ async function stopConsoleInterception(tabId, config) {
385
+ try {
386
+ console.log('[Console Cleanup] Starting cleanup for tab:', tabId);
387
+
388
+ // Check if this tab allows content script injection
389
+ const tab = await chrome.tabs.get(tabId);
390
+ if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://')) {
391
+ console.warn('[Console Cleanup] Cannot inject into restricted URL:', tab.url);
392
+ return false;
393
+ }
394
+
395
+ // Restore console methods in MAIN world
396
+ const mainWorldResult = await chrome.scripting.executeScript({
397
+ target: { tabId: tabId },
398
+ world: 'MAIN',
399
+ func: cleanupConsoleInterceptor,
400
+ args: [config.overrideFlagName, config.originalConsoleName]
401
+ });
402
+ console.log('[Console Cleanup] MAIN world result:', mainWorldResult[0]?.result);
403
+
404
+ // Remove relay in content script
405
+ const contentScriptResult = await chrome.scripting.executeScript({
406
+ target: { tabId: tabId },
407
+ func: cleanupConsoleRelay,
408
+ args: [config.relayFlagName]
409
+ });
410
+ console.log('[Console Cleanup] Content script result:', contentScriptResult[0]?.result);
411
+
412
+ console.log('[Console Cleanup] Cleanup complete');
413
+ return true;
414
+ } catch (error) {
415
+ console.error('[Console Cleanup] Failed:', error);
416
+ return false;
417
+ }
418
+ }
419
+
420
+ // Export for use in Chrome extension background script (service worker context)
421
+ // Direct assignment without wrapper to avoid importScripts NetworkError
422
+ self.ConsoleInterceptionLibrary = {
423
+ injectConsoleInterceptor,
424
+ injectConsoleRelay,
425
+ cleanupConsoleInterceptor,
426
+ cleanupConsoleRelay,
427
+ startConsoleInterception,
428
+ stopConsoleInterception
429
+ };
430
+