@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.
Files changed (95) hide show
  1. package/CLAUDE.md +344 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/chrome-extension/README.md +41 -0
  5. package/chrome-extension/background.js +3917 -0
  6. package/chrome-extension/chrome-session-manager.js +706 -0
  7. package/chrome-extension/content.css +181 -0
  8. package/chrome-extension/content.js +3022 -0
  9. package/chrome-extension/data-buffer.js +435 -0
  10. package/chrome-extension/dom-tracker.js +411 -0
  11. package/chrome-extension/extension-config.js +78 -0
  12. package/chrome-extension/firebase-client.js +278 -0
  13. package/chrome-extension/firebase-config.js +32 -0
  14. package/chrome-extension/firebase-config.module.js +22 -0
  15. package/chrome-extension/firebase-config.module.template.js +27 -0
  16. package/chrome-extension/firebase-config.template.js +36 -0
  17. package/chrome-extension/frame-capture.js +407 -0
  18. package/chrome-extension/icon128.png +1 -0
  19. package/chrome-extension/icon16.png +1 -0
  20. package/chrome-extension/icon48.png +1 -0
  21. package/chrome-extension/license-helper.js +181 -0
  22. package/chrome-extension/logger.js +23 -0
  23. package/chrome-extension/manifest.json +73 -0
  24. package/chrome-extension/network-tracker.js +510 -0
  25. package/chrome-extension/offscreen.html +10 -0
  26. package/chrome-extension/options.html +203 -0
  27. package/chrome-extension/options.js +282 -0
  28. package/chrome-extension/pako.min.js +2 -0
  29. package/chrome-extension/performance-monitor.js +533 -0
  30. package/chrome-extension/pii-redactor.js +405 -0
  31. package/chrome-extension/popup.html +532 -0
  32. package/chrome-extension/popup.js +2446 -0
  33. package/chrome-extension/upload-manager.js +323 -0
  34. package/chrome-extension/web-vitals.iife.js +1 -0
  35. package/config/api-keys.json +11 -0
  36. package/config/chrome-pilot-config.json +45 -0
  37. package/package.json +126 -0
  38. package/scripts/cleanup-processes.js +109 -0
  39. package/scripts/config-manager.js +280 -0
  40. package/scripts/generate-extension-config.js +53 -0
  41. package/scripts/setup-security.js +64 -0
  42. package/src/capture/architecture.js +426 -0
  43. package/src/capture/error-handling-tests.md +38 -0
  44. package/src/capture/error-handling-types.ts +360 -0
  45. package/src/capture/index.js +508 -0
  46. package/src/capture/interfaces.js +625 -0
  47. package/src/capture/memory-manager.js +713 -0
  48. package/src/capture/types.js +342 -0
  49. package/src/chrome-controller.js +2658 -0
  50. package/src/cli.js +19 -0
  51. package/src/config-loader.js +303 -0
  52. package/src/database.js +2178 -0
  53. package/src/firebase-license-manager.js +462 -0
  54. package/src/firebase-privacy-guard.js +397 -0
  55. package/src/http-server.js +1516 -0
  56. package/src/index-direct.js +157 -0
  57. package/src/index-modular.js +219 -0
  58. package/src/index-monolithic-backup.js +2230 -0
  59. package/src/index.js +305 -0
  60. package/src/legacy/chrome-controller-old.js +1406 -0
  61. package/src/legacy/index-express.js +625 -0
  62. package/src/legacy/index-old.js +977 -0
  63. package/src/legacy/routes.js +260 -0
  64. package/src/legacy/shared-storage.js +101 -0
  65. package/src/logger.js +10 -0
  66. package/src/mcp/handlers/chrome-tool-handler.js +306 -0
  67. package/src/mcp/handlers/element-tool-handler.js +51 -0
  68. package/src/mcp/handlers/frame-tool-handler.js +957 -0
  69. package/src/mcp/handlers/request-handler.js +104 -0
  70. package/src/mcp/handlers/workflow-tool-handler.js +636 -0
  71. package/src/mcp/server.js +68 -0
  72. package/src/mcp/tools/index.js +701 -0
  73. package/src/middleware/auth.js +371 -0
  74. package/src/middleware/security.js +267 -0
  75. package/src/port-discovery.js +258 -0
  76. package/src/routes/admin.js +182 -0
  77. package/src/services/browser-daemon.js +494 -0
  78. package/src/services/chrome-service.js +375 -0
  79. package/src/services/failover-manager.js +412 -0
  80. package/src/services/git-safety-service.js +675 -0
  81. package/src/services/heartbeat-manager.js +200 -0
  82. package/src/services/http-client.js +195 -0
  83. package/src/services/process-manager.js +318 -0
  84. package/src/services/process-tracker.js +574 -0
  85. package/src/services/profile-manager.js +449 -0
  86. package/src/services/project-manager.js +415 -0
  87. package/src/services/session-manager.js +497 -0
  88. package/src/services/session-registry.js +491 -0
  89. package/src/services/unified-session-manager.js +678 -0
  90. package/src/shared-storage-old.js +267 -0
  91. package/src/standalone-server.js +53 -0
  92. package/src/utils/extension-path.js +145 -0
  93. package/src/utils.js +187 -0
  94. package/src/validation/log-transformer.js +125 -0
  95. package/src/validation/schemas.js +391 -0
@@ -0,0 +1,3022 @@
1
+ // ChromeDebug MCP Content Script v2.0
2
+ // Wrapped in IIFE to avoid global scope pollution and duplicate declarations
3
+ (function() {
4
+ 'use strict';
5
+
6
+ // Check if already loaded
7
+ if (window.chromePilotContentScriptLoaded) {
8
+ console.log('[ChromeDebug MCP v2.0] Content script already loaded, skipping re-injection');
9
+ return;
10
+ }
11
+
12
+ // Early bailout for user-configured restricted sites
13
+ const hostname = window.location.hostname.toLowerCase();
14
+
15
+ // Load user restrictions from storage and check immediately
16
+ chrome.storage.sync.get(['chromePilotRestrictedSites', 'chromePilotAllowedSites', 'chromePilotMode'], (result) => {
17
+ const mode = result.chromePilotMode || 'whitelist'; // 'whitelist' or 'blacklist'
18
+ const restrictedSites = result.chromePilotRestrictedSites || [];
19
+ const allowedSites = result.chromePilotAllowedSites || [];
20
+
21
+ let shouldBlock = false;
22
+
23
+ if (mode === 'blacklist') {
24
+ // Block if hostname matches any restricted site pattern
25
+ shouldBlock = restrictedSites.some(pattern => {
26
+ // Special handling for patterns with port wildcards like "localhost:*"
27
+ if (pattern.includes(':*')) {
28
+ const basePattern = pattern.replace(':*', '').toLowerCase();
29
+ if (hostname === basePattern) {
30
+ return true; // Match hostname regardless of port
31
+ }
32
+ }
33
+
34
+ if (pattern.includes('*')) {
35
+ const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\./g, '\\.'));
36
+ return regex.test(hostname);
37
+ }
38
+ return hostname.includes(pattern.toLowerCase());
39
+ });
40
+ } else {
41
+ // Whitelist mode: only allow if hostname matches allowed sites
42
+ const isAllowed = allowedSites.some(pattern => {
43
+ // Special handling for patterns with port wildcards like "localhost:*"
44
+ if (pattern.includes(':*')) {
45
+ const basePattern = pattern.replace(':*', '').toLowerCase();
46
+ if (hostname === basePattern) {
47
+ return true; // Match hostname regardless of port
48
+ }
49
+ }
50
+
51
+ if (pattern.includes('*')) {
52
+ const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\./g, '\\.'));
53
+ return regex.test(hostname);
54
+ }
55
+ return hostname.includes(pattern.toLowerCase());
56
+ });
57
+
58
+ // For whitelist mode, block if not explicitly allowed (unless empty list)
59
+ shouldBlock = allowedSites.length > 0 && !isAllowed;
60
+ }
61
+
62
+ if (shouldBlock) {
63
+ console.log(`[ChromeDebug MCP] Skipping restricted site (${mode} mode):`, hostname);
64
+ return;
65
+ }
66
+
67
+ // Continue with normal initialization
68
+ initializeChromePilot();
69
+ });
70
+
71
+ function initializeChromePilot() {
72
+ window.chromePilotContentScriptLoaded = true;
73
+
74
+ const CHROME_PILOT_VERSION = '2.0.4-BUILD-20250119';
75
+ console.log(`[content.js] Loaded version: ${CHROME_PILOT_VERSION}`);
76
+
77
+ let frameFlashIndicator = null;
78
+
79
+ // Helper function to safely check if extension context is valid
80
+ function isExtensionValid() {
81
+ try {
82
+ return chrome.runtime && chrome.runtime.id;
83
+ } catch (e) {
84
+ return false;
85
+ }
86
+ }
87
+
88
+
89
+ // Initialize ChromeDebug MCP content script
90
+ setTimeout(() => {
91
+ console.log('[ChromeDebug MCP] 🚀 Content script initializing...');
92
+ }, 100);
93
+
94
+ /*
95
+ * SNAPSHOT FEATURE DISABLED (2025-10-01)
96
+ *
97
+ * Console monitor injection was used for snapshot feature to capture console logs.
98
+ *
99
+ * WHY DISABLED:
100
+ * - Snapshots without console logs are just screenshots (users can do this natively)
101
+ * - Console log capture requires always-on monitoring (privacy concern)
102
+ * - Core value proposition (screenshot + searchable logs) cannot be achieved cleanly
103
+ *
104
+ * NOTE: Console logging is still captured during screen recordings via the normal
105
+ * recording system. This code only disabled the on-demand console monitor injection
106
+ * that was used for standalone snapshots.
107
+ *
108
+ * TO RE-ENABLE:
109
+ * 1. Implement privacy-conscious always-on log monitoring
110
+ * 2. Uncomment the code below
111
+ * 3. Re-enable related functions in background.js and popup.js
112
+ *
113
+ * See: SNAPSHOT_FEATURE_DISABLED.md for full explanation
114
+ */
115
+ /*
116
+ const consoleHistory = [];
117
+ const maxHistorySize = 100;
118
+ let consoleMonitorInjected = false;
119
+
120
+ function injectConsoleMonitor() {
121
+ if (consoleMonitorInjected) {
122
+ console.log('[ChromeDebug] Console monitor already injected');
123
+ return Promise.resolve(true);
124
+ }
125
+
126
+ return new Promise((resolve) => {
127
+ const script = document.createElement('script');
128
+ script.src = chrome.runtime.getURL('console-monitor.js');
129
+
130
+ script.onload = function() {
131
+ console.log('[ChromeDebug] Console monitor loaded successfully');
132
+ consoleMonitorInjected = true;
133
+ this.remove();
134
+ resolve(true);
135
+ };
136
+
137
+ script.onerror = function() {
138
+ console.error('[ChromeDebug] Failed to load console monitor script');
139
+ this.remove();
140
+ resolve(false);
141
+ };
142
+
143
+ (document.head || document.documentElement).appendChild(script);
144
+ });
145
+ }
146
+
147
+ window.addEventListener('message', (event) => {
148
+ if (event.source === window && event.data.type === 'CHROME_DEBUG_CONSOLE_LOG') {
149
+ consoleHistory.push(event.data.log);
150
+
151
+ if (consoleHistory.length > maxHistorySize) {
152
+ consoleHistory.shift();
153
+ }
154
+ }
155
+ });
156
+
157
+ console.log('[ChromeDebug] Console monitoring ready (will inject on demand)');
158
+ */
159
+
160
+ function getAngularComponentInfo(element) {
161
+ // Try to find Angular debug info
162
+ try {
163
+ // Method 1: ng.probe (Angular 1.x - 8.x in dev mode)
164
+ if (window.ng && window.ng.probe) {
165
+ const debugElement = window.ng.probe(element);
166
+ if (debugElement && debugElement.componentInstance) {
167
+ return {
168
+ componentName: debugElement.componentInstance.constructor.name,
169
+ framework: 'Angular',
170
+ sourceInfo: null // Angular doesn't expose source info easily
171
+ };
172
+ }
173
+ }
174
+
175
+ // Method 2: __ngContext__ (Angular 9+)
176
+ if (element.__ngContext__) {
177
+ return {
178
+ componentName: 'Angular Component',
179
+ framework: 'Angular 9+',
180
+ sourceInfo: null
181
+ };
182
+ }
183
+
184
+ // Method 3: Check for Ionic classes
185
+ if (element.tagName && element.tagName.toLowerCase().startsWith('ion-')) {
186
+ return {
187
+ componentName: element.tagName.toLowerCase(),
188
+ framework: 'Ionic',
189
+ sourceInfo: null
190
+ };
191
+ }
192
+ } catch (e) {
193
+ console.log('Error detecting Angular component:', e);
194
+ }
195
+
196
+ return null;
197
+ }
198
+
199
+ function getReactComponentInfo(element) {
200
+ // Try to find React Fiber node
201
+ const reactFiberKey = Object.keys(element).find(key =>
202
+ key.startsWith('__reactInternalInstance') ||
203
+ key.startsWith('__reactFiber')
204
+ );
205
+
206
+ if (!reactFiberKey) {
207
+ return null;
208
+ }
209
+
210
+ const fiber = element[reactFiberKey];
211
+ if (!fiber) {
212
+ return null;
213
+ }
214
+
215
+ // Collect all component info in the tree
216
+ const components = [];
217
+ let currentFiber = fiber;
218
+
219
+ // Debug: Log the first fiber to see what properties are available
220
+ if (fiber) {
221
+ console.log('React Fiber found:', fiber);
222
+ console.log('Fiber properties:', Object.keys(fiber));
223
+ }
224
+
225
+ while (currentFiber) {
226
+ // Check if this is a function/class component (not host component)
227
+ if (currentFiber.elementType && typeof currentFiber.elementType === 'function') {
228
+ const componentName = currentFiber.elementType.displayName ||
229
+ currentFiber.elementType.name ||
230
+ 'Unknown Component';
231
+
232
+ // Try multiple ways to get source info
233
+ let sourceInfo = null;
234
+
235
+ // Method 1: _debugSource (development builds)
236
+ if (currentFiber._debugSource) {
237
+ sourceInfo = {
238
+ fileName: currentFiber._debugSource.fileName,
239
+ lineNumber: currentFiber._debugSource.lineNumber,
240
+ columnNumber: currentFiber._debugSource.columnNumber
241
+ };
242
+ console.log('Found _debugSource:', sourceInfo);
243
+ }
244
+
245
+ // Method 2: _owner and _source
246
+ if (!sourceInfo && currentFiber._owner && currentFiber._owner._debugSource) {
247
+ sourceInfo = {
248
+ fileName: currentFiber._owner._debugSource.fileName,
249
+ lineNumber: currentFiber._owner._debugSource.lineNumber,
250
+ columnNumber: currentFiber._owner._debugSource.columnNumber
251
+ };
252
+ }
253
+
254
+ // Method 3: Check the component function itself
255
+ if (!sourceInfo && currentFiber.elementType._source) {
256
+ sourceInfo = {
257
+ fileName: currentFiber.elementType._source.fileName,
258
+ lineNumber: currentFiber.elementType._source.lineNumber,
259
+ columnNumber: currentFiber.elementType._source.columnNumber
260
+ };
261
+ }
262
+
263
+ // Method 4: Try to extract from stack trace (last resort)
264
+ if (!sourceInfo && currentFiber.elementType.toString) {
265
+ const stackMatch = currentFiber.elementType.toString().match(/at\s+(\S+)\s+\((.+):(\d+):(\d+)\)/);
266
+ if (stackMatch) {
267
+ sourceInfo = {
268
+ fileName: stackMatch[2],
269
+ lineNumber: parseInt(stackMatch[3]),
270
+ columnNumber: parseInt(stackMatch[4])
271
+ };
272
+ }
273
+ }
274
+
275
+ components.push({
276
+ componentName,
277
+ sourceInfo,
278
+ props: currentFiber.memoizedProps,
279
+ key: currentFiber.key,
280
+ type: currentFiber.elementType
281
+ });
282
+ }
283
+
284
+ currentFiber = currentFiber.return;
285
+ }
286
+
287
+ // Return the most specific component (first in the list)
288
+ return components.length > 0 ? {
289
+ ...components[0],
290
+ componentHierarchy: components.map(c => c.componentName)
291
+ } : null;
292
+ }
293
+
294
+ function getSelector(element) {
295
+ if (element.id) {
296
+ return `#${element.id}`;
297
+ }
298
+
299
+ if (element.className && typeof element.className === 'string') {
300
+ const classes = element.className.trim().split(/\s+/).filter(c => !c.startsWith('chrome-pilot'));
301
+ if (classes.length > 0) {
302
+ return `${element.tagName.toLowerCase()}.${classes.join('.')}`;
303
+ }
304
+ }
305
+
306
+ const path = [];
307
+ while (element && element.nodeType === Node.ELEMENT_NODE) {
308
+ let selector = element.tagName.toLowerCase();
309
+
310
+ if (element.id) {
311
+ selector = `#${element.id}`;
312
+ path.unshift(selector);
313
+ break;
314
+ }
315
+
316
+ let sibling = element;
317
+ let nth = 1;
318
+ while (sibling.previousElementSibling) {
319
+ sibling = sibling.previousElementSibling;
320
+ if (sibling.tagName === element.tagName) {
321
+ nth++;
322
+ }
323
+ }
324
+
325
+ if (nth > 1) {
326
+ selector += `:nth-of-type(${nth})`;
327
+ }
328
+
329
+ path.unshift(selector);
330
+ element = element.parentElement;
331
+ }
332
+
333
+ return path.join(' > ');
334
+ }
335
+
336
+ function getComponentInfo(element) {
337
+ let currentElement = element;
338
+ let ionicComponent = null;
339
+
340
+ // Check if this is an Ionic component
341
+ if (element.tagName && element.tagName.toLowerCase().startsWith('ion-')) {
342
+ ionicComponent = element.tagName.toLowerCase();
343
+ }
344
+
345
+ // For Ionic React, we need to look for React components in parent elements
346
+ // because Ionic components are web components that wrap React components
347
+ const maxDepth = 10; // Don't go too far up the tree
348
+ let depth = 0;
349
+
350
+ while (currentElement && depth < maxDepth) {
351
+ // Log for debugging
352
+ if (depth === 0) {
353
+ console.log(`Checking element:`, currentElement);
354
+ console.log(`Element keys:`, Object.keys(currentElement).filter(k => k.includes('react')));
355
+ }
356
+
357
+ // Try to find React info on current element
358
+ const reactInfo = getReactComponentInfo(currentElement);
359
+ if (reactInfo) {
360
+ // If we found React info and also have an Ionic component, combine them
361
+ if (ionicComponent) {
362
+ return {
363
+ ...reactInfo,
364
+ framework: 'Ionic React',
365
+ ionicComponent: ionicComponent
366
+ };
367
+ }
368
+ return { ...reactInfo, framework: 'React' };
369
+ }
370
+
371
+ // Move up to parent
372
+ currentElement = currentElement.parentElement;
373
+ depth++;
374
+ }
375
+
376
+ // If we only found Ionic component (no React info)
377
+ if (ionicComponent) {
378
+ return {
379
+ componentName: ionicComponent,
380
+ framework: 'Ionic',
381
+ sourceInfo: null
382
+ };
383
+ }
384
+
385
+ // Try Angular as last resort
386
+ const angularInfo = getAngularComponentInfo(element);
387
+ if (angularInfo) {
388
+ return angularInfo;
389
+ }
390
+
391
+ console.log('No component info found');
392
+ return null;
393
+ }
394
+
395
+
396
+ // Legacy recording indicator functions removed - now handled by ScreenCaptureVisualFeedback class
397
+
398
+ // Idempotent protection for cleanup function
399
+ let cleanupExecuted = false;
400
+
401
+ function cleanup() {
402
+ // Idempotent protection - only run cleanup once
403
+ if (cleanupExecuted) {
404
+ console.log('[ChromeDebug MCP] Cleanup already executed, skipping');
405
+ return;
406
+ }
407
+
408
+ cleanupExecuted = true;
409
+ console.log('[ChromeDebug MCP] Executing cleanup...');
410
+
411
+ // Clean up recording indicators and event listeners
412
+ try {
413
+ // Legacy recording indicator cleanup removed - now handled by ScreenCaptureVisualFeedback
414
+
415
+ // Stop workflow recording if active
416
+ if (typeof isWorkflowRecording !== 'undefined' && isWorkflowRecording) {
417
+ console.log('[ChromeDebug MCP] Stopping workflow recording during cleanup');
418
+ if (typeof stopWorkflowRecording === 'function') {
419
+ stopWorkflowRecording();
420
+ }
421
+ }
422
+
423
+ // Stop screen interaction tracking if active
424
+ if (typeof isScreenRecording !== 'undefined' && isScreenRecording) {
425
+ console.log('[ChromeDebug MCP] Stopping screen recording during cleanup');
426
+ if (typeof stopScreenInteractionTracking === 'function') {
427
+ stopScreenInteractionTracking();
428
+ }
429
+ }
430
+
431
+ console.log('[ChromeDebug MCP] Cleanup completed successfully');
432
+ } catch (error) {
433
+ console.warn('[ChromeDebug MCP] Error during cleanup:', error);
434
+ }
435
+ }
436
+
437
+
438
+
439
+ // Only add message listener if extension context is valid
440
+ if (isExtensionValid()) {
441
+ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
442
+ if (request.action === 'getConsoleHistory') {
443
+ // Inject console monitor if not already injected, then return logs
444
+ injectConsoleMonitor().then(() => {
445
+ // Wait a moment for any pending console messages to be captured
446
+ setTimeout(() => {
447
+ sendResponse({ logs: consoleHistory.slice() }); // Return a copy
448
+ }, 100);
449
+ });
450
+ return true; // Keep channel open for async response
451
+ } else if (request.action === 'showRecordingUI') {
452
+ console.log('Received showRecordingUI message');
453
+ showRecordingInterface();
454
+ sendResponse({ success: true });
455
+ return true; // Keep channel open for async response
456
+ } else if (request.action === 'showFrameFlash') {
457
+ showFrameFlash();
458
+ sendResponse({ success: true });
459
+ } else if (request.action === 'recordingComplete') {
460
+ // Show the recording ID in the floating UI
461
+ console.log('Recording complete with ID:', request.recordingId);
462
+ if (recordingOverlay) {
463
+ const statusDiv = document.getElementById('chrome-pilot-recording-status');
464
+ if (statusDiv && request.recordingId) {
465
+ statusDiv.innerHTML = `<strong style="color: #4CAF50;">Recording saved!</strong><br>ID: <code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family: monospace;">${request.recordingId}</code>`;
466
+
467
+ // Keep the overlay visible for a few seconds to show the ID
468
+ setTimeout(() => {
469
+ if (recordingOverlay) {
470
+ recordingOverlay.remove();
471
+ recordingOverlay = null;
472
+ }
473
+ }, 10000); // Keep visible for 10 seconds
474
+ }
475
+ }
476
+ sendResponse({ success: true });
477
+ } else if (request.action === 'recordingStarted') {
478
+ // Recording indicator now handled by ScreenCaptureVisualFeedback
479
+ console.log('[ChromeDebug MCP] Received recordingStarted message with scheduled start time:', request.scheduledStartTime);
480
+ // Start tracking screen interactions with scheduled timing
481
+ startScreenInteractionTracking(request.scheduledStartTime, request.sessionId);
482
+ sendResponse({ success: true });
483
+ } else if (request.action === 'recordingStopped') {
484
+ // Recording indicator cleanup now handled by ScreenCaptureVisualFeedback
485
+ console.log('[ChromeDebug MCP] Received recordingStopped message');
486
+ // Stop tracking screen interactions
487
+ stopScreenInteractionTracking();
488
+ sendResponse({ success: true });
489
+ } else if (request.action === 'recordingSessionComplete') {
490
+ // Recording indicator cleanup now handled by ScreenCaptureVisualFeedback
491
+ console.log('[ChromeDebug MCP] Received recordingSessionComplete message');
492
+ // Stop tracking screen interactions
493
+ stopScreenInteractionTracking();
494
+ sendResponse({ success: true });
495
+ } else if (request.action === 'startWorkflowRecording') {
496
+ console.log('[ChromeDebug MCP] Starting workflow recording with settings:', request.screenshotSettings);
497
+ startWorkflowRecording(request.screenshotSettings);
498
+ sendResponse({ success: true });
499
+ } else if (request.action === 'stopWorkflowRecording') {
500
+ console.log('[ChromeDebug MCP] Stopping workflow recording');
501
+ const workflow = stopWorkflowRecording();
502
+ sendResponse({ success: true, workflow: workflow });
503
+ } else if (request.action === 'createRestorePoint') {
504
+ // Create restore point for current workflow
505
+ createRestorePoint(request.actionIndex || workflowActions.length).then(restorePointId => {
506
+ sendResponse({ success: true, restorePointId });
507
+ }).catch(error => {
508
+ sendResponse({ success: false, error: error.message });
509
+ });
510
+ return true; // Keep channel open for async response
511
+ } else if (request.action === 'restoreFromPoint') {
512
+ // Restore from a restore point
513
+ restoreFromPoint(request.restorePointData).then(() => {
514
+ sendResponse({ success: true });
515
+ }).catch(error => {
516
+ sendResponse({ success: false, error: error.message });
517
+ });
518
+ return true; // Keep channel open for async response
519
+ } else if (request.action === 'playWorkflow') {
520
+ // Play a workflow recording
521
+ playWorkflowRecording(request.workflow, request.actions).then(() => {
522
+ sendResponse({ success: true });
523
+ }).catch(error => {
524
+ sendResponse({ success: false, error: error.message });
525
+ });
526
+ return true; // Keep channel open for async response
527
+ } else if (request.action === 'startFullDataRecording') {
528
+ // Start full data recording with user configuration
529
+ console.log('[ChromeDebug MCP] Starting full data recording with config:', request.config);
530
+ startFullDataRecording(request.config).then(() => {
531
+ sendResponse({ success: true });
532
+ }).catch(error => {
533
+ sendResponse({ success: false, error: error.message });
534
+ });
535
+ return true; // Keep channel open for async response
536
+ } else if (request.action === 'stopFullDataRecording') {
537
+ // Stop full data recording
538
+ console.log('[ChromeDebug MCP] Stopping full data recording');
539
+ stopFullDataRecording().then(() => {
540
+ sendResponse({ success: true });
541
+ }).catch(error => {
542
+ sendResponse({ success: false, error: error.message });
543
+ });
544
+ return true; // Keep channel open for async response
545
+ } else if (request.action === 'getFullDataRecordingStats') {
546
+ // Get full data recording statistics
547
+ const stats = getFullDataRecordingStats();
548
+ sendResponse({ success: true, stats: stats });
549
+ }
550
+ });
551
+ }
552
+
553
+ // Helper function to fix CSS selectors that might have issues
554
+ function fixSelector(selector) {
555
+ if (!selector || typeof selector !== 'string') return selector;
556
+
557
+ // Remove pseudo-classes like :hover, :focus, :active that can't be used in querySelector
558
+ let fixedSelector = selector.replace(/:(hover|focus|active|visited|focus-within|focus-visible)/g, '');
559
+
560
+ // Remove any trailing dots
561
+ fixedSelector = fixedSelector.replace(/\.+$/, '');
562
+
563
+ // Remove empty class selectors (consecutive dots)
564
+ fixedSelector = fixedSelector.replace(/\.\./g, '.');
565
+
566
+ // Try to validate the selector
567
+ try {
568
+ // Test if selector is valid
569
+ document.querySelector(fixedSelector);
570
+ return fixedSelector; // It's valid, return the fixed version
571
+ } catch (e) {
572
+ console.warn('[ChromePilot] Invalid selector even after fixes:', fixedSelector, 'Original:', selector);
573
+ // Return the fixed version anyway, as it's our best attempt
574
+ return fixedSelector;
575
+ }
576
+ }
577
+
578
+ // Helper function to wait for page load
579
+ async function waitForPageLoad(timeout = 10000) {
580
+ return new Promise((resolve) => {
581
+ // If document is already loaded, resolve immediately
582
+ if (document.readyState === 'complete') {
583
+ resolve();
584
+ return;
585
+ }
586
+
587
+ let timeoutId;
588
+ const cleanup = () => {
589
+ clearTimeout(timeoutId);
590
+ window.removeEventListener('load', loadHandler);
591
+ };
592
+
593
+ const loadHandler = () => {
594
+ cleanup();
595
+ resolve();
596
+ };
597
+
598
+ // Set up load listener
599
+ window.addEventListener('load', loadHandler);
600
+
601
+ // Set up timeout
602
+ timeoutId = setTimeout(() => {
603
+ cleanup();
604
+ console.log('[ChromePilot] Page load timeout reached, continuing...');
605
+ resolve();
606
+ }, timeout);
607
+ });
608
+ }
609
+
610
+ // Helper function to wait for element to appear
611
+ async function waitForElement(selector, timeout = 5000) {
612
+ const startTime = Date.now();
613
+
614
+ while (Date.now() - startTime < timeout) {
615
+ const element = document.querySelector(selector);
616
+ if (element) {
617
+ return element;
618
+ }
619
+ await new Promise(resolve => setTimeout(resolve, 100));
620
+ }
621
+
622
+ return null;
623
+ }
624
+
625
+ // Function to play a workflow recording
626
+ async function playWorkflowRecording(workflow, actions) {
627
+ console.log('[ChromePilot] Starting workflow playback with', actions.length, 'actions');
628
+
629
+ try {
630
+ // Show playback indicator
631
+ const playbackIndicator = document.createElement('div');
632
+ playbackIndicator.id = 'chrome-pilot-playback-indicator';
633
+ playbackIndicator.style.cssText = `
634
+ position: fixed;
635
+ top: 20px;
636
+ right: 20px;
637
+ background: #4CAF50;
638
+ color: white;
639
+ padding: 12px 24px;
640
+ border-radius: 4px;
641
+ font-family: Arial, sans-serif;
642
+ font-size: 14px;
643
+ z-index: 10000;
644
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
645
+ display: flex;
646
+ align-items: center;
647
+ gap: 10px;
648
+ `;
649
+ playbackIndicator.innerHTML = `
650
+ <div style="
651
+ width: 12px;
652
+ height: 12px;
653
+ border-radius: 50%;
654
+ background: white;
655
+ animation: pulse 1s infinite;
656
+ "></div>
657
+ <span>Playing workflow: <span id="action-counter">0</span>/${actions.length}</span>
658
+ `;
659
+
660
+ // Add animation style
661
+ const style = document.createElement('style');
662
+ style.textContent = `
663
+ @keyframes pulse {
664
+ 0% { opacity: 1; }
665
+ 50% { opacity: 0.5; }
666
+ 100% { opacity: 1; }
667
+ }
668
+ `;
669
+ document.head.appendChild(style);
670
+ document.body.appendChild(playbackIndicator);
671
+
672
+ const actionCounter = document.getElementById('action-counter');
673
+ let executedActions = 0;
674
+
675
+ // Execute each action with proper timing
676
+ for (let i = 0; i < actions.length; i++) {
677
+ const action = actions[i];
678
+ actionCounter.textContent = i + 1;
679
+
680
+ try {
681
+ console.log(`[ChromePilot] Executing action ${i + 1}/${actions.length}:`, action.type, 'selector:', action.selector);
682
+
683
+ // Calculate delay based on timestamps
684
+ let delay = 500; // Default delay
685
+ if (i > 0 && action.timestamp && actions[i - 1].timestamp) {
686
+ delay = action.timestamp - actions[i - 1].timestamp;
687
+ // Apply reasonable limits
688
+ delay = Math.min(delay, 10000); // Cap at 10 seconds
689
+ delay = Math.max(delay, 100); // Minimum 100ms
690
+ console.log(`[ChromePilot] Calculated delay: ${delay}ms`);
691
+ }
692
+
693
+ // Wait between actions
694
+ if (i > 0) { // Don't delay before first action
695
+ await new Promise(resolve => setTimeout(resolve, delay));
696
+ }
697
+
698
+ switch (action.type) {
699
+ case 'click':
700
+ if (action.selector) {
701
+ // Fix selector if needed
702
+ const fixedSelector = fixSelector(action.selector);
703
+
704
+ // Wait for element to appear if it's not immediately available
705
+ let element = document.querySelector(fixedSelector);
706
+ if (!element && action.xpath) {
707
+ // Try XPath as fallback
708
+ console.log('[ChromePilot] Trying XPath fallback:', action.xpath);
709
+ const xpathResult = document.evaluate(action.xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
710
+ element = xpathResult.singleNodeValue;
711
+ }
712
+
713
+ if (!element) {
714
+ console.log('[ChromePilot] Element not found immediately, waiting...');
715
+ element = await waitForElement(fixedSelector, 3000);
716
+ }
717
+
718
+ if (element) {
719
+ // If the element is an SVG or inside a button/link, find the clickable parent
720
+ let clickableElement = element;
721
+ if (element.tagName === 'svg' || element.tagName === 'path' || element.tagName === 'g') {
722
+ // Find parent button or link
723
+ clickableElement = element.closest('button, a') || element;
724
+ }
725
+
726
+ console.log('[ChromePilot] Found element:', clickableElement.tagName, 'for selector:', fixedSelector);
727
+ clickableElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
728
+ await new Promise(resolve => setTimeout(resolve, 200)); // Wait for scroll
729
+
730
+ // Ensure element is visible and enabled
731
+ const rect = clickableElement.getBoundingClientRect();
732
+ if (rect.width === 0 || rect.height === 0) {
733
+ console.warn('[ChromePilot] Element has zero dimensions, may not be visible');
734
+ }
735
+
736
+ // Try to click
737
+ try {
738
+ clickableElement.click();
739
+ console.log('[ChromePilot] Clicked element successfully');
740
+ } catch (e) {
741
+ console.error('[ChromePilot] Error clicking element:', e);
742
+ // Try alternative click method
743
+ clickableElement.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
744
+ }
745
+
746
+ // After click, check if navigation might occur
747
+ const currentUrl = window.location.href;
748
+ await new Promise(resolve => setTimeout(resolve, 500));
749
+
750
+ // If URL changed, wait for page load
751
+ if (window.location.href !== currentUrl) {
752
+ console.log('[ChromePilot] Navigation detected after click, waiting for page load...');
753
+ await waitForPageLoad();
754
+ }
755
+ } else {
756
+ console.warn('[ChromePilot] Element not found:', action.selector);
757
+ }
758
+ } else if (action.x !== undefined && action.y !== undefined) {
759
+ // Click at coordinates
760
+ const element = document.elementFromPoint(action.x, action.y);
761
+ if (element) {
762
+ element.click();
763
+ }
764
+ }
765
+ break;
766
+
767
+ case 'input':
768
+ if (action.selector) {
769
+ // Wait for element to appear if it's not immediately available
770
+ let element = document.querySelector(action.selector);
771
+ if (!element) {
772
+ console.log('[ChromePilot] Input element not found immediately, waiting...');
773
+ element = await waitForElement(action.selector, 3000);
774
+ }
775
+
776
+ if (element) {
777
+ console.log('[ChromePilot] Found input element:', element.tagName, 'type:', element.type, 'for selector:', action.selector);
778
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
779
+ await new Promise(resolve => setTimeout(resolve, 200)); // Wait for scroll
780
+
781
+ // Focus the element
782
+ element.focus();
783
+
784
+ // Clear existing value
785
+ element.value = '';
786
+
787
+ // Set new value
788
+ element.value = action.value || '';
789
+ console.log('[ChromePilot] Set input value to:', action.value);
790
+
791
+ // Trigger events in the right order
792
+ element.dispatchEvent(new Event('focus', { bubbles: true }));
793
+ element.dispatchEvent(new Event('input', { bubbles: true }));
794
+ element.dispatchEvent(new Event('change', { bubbles: true }));
795
+ element.dispatchEvent(new Event('blur', { bubbles: true }));
796
+ } else {
797
+ console.warn('[ChromePilot] Input element not found:', action.selector);
798
+ }
799
+ }
800
+ break;
801
+
802
+ case 'keypress':
803
+ if (action.key) {
804
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: action.key }));
805
+ document.dispatchEvent(new KeyboardEvent('keyup', { key: action.key }));
806
+ }
807
+ break;
808
+
809
+ case 'scroll':
810
+ if (action.deltaY !== undefined) {
811
+ window.scrollBy(0, action.deltaY);
812
+ }
813
+ break;
814
+
815
+ case 'navigation':
816
+ // Navigation will be handled by the browser
817
+ console.log('[ChromePilot] Navigation detected');
818
+ break;
819
+
820
+ default:
821
+ console.warn('[ChromePilot] Unknown action type:', action.type);
822
+ }
823
+
824
+ executedActions++;
825
+ } catch (error) {
826
+ console.error('[ChromePilot] Error executing action:', error);
827
+ }
828
+ }
829
+
830
+ // Show completion
831
+ playbackIndicator.style.background = '#2196F3';
832
+ playbackIndicator.innerHTML = `
833
+ <span>✓ Workflow completed: ${executedActions}/${actions.length} actions</span>
834
+ `;
835
+
836
+ // Remove indicator after 3 seconds
837
+ setTimeout(() => {
838
+ playbackIndicator.remove();
839
+ style.remove();
840
+ }, 3000);
841
+
842
+ console.log('[ChromePilot] Workflow playback completed');
843
+
844
+ } catch (error) {
845
+ console.error('[ChromePilot] Error during workflow playback:', error);
846
+ throw error;
847
+ }
848
+ }
849
+
850
+ // Workflow recording variables
851
+ let isWorkflowRecording = false;
852
+ let workflowActions = [];
853
+ let workflowRecordingIndicator = null;
854
+ let workflowScreenshotSettings = null;
855
+
856
+ // Helper function to capture screenshot
857
+ async function captureWorkflowScreenshot() {
858
+ if (!workflowScreenshotSettings || !workflowScreenshotSettings.enabled) {
859
+ return null;
860
+ }
861
+
862
+ try {
863
+ // Send message to background script to capture screenshot
864
+ const response = await chrome.runtime.sendMessage({
865
+ action: 'captureWorkflowScreenshot',
866
+ settings: workflowScreenshotSettings
867
+ });
868
+
869
+ if (response && response.success) {
870
+ return response.screenshotData;
871
+ }
872
+ } catch (error) {
873
+ console.error('[ChromeDebug MCP] Error capturing screenshot:', error);
874
+ }
875
+
876
+ return null;
877
+ }
878
+
879
+ // Helper function to get unique selector for an element
880
+ function getUniqueSelector(element) {
881
+ // Try ID first
882
+ if (element.id) {
883
+ return `#${element.id}`;
884
+ }
885
+
886
+ // Try unique class combination
887
+ if (element.className && typeof element.className === 'string') {
888
+ const classes = element.className.trim().split(/\s+/)
889
+ .filter(c => !c.startsWith('chrome-pilot'))
890
+ .filter(c => !c.includes(':')) // Filter out pseudo-class artifacts
891
+ .map(c => {
892
+ // Escape special characters in class names
893
+ return c.replace(/([^\w-])/g, '\\$1');
894
+ });
895
+ if (classes.length > 0) {
896
+ const selector = `${element.tagName.toLowerCase()}.${classes.join('.')}`;
897
+ // Check if this selector is unique
898
+ try {
899
+ if (document.querySelectorAll(selector).length === 1) {
900
+ return selector;
901
+ }
902
+ } catch (e) {
903
+ // Invalid selector, continue to next method
904
+ }
905
+ }
906
+ }
907
+
908
+ // Fall back to full path selector
909
+ return getSelector(element);
910
+ }
911
+
912
+ // Helper function to get XPath for an element
913
+ function getXPath(element) {
914
+ if (element.id !== '') {
915
+ return `//*[@id="${element.id}"]`;
916
+ }
917
+
918
+ if (element === document.body) {
919
+ return '/html/body';
920
+ }
921
+
922
+ let ix = 0;
923
+ const siblings = element.parentNode.childNodes;
924
+
925
+ for (let i = 0; i < siblings.length; i++) {
926
+ const sibling = siblings[i];
927
+ if (sibling === element) {
928
+ return getXPath(element.parentNode) + '/' + element.tagName.toLowerCase() + '[' + (ix + 1) + ']';
929
+ }
930
+ if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
931
+ ix++;
932
+ }
933
+ }
934
+ }
935
+
936
+ // Create workflow recording indicator
937
+ function createWorkflowRecordingIndicator() {
938
+ if (workflowRecordingIndicator) return;
939
+
940
+ workflowRecordingIndicator = document.createElement('div');
941
+ workflowRecordingIndicator.id = 'chrome-pilot-workflow-indicator';
942
+ workflowRecordingIndicator.innerHTML = `
943
+ <div style="display: flex; align-items: center; gap: 8px;">
944
+ <span style="color: #9c27b0; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
945
+ <span>Recording Workflow...</span>
946
+ <span id="workflow-action-count" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
947
+ </div>
948
+ `;
949
+
950
+ Object.assign(workflowRecordingIndicator.style, {
951
+ position: 'fixed',
952
+ bottom: '20px',
953
+ left: '20px',
954
+ background: 'rgba(0, 0, 0, 0.9)',
955
+ color: 'white',
956
+ padding: '12px 20px',
957
+ borderRadius: '25px',
958
+ fontSize: '14px',
959
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif',
960
+ fontWeight: '500',
961
+ zIndex: '2147483647',
962
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
963
+ backdropFilter: 'blur(10px)',
964
+ WebkitBackdropFilter: 'blur(10px)',
965
+ pointerEvents: 'none',
966
+ userSelect: 'none'
967
+ });
968
+
969
+ document.body.appendChild(workflowRecordingIndicator);
970
+ }
971
+
972
+ function removeWorkflowRecordingIndicator() {
973
+ if (workflowRecordingIndicator) {
974
+ workflowRecordingIndicator.remove();
975
+ workflowRecordingIndicator = null;
976
+ }
977
+ }
978
+
979
+ function updateWorkflowActionCount() {
980
+ const countEl = document.getElementById('workflow-action-count');
981
+ if (countEl) {
982
+ countEl.textContent = `${workflowActions.length} action${workflowActions.length !== 1 ? 's' : ''}`;
983
+ }
984
+ }
985
+
986
+ // Enhanced click tracking helper functions - Pro Feature
987
+ // Try to import enhanced capture module (only exists in pro build)
988
+ let enhancedCaptureModule = null;
989
+ let captureElementHTML = null;
990
+ let extractEventHandlers = null;
991
+ let captureElementState = null;
992
+ let getEnhancedComponentInfo = null;
993
+ let getPerformanceMetrics = null;
994
+
995
+ // Attempt to load pro module
996
+ (async () => {
997
+ try {
998
+ const module = await import(chrome.runtime.getURL('pro/enhanced-capture.js'));
999
+ enhancedCaptureModule = module;
1000
+ captureElementHTML = module.captureElementHTML;
1001
+ extractEventHandlers = module.extractEventHandlers;
1002
+ captureElementState = module.captureElementState;
1003
+ getEnhancedComponentInfo = module.getEnhancedComponentInfo;
1004
+ getPerformanceMetrics = module.getPerformanceMetrics;
1005
+ console.log('[ChromePilot Pro] Enhanced capture module loaded');
1006
+ } catch (e) {
1007
+ console.log('[ChromePilot Free] Enhanced capture not available - using basic mode');
1008
+ // Create stub functions that return empty data for free version
1009
+ captureElementHTML = () => '';
1010
+ extractEventHandlers = () => ({});
1011
+ captureElementState = () => ({});
1012
+ getEnhancedComponentInfo = () => ({});
1013
+ getPerformanceMetrics = () => ({});
1014
+ }
1015
+ })();
1016
+
1017
+ // Helper function to check if enhanced capture is enabled
1018
+ async function shouldEnhanceCapture() {
1019
+ return new Promise((resolve) => {
1020
+ chrome.storage.local.get(['enhancedClickCapture'], (result) => {
1021
+ resolve(result.enhancedClickCapture === true);
1022
+ });
1023
+ });
1024
+ }
1025
+
1026
+ // Record click action
1027
+ async function recordClick(event) {
1028
+ if (!isWorkflowRecording) {
1029
+ console.log('[ChromePilot] Click ignored - not recording');
1030
+ return;
1031
+ }
1032
+
1033
+ console.log('[ChromePilot] Recording click event');
1034
+
1035
+ let element = event.target;
1036
+
1037
+ // If clicking on an SVG element or its children, find the parent button/link
1038
+ if (element.tagName === 'svg' || element.tagName === 'path' || element.tagName === 'g' || element.tagName === 'circle' || element.tagName === 'rect') {
1039
+ const clickableParent = element.closest('button, a, [role="button"], [onclick]');
1040
+ if (clickableParent) {
1041
+ console.log('[ChromePilot] Adjusted click target from', element.tagName, 'to', clickableParent.tagName);
1042
+ element = clickableParent;
1043
+ }
1044
+ }
1045
+
1046
+ const action = {
1047
+ type: 'click',
1048
+ selector: getUniqueSelector(element),
1049
+ xpath: getXPath(element),
1050
+ x: event.clientX,
1051
+ y: event.clientY,
1052
+ timestamp: Date.now()
1053
+ };
1054
+
1055
+ // Add text content for buttons and links
1056
+ if (element.tagName === 'BUTTON' || element.tagName === 'A') {
1057
+ action.text = element.textContent.trim();
1058
+ }
1059
+
1060
+ // Enhanced click tracking data (conditional based on user setting)
1061
+ const enhanceCapture = await shouldEnhanceCapture();
1062
+ if (enhanceCapture) {
1063
+ try {
1064
+ console.log('[ChromePilot] Capturing enhanced click data...');
1065
+
1066
+ // Capture enhanced element data (only include fields with meaningful values)
1067
+ const capturedHTML = captureElementHTML(element);
1068
+ if (capturedHTML) {
1069
+ action.element_html = capturedHTML;
1070
+ }
1071
+
1072
+ const componentData = getEnhancedComponentInfo(element);
1073
+ if (componentData) {
1074
+ try {
1075
+ JSON.stringify(componentData); // Test if serializable
1076
+ action.component_data = componentData;
1077
+ } catch (e) {
1078
+ console.error('[ChromePilot] Component data cannot be serialized:', e.message);
1079
+ action.component_data = { error: 'Serialization failed', type: typeof componentData };
1080
+ }
1081
+ }
1082
+
1083
+ const eventHandlers = extractEventHandlers(element);
1084
+ if (eventHandlers) {
1085
+ try {
1086
+ JSON.stringify(eventHandlers); // Test if serializable
1087
+ action.event_handlers = eventHandlers;
1088
+ } catch (e) {
1089
+ console.error('[ChromePilot] Event handlers cannot be serialized:', e.message);
1090
+ action.event_handlers = { error: 'Serialization failed', type: typeof eventHandlers };
1091
+ }
1092
+ }
1093
+
1094
+ const elementState = captureElementState(element);
1095
+ if (elementState) {
1096
+ try {
1097
+ JSON.stringify(elementState); // Test if serializable
1098
+ action.element_state = elementState;
1099
+ } catch (e) {
1100
+ console.error('[ChromePilot] Element state cannot be serialized:', e.message);
1101
+ action.element_state = { error: 'Serialization failed', type: typeof elementState };
1102
+ }
1103
+ }
1104
+
1105
+ const perfMetrics = getPerformanceMetrics();
1106
+ if (perfMetrics) {
1107
+ try {
1108
+ JSON.stringify(perfMetrics); // Test if serializable
1109
+ action.performance_metrics = perfMetrics;
1110
+ } catch (e) {
1111
+ console.error('[ChromePilot] Performance metrics cannot be serialized:', e.message);
1112
+ action.performance_metrics = { error: 'Serialization failed', type: typeof perfMetrics };
1113
+ }
1114
+ }
1115
+
1116
+ console.log('[ChromePilot] Enhanced click data captured successfully');
1117
+ } catch (error) {
1118
+ console.warn('[ChromePilot] Error capturing enhanced click data:', error);
1119
+ // Continue with basic click recording if enhanced capture fails
1120
+ }
1121
+ } else {
1122
+ console.log('[ChromePilot] Enhanced click capture disabled - using basic capture only');
1123
+ }
1124
+
1125
+ // Capture screenshot if enabled
1126
+ if (workflowScreenshotSettings && workflowScreenshotSettings.enabled) {
1127
+ // Give a moment for any UI changes to settle
1128
+ setTimeout(async () => {
1129
+ const screenshotData = await captureWorkflowScreenshot();
1130
+ if (screenshotData) {
1131
+ action.screenshot_data = screenshotData;
1132
+ }
1133
+ }, 100);
1134
+ }
1135
+
1136
+ workflowActions.push(action);
1137
+ console.log('[ChromePilot] Action recorded, total actions:', workflowActions.length);
1138
+ updateWorkflowActionCount();
1139
+
1140
+ // Visual feedback
1141
+ flashElement(element);
1142
+ }
1143
+
1144
+ // Record input changes
1145
+ async function recordInput(event) {
1146
+ if (!isWorkflowRecording) return;
1147
+
1148
+ const element = event.target;
1149
+ const action = {
1150
+ type: 'input',
1151
+ selector: getUniqueSelector(element),
1152
+ xpath: getXPath(element),
1153
+ value: element.value,
1154
+ timestamp: Date.now()
1155
+ };
1156
+
1157
+ // Add input type for better context
1158
+ if (element.type) {
1159
+ action.inputType = element.type;
1160
+ }
1161
+
1162
+ // Add placeholder for context
1163
+ if (element.placeholder) {
1164
+ action.placeholder = element.placeholder;
1165
+ }
1166
+
1167
+ // Capture screenshot if enabled
1168
+ if (workflowScreenshotSettings && workflowScreenshotSettings.enabled) {
1169
+ setTimeout(async () => {
1170
+ const screenshotData = await captureWorkflowScreenshot();
1171
+ if (screenshotData) {
1172
+ action.screenshot_data = screenshotData;
1173
+ }
1174
+ }, 100);
1175
+ }
1176
+
1177
+ workflowActions.push(action);
1178
+ updateWorkflowActionCount();
1179
+
1180
+ // Visual feedback
1181
+ flashElement(element);
1182
+ }
1183
+
1184
+ // Visual feedback for recorded actions
1185
+ function flashElement(element) {
1186
+ const originalBorder = element.style.border;
1187
+ const originalOutline = element.style.outline;
1188
+
1189
+ element.style.outline = '3px solid #9c27b0';
1190
+ element.style.outlineOffset = '2px';
1191
+
1192
+ setTimeout(() => {
1193
+ element.style.outline = originalOutline;
1194
+ element.style.outlineOffset = '';
1195
+ }, 300);
1196
+ }
1197
+
1198
+ // Drag tracking variables
1199
+ let isDragging = false;
1200
+ let dragStartElement = null;
1201
+ let dragStartX = 0;
1202
+ let dragStartY = 0;
1203
+
1204
+ // Record drag start
1205
+ function recordMouseDown(event) {
1206
+ if (!isWorkflowRecording) return;
1207
+
1208
+ // Only track left mouse button
1209
+ if (event.button !== 0) return;
1210
+
1211
+ isDragging = true;
1212
+ dragStartElement = event.target;
1213
+ dragStartX = event.clientX;
1214
+ dragStartY = event.clientY;
1215
+ }
1216
+
1217
+ // Record drag end
1218
+ function recordMouseUp(event) {
1219
+ if (!isWorkflowRecording || !isDragging) return;
1220
+
1221
+ const endX = event.clientX;
1222
+ const endY = event.clientY;
1223
+
1224
+ // Only record if there was actual movement
1225
+ const distance = Math.sqrt(Math.pow(endX - dragStartX, 2) + Math.pow(endY - dragStartY, 2));
1226
+ if (distance > 5) { // Minimum 5px movement to count as drag
1227
+ const action = {
1228
+ type: 'drag',
1229
+ startSelector: getUniqueSelector(dragStartElement),
1230
+ startX: dragStartX,
1231
+ startY: dragStartY,
1232
+ endX: endX,
1233
+ endY: endY,
1234
+ distance: Math.round(distance),
1235
+ timestamp: Date.now()
1236
+ };
1237
+
1238
+ // Add element info if dragging specific elements
1239
+ if (dragStartElement.tagName === 'IMG' || dragStartElement.draggable) {
1240
+ action.draggedElement = dragStartElement.tagName.toLowerCase();
1241
+ }
1242
+
1243
+ workflowActions.push(action);
1244
+ updateWorkflowActionCount();
1245
+
1246
+ // Visual feedback
1247
+ flashElement(dragStartElement);
1248
+ }
1249
+
1250
+ isDragging = false;
1251
+ dragStartElement = null;
1252
+ }
1253
+
1254
+ // Start workflow recording
1255
+ function startWorkflowRecording(screenshotSettings) {
1256
+ isWorkflowRecording = true;
1257
+ workflowActions = [];
1258
+ workflowScreenshotSettings = screenshotSettings;
1259
+ createWorkflowRecordingIndicator();
1260
+
1261
+ console.log('[ChromePilot] Workflow recording started with settings:', screenshotSettings);
1262
+
1263
+ // Start runtime bridge recording for function traces
1264
+ if (window.__chromePilot && window.__chromePilot.startRecording) {
1265
+ window.__chromePilot.startRecording();
1266
+ console.log('[ChromePilot] Started runtime bridge recording for function traces');
1267
+ }
1268
+
1269
+ // Connect to the existing FunctionTracker for workflow recording
1270
+ if (window.ChromePilotTracker && window.ChromePilotTracker._functionTracker) {
1271
+ const tracker = window.ChromePilotTracker._functionTracker;
1272
+
1273
+ // Set up workflow-specific onDataReady callback for function traces
1274
+ const originalOnDataReady = tracker.onDataReady;
1275
+ tracker.onDataReady = (events) => {
1276
+ // Call original callback if it exists (for full data recording)
1277
+ if (originalOnDataReady) {
1278
+ originalOnDataReady(events);
1279
+ }
1280
+
1281
+ // Process events for workflow recording
1282
+ events.forEach(event => {
1283
+ if (event.type === 'execution_trace' && event.function_name) {
1284
+ // Convert execution_trace to workflow function-trace format
1285
+ workflowActions.push({
1286
+ type: 'function-trace',
1287
+ component: event.function_name, // Map function_name to component
1288
+ args: event.arguments || event.arg_summary || 'unknown',
1289
+ timestamp: event.timestamp,
1290
+ stack: event.call_stack || null
1291
+ });
1292
+
1293
+ console.log('[ChromePilot] Function trace captured for workflow:', event.function_name);
1294
+ }
1295
+ });
1296
+ };
1297
+
1298
+ // Enable function tracking via ChromePilotTracker
1299
+ window.ChromePilotTracker._setRecordingStatus(true);
1300
+ console.log('[ChromePilot] Connected workflow recording to FunctionTracker');
1301
+ } else {
1302
+ console.warn('[ChromePilot] FunctionTracker not available for workflow recording');
1303
+ }
1304
+
1305
+ // Add event listeners for recording
1306
+ document.addEventListener('click', recordClick, true);
1307
+ document.addEventListener('input', recordInput, true);
1308
+ document.addEventListener('change', recordInput, true);
1309
+ document.addEventListener('mousedown', recordMouseDown, true);
1310
+ document.addEventListener('mouseup', recordMouseUp, true);
1311
+
1312
+ // Function traces are now handled directly via FunctionTracker connection above
1313
+ // Removed phantom message listener that was waiting for messages that never came
1314
+
1315
+ // Track navigation events
1316
+ const currentUrl = window.location.href;
1317
+ window.addEventListener('beforeunload', () => {
1318
+ if (isWorkflowRecording && window.location.href !== currentUrl) {
1319
+ workflowActions.push({
1320
+ type: 'navigation',
1321
+ url: window.location.href,
1322
+ timestamp: Date.now()
1323
+ });
1324
+ }
1325
+ });
1326
+ }
1327
+
1328
+ // Stop workflow recording
1329
+ function stopWorkflowRecording() {
1330
+ isWorkflowRecording = false;
1331
+ removeWorkflowRecordingIndicator();
1332
+
1333
+ console.log(`[ChromePilot] Stopping recording. Captured ${workflowActions.length} actions`);
1334
+
1335
+ // Disconnect from FunctionTracker and disable recording
1336
+ if (window.ChromePilotTracker && window.ChromePilotTracker._functionTracker) {
1337
+ // Disable function tracking
1338
+ window.ChromePilotTracker._setRecordingStatus(false);
1339
+
1340
+ // Count function traces captured during workflow recording
1341
+ const functionTraces = workflowActions.filter(action => action.type === 'function-trace');
1342
+ console.log(`[ChromePilot] Function tracking stopped. Captured ${functionTraces.length} function traces`);
1343
+ }
1344
+
1345
+ // Remove event listeners
1346
+ document.removeEventListener('click', recordClick, true);
1347
+ document.removeEventListener('input', recordInput, true);
1348
+ document.removeEventListener('change', recordInput, true);
1349
+ document.removeEventListener('mousedown', recordMouseDown, true);
1350
+ document.removeEventListener('mouseup', recordMouseUp, true);
1351
+
1352
+ // Reset drag state
1353
+ isDragging = false;
1354
+ dragStartElement = null;
1355
+
1356
+ // Extract function traces from the __chromePilot bridge if available
1357
+ let functionTraces = [];
1358
+
1359
+ // First, get any function traces from the runtime bridge
1360
+ if (window.__chromePilot && window.__chromePilot.stopRecording) {
1361
+ console.log('[ChromeDebug MCP] Stopping runtime bridge recording...');
1362
+ const bridgeTraces = window.__chromePilot.stopRecording();
1363
+ if (Array.isArray(bridgeTraces)) {
1364
+ // Convert bridge traces to workflow action format
1365
+ functionTraces = bridgeTraces.map((trace, index) => ({
1366
+ type: 'function-trace',
1367
+ component: trace.component || 'unknown',
1368
+ args: trace.args || [],
1369
+ stack: trace.stack || '',
1370
+ timestamp: trace.timestamp || Date.now(),
1371
+ selector: '',
1372
+ x: null,
1373
+ y: null,
1374
+ value: null,
1375
+ text: null,
1376
+ placeholder: null,
1377
+ screenshot_data: null
1378
+ }));
1379
+ console.log(`[Chrome Debug] Retrieved ${functionTraces.length} function traces from runtime bridge`);
1380
+ }
1381
+ }
1382
+
1383
+ // Also include any function traces that were added to workflowActions
1384
+ const embeddedTraces = workflowActions.filter(action => action.type === 'function-trace');
1385
+ if (embeddedTraces.length > 0) {
1386
+ console.log(`[Chrome Debug] Found ${embeddedTraces.length} embedded function traces`);
1387
+ functionTraces = [...functionTraces, ...embeddedTraces];
1388
+ }
1389
+
1390
+ // Return the recorded workflow with proper functionTraces field
1391
+ return {
1392
+ actions: workflowActions,
1393
+ functionTraces: functionTraces // Now includes actual function traces with component details
1394
+ };
1395
+ }
1396
+
1397
+ // Capture DOM snapshot with form values and states
1398
+ function captureDOMSnapshot() {
1399
+ const snapshot = {
1400
+ html: document.documentElement.outerHTML,
1401
+ formData: {},
1402
+ checkboxStates: {},
1403
+ radioStates: {},
1404
+ selectValues: {},
1405
+ textareaValues: {},
1406
+ customElements: [],
1407
+ activeElement: null,
1408
+ modals: []
1409
+ };
1410
+
1411
+ // Capture form input values
1412
+ const inputs = document.querySelectorAll('input');
1413
+ inputs.forEach((input, index) => {
1414
+ const selector = getUniqueSelector(input);
1415
+ if (input.type === 'checkbox') {
1416
+ snapshot.checkboxStates[selector] = input.checked;
1417
+ } else if (input.type === 'radio') {
1418
+ snapshot.radioStates[selector] = {
1419
+ checked: input.checked,
1420
+ value: input.value,
1421
+ name: input.name
1422
+ };
1423
+ } else if (input.type !== 'file' && input.type !== 'password') {
1424
+ snapshot.formData[selector] = input.value;
1425
+ }
1426
+ });
1427
+
1428
+ // Capture select values
1429
+ const selects = document.querySelectorAll('select');
1430
+ selects.forEach(select => {
1431
+ const selector = getUniqueSelector(select);
1432
+ snapshot.selectValues[selector] = {
1433
+ value: select.value,
1434
+ selectedIndex: select.selectedIndex,
1435
+ options: Array.from(select.options).map(opt => ({
1436
+ value: opt.value,
1437
+ text: opt.text,
1438
+ selected: opt.selected
1439
+ }))
1440
+ };
1441
+ });
1442
+
1443
+ // Capture textarea values
1444
+ const textareas = document.querySelectorAll('textarea');
1445
+ textareas.forEach(textarea => {
1446
+ const selector = getUniqueSelector(textarea);
1447
+ snapshot.textareaValues[selector] = textarea.value;
1448
+ });
1449
+
1450
+ // Capture active element
1451
+ if (document.activeElement) {
1452
+ snapshot.activeElement = getUniqueSelector(document.activeElement);
1453
+ }
1454
+
1455
+ // Capture visible modals/dialogs
1456
+ const dialogs = document.querySelectorAll('dialog[open], [role="dialog"]:not([aria-hidden="true"])');
1457
+ dialogs.forEach(dialog => {
1458
+ snapshot.modals.push({
1459
+ selector: getUniqueSelector(dialog),
1460
+ isOpen: true,
1461
+ zIndex: window.getComputedStyle(dialog).zIndex
1462
+ });
1463
+ });
1464
+
1465
+ // Capture custom element states (e.g., web components)
1466
+ const customEls = document.querySelectorAll('[data-state], [aria-expanded], [aria-selected]');
1467
+ customEls.forEach(el => {
1468
+ snapshot.customElements.push({
1469
+ selector: getUniqueSelector(el),
1470
+ attributes: {
1471
+ 'data-state': el.getAttribute('data-state'),
1472
+ 'aria-expanded': el.getAttribute('aria-expanded'),
1473
+ 'aria-selected': el.getAttribute('aria-selected')
1474
+ }
1475
+ });
1476
+ });
1477
+
1478
+ return snapshot;
1479
+ }
1480
+
1481
+ // Capture storage data
1482
+ async function captureStorageData() {
1483
+ const storage = {
1484
+ localStorage: {},
1485
+ sessionStorage: {},
1486
+ cookies: []
1487
+ };
1488
+
1489
+ // Capture localStorage
1490
+ try {
1491
+ for (let i = 0; i < localStorage.length; i++) {
1492
+ const key = localStorage.key(i);
1493
+ storage.localStorage[key] = localStorage.getItem(key);
1494
+ }
1495
+ } catch (e) {
1496
+ console.error('[ChromePilot] Error capturing localStorage:', e);
1497
+ }
1498
+
1499
+ // Capture sessionStorage
1500
+ try {
1501
+ for (let i = 0; i < sessionStorage.length; i++) {
1502
+ const key = sessionStorage.key(i);
1503
+ storage.sessionStorage[key] = sessionStorage.getItem(key);
1504
+ }
1505
+ } catch (e) {
1506
+ console.error('[ChromePilot] Error capturing sessionStorage:', e);
1507
+ }
1508
+
1509
+ // Request cookies from background script
1510
+ try {
1511
+ const response = await chrome.runtime.sendMessage({
1512
+ action: 'getCookies',
1513
+ url: window.location.href
1514
+ });
1515
+ if (response && response.cookies) {
1516
+ storage.cookies = response.cookies;
1517
+ }
1518
+ } catch (e) {
1519
+ console.error('[ChromePilot] Error capturing cookies:', e);
1520
+ }
1521
+
1522
+ return storage;
1523
+ }
1524
+
1525
+ // Capture console logs (recent ones)
1526
+ function captureConsoleLogs() {
1527
+ // This would be populated by console interception
1528
+ // For now, return empty array - logs are captured separately
1529
+ return [];
1530
+ }
1531
+
1532
+ // Create restore point
1533
+ async function createRestorePoint(actionIndex) {
1534
+ console.log('[ChromePilot] Creating restore point at action index:', actionIndex);
1535
+
1536
+ try {
1537
+ // Capture all data
1538
+ const domSnapshot = captureDOMSnapshot();
1539
+ const storageData = await captureStorageData();
1540
+ const consoleLogs = captureConsoleLogs();
1541
+
1542
+ const restorePoint = {
1543
+ actionIndex: actionIndex,
1544
+ url: window.location.href,
1545
+ title: document.title,
1546
+ domSnapshot: domSnapshot,
1547
+ scrollX: window.scrollX,
1548
+ scrollY: window.scrollY,
1549
+ localStorage: storageData.localStorage,
1550
+ sessionStorage: storageData.sessionStorage,
1551
+ cookies: storageData.cookies,
1552
+ consoleLogs: consoleLogs,
1553
+ timestamp: Date.now()
1554
+ };
1555
+
1556
+ // Send to background script to store
1557
+ const response = await chrome.runtime.sendMessage({
1558
+ action: 'saveRestorePoint',
1559
+ restorePoint: restorePoint
1560
+ });
1561
+
1562
+ if (response && response.success) {
1563
+ console.log('[ChromePilot] Restore point saved:', response.restorePointId);
1564
+ // Visual feedback
1565
+ showRestorePointNotification('Restore point saved!');
1566
+ return response.restorePointId;
1567
+ } else {
1568
+ console.error('[ChromePilot] Failed to save restore point:', response);
1569
+ return null;
1570
+ }
1571
+ } catch (error) {
1572
+ console.error('[ChromePilot] Error creating restore point:', error);
1573
+ return null;
1574
+ }
1575
+ }
1576
+
1577
+ // Restore from a restore point
1578
+ async function restoreFromPoint(restorePointData) {
1579
+ console.log('[ChromePilot] Restoring from restore point');
1580
+
1581
+ try {
1582
+ // First check if we need to navigate
1583
+ if (restorePointData.url !== window.location.href) {
1584
+ // Request navigation and wait for it
1585
+ await chrome.runtime.sendMessage({
1586
+ action: 'navigateAndRestore',
1587
+ url: restorePointData.url,
1588
+ restorePointId: restorePointData.id
1589
+ });
1590
+ // Navigation will trigger restore after page loads
1591
+ return;
1592
+ }
1593
+
1594
+ // Restore localStorage
1595
+ try {
1596
+ localStorage.clear();
1597
+ Object.entries(restorePointData.localStorage).forEach(([key, value]) => {
1598
+ localStorage.setItem(key, value);
1599
+ });
1600
+ } catch (e) {
1601
+ console.error('[ChromePilot] Error restoring localStorage:', e);
1602
+ }
1603
+
1604
+ // Restore sessionStorage
1605
+ try {
1606
+ sessionStorage.clear();
1607
+ Object.entries(restorePointData.sessionStorage).forEach(([key, value]) => {
1608
+ sessionStorage.setItem(key, value);
1609
+ });
1610
+ } catch (e) {
1611
+ console.error('[ChromePilot] Error restoring sessionStorage:', e);
1612
+ }
1613
+
1614
+ // Restore DOM state
1615
+ const snapshot = restorePointData.domSnapshot;
1616
+
1617
+ // Restore form values
1618
+ Object.entries(snapshot.formData).forEach(([selector, value]) => {
1619
+ try {
1620
+ const element = document.querySelector(selector);
1621
+ if (element) element.value = value;
1622
+ } catch (e) {}
1623
+ });
1624
+
1625
+ // Restore checkboxes
1626
+ Object.entries(snapshot.checkboxStates).forEach(([selector, checked]) => {
1627
+ try {
1628
+ const element = document.querySelector(selector);
1629
+ if (element) element.checked = checked;
1630
+ } catch (e) {}
1631
+ });
1632
+
1633
+ // Restore radio buttons
1634
+ Object.entries(snapshot.radioStates).forEach(([selector, state]) => {
1635
+ try {
1636
+ const element = document.querySelector(selector);
1637
+ if (element && state.checked) element.checked = true;
1638
+ } catch (e) {}
1639
+ });
1640
+
1641
+ // Restore select values
1642
+ Object.entries(snapshot.selectValues).forEach(([selector, data]) => {
1643
+ try {
1644
+ const element = document.querySelector(selector);
1645
+ if (element) {
1646
+ element.value = data.value;
1647
+ element.selectedIndex = data.selectedIndex;
1648
+ }
1649
+ } catch (e) {}
1650
+ });
1651
+
1652
+ // Restore textarea values
1653
+ Object.entries(snapshot.textareaValues).forEach(([selector, value]) => {
1654
+ try {
1655
+ const element = document.querySelector(selector);
1656
+ if (element) element.value = value;
1657
+ } catch (e) {}
1658
+ });
1659
+
1660
+ // Restore custom element states
1661
+ snapshot.customElements.forEach(({ selector, attributes }) => {
1662
+ try {
1663
+ const element = document.querySelector(selector);
1664
+ if (element) {
1665
+ Object.entries(attributes).forEach(([attr, value]) => {
1666
+ if (value !== null) element.setAttribute(attr, value);
1667
+ });
1668
+ }
1669
+ } catch (e) {}
1670
+ });
1671
+
1672
+ // Restore scroll position
1673
+ window.scrollTo(restorePointData.scrollX, restorePointData.scrollY);
1674
+
1675
+ // Focus active element if any
1676
+ if (snapshot.activeElement) {
1677
+ try {
1678
+ const element = document.querySelector(snapshot.activeElement);
1679
+ if (element) element.focus();
1680
+ } catch (e) {}
1681
+ }
1682
+
1683
+ // Show notification
1684
+ showRestorePointNotification('State restored successfully!');
1685
+
1686
+ } catch (error) {
1687
+ console.error('[ChromePilot] Error restoring from restore point:', error);
1688
+ showRestorePointNotification('Error restoring state', true);
1689
+ }
1690
+ }
1691
+
1692
+ // Screen interaction recording functions
1693
+ function sendScreenInteraction(interaction) {
1694
+ if (!isScreenRecording || !isExtensionValid()) return;
1695
+
1696
+ chrome.runtime.sendMessage({
1697
+ action: 'screenInteraction',
1698
+ interaction: interaction
1699
+ }).catch(err => {
1700
+ // Ignore errors if extension context is invalid
1701
+ });
1702
+ }
1703
+
1704
+ async function recordScreenClick(event) {
1705
+ if (!isScreenRecording) return;
1706
+
1707
+ let element = event.target;
1708
+
1709
+ // If clicking on an SVG element or its children, find the parent button/link
1710
+ if (element.tagName === 'svg' || element.tagName === 'path' || element.tagName === 'g' || element.tagName === 'circle' || element.tagName === 'rect') {
1711
+ const clickableParent = element.closest('button, a, [role="button"], [onclick]');
1712
+ if (clickableParent) {
1713
+ element = clickableParent;
1714
+ }
1715
+ }
1716
+
1717
+ const interaction = {
1718
+ type: 'click',
1719
+ selector: getUniqueSelector(element),
1720
+ xpath: getXPath(element),
1721
+ x: event.clientX,
1722
+ y: event.clientY,
1723
+ timestamp: Date.now() - recordingScheduledStartTime // Relative timestamp from scheduled start
1724
+ };
1725
+
1726
+ // Add text content for buttons and links
1727
+ if (element.tagName === 'BUTTON' || element.tagName === 'A') {
1728
+ interaction.text = element.textContent.trim();
1729
+ }
1730
+
1731
+ // Enhanced click tracking data (conditional based on user setting)
1732
+ const enhanceCapture = await shouldEnhanceCapture();
1733
+ if (enhanceCapture) {
1734
+ try {
1735
+ console.log('[ChromePilot] Capturing enhanced screen click data...');
1736
+
1737
+ // Capture enhanced element data (only include fields with meaningful values)
1738
+ const capturedHTML = captureElementHTML(element);
1739
+ if (capturedHTML) {
1740
+ interaction.element_html = capturedHTML;
1741
+ }
1742
+
1743
+ const componentData = getEnhancedComponentInfo(element);
1744
+ if (componentData) {
1745
+ try {
1746
+ JSON.stringify(componentData); // Test if serializable
1747
+ interaction.component_data = componentData;
1748
+ } catch (e) {
1749
+ console.error('[ChromePilot] Component data cannot be serialized:', e.message);
1750
+ interaction.component_data = { error: 'Serialization failed', type: typeof componentData };
1751
+ }
1752
+ }
1753
+
1754
+ const eventHandlers = extractEventHandlers(element);
1755
+ if (eventHandlers) {
1756
+ try {
1757
+ JSON.stringify(eventHandlers); // Test if serializable
1758
+ interaction.event_handlers = eventHandlers;
1759
+ } catch (e) {
1760
+ console.error('[ChromePilot] Event handlers cannot be serialized:', e.message);
1761
+ interaction.event_handlers = { error: 'Serialization failed', type: typeof eventHandlers };
1762
+ }
1763
+ }
1764
+
1765
+ const elementState = captureElementState(element);
1766
+ if (elementState) {
1767
+ try {
1768
+ JSON.stringify(elementState); // Test if serializable
1769
+ interaction.element_state = elementState;
1770
+ } catch (e) {
1771
+ console.error('[ChromePilot] Element state cannot be serialized:', e.message);
1772
+ interaction.element_state = { error: 'Serialization failed', type: typeof elementState };
1773
+ }
1774
+ }
1775
+
1776
+ const perfMetrics = getPerformanceMetrics();
1777
+ if (perfMetrics) {
1778
+ try {
1779
+ JSON.stringify(perfMetrics); // Test if serializable
1780
+ interaction.performance_metrics = perfMetrics;
1781
+ } catch (e) {
1782
+ console.error('[ChromePilot] Performance metrics cannot be serialized:', e.message);
1783
+ interaction.performance_metrics = { error: 'Serialization failed', type: typeof perfMetrics };
1784
+ }
1785
+ }
1786
+
1787
+ console.log('[ChromePilot] Enhanced screen click data captured successfully');
1788
+ } catch (error) {
1789
+ console.warn('[ChromePilot] Error capturing enhanced screen click data:', error);
1790
+ // Continue with basic click recording if enhanced capture fails
1791
+ }
1792
+ } else {
1793
+ console.log('[ChromePilot] Enhanced screen click capture disabled - using basic capture only');
1794
+ }
1795
+
1796
+ sendScreenInteraction(interaction);
1797
+
1798
+ // Trigger visual feedback
1799
+ if (window.screenCaptureVisualFeedback) {
1800
+ window.screenCaptureVisualFeedback.handleScreenCaptureInteraction(interaction);
1801
+ }
1802
+ }
1803
+
1804
+ function recordScreenInput(event) {
1805
+ if (!isScreenRecording) return;
1806
+
1807
+ const element = event.target;
1808
+ const interaction = {
1809
+ type: 'input',
1810
+ selector: getUniqueSelector(element),
1811
+ xpath: getXPath(element),
1812
+ value: element.value,
1813
+ timestamp: Date.now() - recordingScheduledStartTime // Relative timestamp from scheduled start
1814
+ };
1815
+
1816
+ // Add input type for better context
1817
+ if (element.type) {
1818
+ interaction.inputType = element.type;
1819
+ }
1820
+
1821
+ // Add placeholder for context
1822
+ if (element.placeholder) {
1823
+ interaction.placeholder = element.placeholder;
1824
+ }
1825
+
1826
+ sendScreenInteraction(interaction);
1827
+
1828
+ // Trigger visual feedback
1829
+ if (window.screenCaptureVisualFeedback) {
1830
+ window.screenCaptureVisualFeedback.handleScreenCaptureInteraction(interaction);
1831
+ }
1832
+ }
1833
+
1834
+ function recordScreenKeypress(event) {
1835
+ if (!isScreenRecording) return;
1836
+
1837
+ // Only record special keys
1838
+ if (event.key === 'Enter' || event.key === 'Escape' || event.key === 'Tab') {
1839
+ const interaction = {
1840
+ type: 'keypress',
1841
+ key: event.key,
1842
+ timestamp: Date.now() - recordingScheduledStartTime // Relative timestamp from scheduled start
1843
+ };
1844
+
1845
+ sendScreenInteraction(interaction);
1846
+ }
1847
+ }
1848
+
1849
+ function recordScreenScroll(event) {
1850
+ if (!isScreenRecording) return;
1851
+
1852
+ // Throttle scroll events
1853
+ if (this.scrollTimeout) {
1854
+ clearTimeout(this.scrollTimeout);
1855
+ }
1856
+
1857
+ this.scrollTimeout = setTimeout(() => {
1858
+ const interaction = {
1859
+ type: 'scroll',
1860
+ scrollX: window.scrollX,
1861
+ scrollY: window.scrollY,
1862
+ timestamp: Date.now() - recordingScheduledStartTime // Relative timestamp from scheduled start
1863
+ };
1864
+
1865
+ sendScreenInteraction(interaction);
1866
+ }, 200);
1867
+ }
1868
+
1869
+ // Record screen drag start - mirrors workflow recordMouseDown
1870
+ function recordScreenMouseDown(event) {
1871
+ if (!isScreenRecording) return;
1872
+
1873
+ // Only track left mouse button
1874
+ if (event.button !== 0) return;
1875
+
1876
+ isScreenRecordingDragging = true;
1877
+ screenRecordingDragStartElement = event.target;
1878
+ screenRecordingDragStartX = event.clientX;
1879
+ screenRecordingDragStartY = event.clientY;
1880
+ }
1881
+
1882
+ // Record screen drag end - mirrors workflow recordMouseUp
1883
+ function recordScreenMouseUp(event) {
1884
+ if (!isScreenRecording || !isScreenRecordingDragging) return;
1885
+
1886
+ const endX = event.clientX;
1887
+ const endY = event.clientY;
1888
+
1889
+ // Only record if there was actual movement (same 5px threshold as workflow)
1890
+ const distance = Math.sqrt(Math.pow(endX - screenRecordingDragStartX, 2) + Math.pow(endY - screenRecordingDragStartY, 2));
1891
+ if (distance > 5) {
1892
+ const interaction = {
1893
+ type: 'drag',
1894
+ selector: getUniqueSelector(screenRecordingDragStartElement),
1895
+ xpath: getXPath(screenRecordingDragStartElement),
1896
+ x: screenRecordingDragStartX,
1897
+ y: screenRecordingDragStartY,
1898
+ value: JSON.stringify({
1899
+ endX: endX,
1900
+ endY: endY,
1901
+ distance: Math.round(distance)
1902
+ }),
1903
+ timestamp: Date.now() - recordingScheduledStartTime
1904
+ };
1905
+
1906
+ // Add element info if dragging specific elements (mirrors workflow logic)
1907
+ if (screenRecordingDragStartElement.tagName === 'IMG' || screenRecordingDragStartElement.draggable) {
1908
+ interaction.text = screenRecordingDragStartElement.tagName.toLowerCase();
1909
+ }
1910
+
1911
+ sendScreenInteraction(interaction);
1912
+
1913
+ // Trigger visual feedback
1914
+ if (window.screenCaptureVisualFeedback) {
1915
+ window.screenCaptureVisualFeedback.handleScreenCaptureInteraction(interaction);
1916
+ }
1917
+ }
1918
+
1919
+ isScreenRecordingDragging = false;
1920
+ screenRecordingDragStartElement = null;
1921
+ }
1922
+
1923
+ // Global variables for synchronized timing
1924
+ let recordingScheduledStartTime = null;
1925
+ let recordingSessionId = null;
1926
+
1927
+ // Start/stop screen interaction tracking
1928
+ function startScreenInteractionTracking(scheduledStartTime, sessionId) {
1929
+ // Store the scheduled start time and session ID for timestamp calculations
1930
+ recordingScheduledStartTime = scheduledStartTime;
1931
+ recordingSessionId = sessionId;
1932
+
1933
+ console.log('[ChromePilot] Screen interaction tracking scheduled to start at:', new Date(scheduledStartTime));
1934
+
1935
+ // Trigger visual feedback start
1936
+ if (window.screenCaptureVisualFeedback) {
1937
+ window.screenCaptureVisualFeedback.handleStartScreenCapture({ sessionId, scheduledStartTime });
1938
+ }
1939
+
1940
+ // Wait until the scheduled start time before activating tracking
1941
+ const waitTime = Math.max(0, scheduledStartTime - Date.now());
1942
+ setTimeout(() => {
1943
+ isScreenRecording = true;
1944
+
1945
+ // Add event listeners
1946
+ document.addEventListener('click', recordScreenClick, true);
1947
+ document.addEventListener('input', recordScreenInput, true);
1948
+ document.addEventListener('change', recordScreenInput, true);
1949
+ document.addEventListener('keydown', recordScreenKeypress, true);
1950
+ window.addEventListener('scroll', recordScreenScroll, true);
1951
+ document.addEventListener('mousedown', recordScreenMouseDown, true);
1952
+ document.addEventListener('mouseup', recordScreenMouseUp, true);
1953
+
1954
+ console.log('[ChromePilot] Screen interaction tracking started at scheduled time');
1955
+ }, waitTime);
1956
+ }
1957
+
1958
+ function stopScreenInteractionTracking() {
1959
+ isScreenRecording = false;
1960
+
1961
+ // Array of cleanup operations for systematic execution
1962
+ const cleanupOperations = [
1963
+ () => document.removeEventListener('click', recordScreenClick, true),
1964
+ () => document.removeEventListener('input', recordScreenInput, true),
1965
+ () => document.removeEventListener('change', recordScreenInput, true),
1966
+ () => document.removeEventListener('keydown', recordScreenKeypress, true),
1967
+ () => window.removeEventListener('scroll', recordScreenScroll, true),
1968
+ () => document.removeEventListener('mousedown', recordScreenMouseDown, true),
1969
+ () => document.removeEventListener('mouseup', recordScreenMouseUp, true)
1970
+ ];
1971
+
1972
+ // Execute all cleanup with individual error handling
1973
+ cleanupOperations.forEach((cleanup, index) => {
1974
+ try {
1975
+ cleanup();
1976
+ } catch (e) {
1977
+ console.warn(`[ChromePilot] Failed cleanup operation ${index}:`, e);
1978
+ }
1979
+ });
1980
+
1981
+ // ALWAYS reset ALL state variables regardless of errors above
1982
+ isScreenRecordingDragging = false;
1983
+ screenRecordingDragStartElement = null;
1984
+ screenRecordingDragStartX = 0; // CRITICAL: This was missing
1985
+ screenRecordingDragStartY = 0; // CRITICAL: This was missing
1986
+
1987
+ console.log('[ChromePilot] Screen interaction tracking stopped');
1988
+
1989
+ // Visual feedback stop with error isolation
1990
+ try {
1991
+ if (window.screenCaptureVisualFeedback) {
1992
+ window.screenCaptureVisualFeedback.handleStopScreenCapture({});
1993
+ }
1994
+ } catch (e) {
1995
+ console.warn('[ChromePilot] Failed to stop visual feedback:', e);
1996
+ }
1997
+ }
1998
+
1999
+ // Show restore point notification
2000
+ function showRestorePointNotification(message, isError = false) {
2001
+ const notification = document.createElement('div');
2002
+ notification.textContent = message;
2003
+ notification.style.cssText = `
2004
+ position: fixed;
2005
+ top: 20px;
2006
+ right: 20px;
2007
+ padding: 12px 24px;
2008
+ background: ${isError ? '#f44336' : '#4CAF50'};
2009
+ color: white;
2010
+ border-radius: 4px;
2011
+ font-family: Arial, sans-serif;
2012
+ font-size: 14px;
2013
+ z-index: 10000;
2014
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
2015
+ animation: slideIn 0.3s ease-out;
2016
+ `;
2017
+
2018
+ const style = document.createElement('style');
2019
+ style.textContent = `
2020
+ @keyframes slideIn {
2021
+ from { transform: translateX(100%); opacity: 0; }
2022
+ to { transform: translateX(0); opacity: 1; }
2023
+ }
2024
+ `;
2025
+ document.head.appendChild(style);
2026
+
2027
+ document.body.appendChild(notification);
2028
+
2029
+ setTimeout(() => {
2030
+ notification.style.animation = 'slideOut 0.3s ease-in forwards';
2031
+ notification.style.animationName = 'slideOut';
2032
+ setTimeout(() => {
2033
+ notification.remove();
2034
+ style.remove();
2035
+ }, 300);
2036
+ }, 3000);
2037
+ }
2038
+
2039
+ // Clean up when navigating away or extension is unloaded
2040
+ window.addEventListener('beforeunload', cleanup);
2041
+ // Use pagehide instead of deprecated unload event for better Chrome compatibility
2042
+ window.addEventListener('pagehide', cleanup);
2043
+
2044
+ // Clean up if extension context becomes invalid
2045
+ // Note: We don't need chrome.runtime.connect() here because:
2046
+ // 1. It requires a corresponding onConnect listener in background script
2047
+ // 2. The beforeunload/unload listeners already handle cleanup
2048
+ // 3. Extension invalidation is handled by isExtensionValid() checks
2049
+
2050
+ // Recording UI elements
2051
+ let recordingOverlay = null;
2052
+ let recordingStartTime = null;
2053
+ let recordingTimer = null;
2054
+ let isRecording = false;
2055
+
2056
+ // Screen recording interaction tracking
2057
+ let isScreenRecording = false;
2058
+
2059
+ // Screen recording drag detection - mirrors workflow recording logic
2060
+ // See recordMouseDown/Up (workflow) for reference implementation
2061
+ let isScreenRecordingDragging = false;
2062
+ let screenRecordingDragStartElement = null;
2063
+ let screenRecordingDragStartX = 0;
2064
+ let screenRecordingDragStartY = 0;
2065
+
2066
+ // Full data recording system components
2067
+ let fullDataRecording = null;
2068
+ let dataBuffer = null;
2069
+ // uploadManager is now centralized in background script
2070
+ let performanceMonitor = null;
2071
+ let piiRedactor = null;
2072
+ let domTracker = null;
2073
+ let networkTracker = null;
2074
+ let functionTracker = null;
2075
+ let currentFullRecordingId = null;
2076
+ // Disable full data recording permanently
2077
+ let isFullDataRecordingActive = false;
2078
+
2079
+ // Create floating recording interface
2080
+ function showRecordingInterface() {
2081
+ console.log('showRecordingInterface called');
2082
+
2083
+ // Remove any existing recording overlay
2084
+ if (recordingOverlay) {
2085
+ recordingOverlay.remove();
2086
+ }
2087
+
2088
+ // Create recording overlay
2089
+ recordingOverlay = document.createElement('div');
2090
+ recordingOverlay.id = 'chrome-pilot-recording-overlay';
2091
+ recordingOverlay.style.cssText = `
2092
+ position: fixed;
2093
+ bottom: 20px;
2094
+ right: 20px;
2095
+ width: 300px;
2096
+ background: white;
2097
+ border: 2px solid #f44336;
2098
+ border-radius: 8px;
2099
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
2100
+ z-index: 2147483647;
2101
+ font-family: Arial, sans-serif;
2102
+ padding: 20px;
2103
+ `;
2104
+
2105
+ recordingOverlay.innerHTML = `
2106
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
2107
+ <h3 style="margin: 0; font-size: 16px; color: #333;">Screen Recording</h3>
2108
+ <button id="chrome-pilot-close-recording" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #666;">&times;</button>
2109
+ </div>
2110
+ <p style="margin: 0 0 15px 0; font-size: 13px; color: #666;">
2111
+ Recording this tab with console logs
2112
+ </p>
2113
+ <div id="chrome-pilot-recording-status" style="text-align: center; margin-bottom: 15px; font-size: 14px; color: #666;"></div>
2114
+ <button id="chrome-pilot-record-btn" style="
2115
+ width: 100%;
2116
+ padding: 10px;
2117
+ background: #f44336;
2118
+ color: white;
2119
+ border: none;
2120
+ border-radius: 4px;
2121
+ cursor: pointer;
2122
+ font-size: 14px;
2123
+ font-weight: 500;
2124
+ ">Start Recording</button>
2125
+ <div id="chrome-pilot-recording-result" style="margin-top: 15px; display: none;"></div>
2126
+ `;
2127
+
2128
+ document.body.appendChild(recordingOverlay);
2129
+
2130
+ // Add event listeners
2131
+ document.getElementById('chrome-pilot-close-recording').addEventListener('click', () => {
2132
+ if (isRecording) {
2133
+ if (confirm('Stop recording and close?')) {
2134
+ stopRecording();
2135
+ recordingOverlay.remove();
2136
+ recordingOverlay = null;
2137
+ }
2138
+ } else {
2139
+ recordingOverlay.remove();
2140
+ recordingOverlay = null;
2141
+ }
2142
+ });
2143
+
2144
+ document.getElementById('chrome-pilot-record-btn').addEventListener('click', () => {
2145
+ if (!isRecording) {
2146
+ startRecording();
2147
+ } else {
2148
+ stopRecording();
2149
+ }
2150
+ });
2151
+ }
2152
+
2153
+ // Start recording from content script
2154
+ function startRecording() {
2155
+ const recordBtn = document.getElementById('chrome-pilot-record-btn');
2156
+ const statusDiv = document.getElementById('chrome-pilot-recording-status');
2157
+
2158
+ recordBtn.disabled = true;
2159
+ recordBtn.textContent = 'Starting...';
2160
+
2161
+ // Request recording from background script
2162
+ chrome.runtime.sendMessage({
2163
+ action: 'startRecordingFromContent'
2164
+ }, (response) => {
2165
+ if (chrome.runtime.lastError) {
2166
+ console.error('Error starting recording:', chrome.runtime.lastError);
2167
+ statusDiv.textContent = 'Error: ' + chrome.runtime.lastError.message;
2168
+ recordBtn.disabled = false;
2169
+ recordBtn.textContent = 'Start Recording';
2170
+ return;
2171
+ }
2172
+
2173
+ if (response && response.success) {
2174
+ isRecording = true;
2175
+ recordBtn.disabled = false;
2176
+ recordBtn.textContent = 'Stop Recording';
2177
+ recordBtn.style.background = '#4CAF50';
2178
+ recordingStartTime = Date.now();
2179
+
2180
+ // Start performance monitoring when recording begins
2181
+ if (performanceMonitor) {
2182
+ performanceMonitor.startMonitoring();
2183
+ console.log('[ChromeDebug MCP] Performance monitoring started with recording');
2184
+ }
2185
+
2186
+ // Start timer display
2187
+ recordingTimer = setInterval(() => {
2188
+ const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
2189
+ const minutes = Math.floor(elapsed / 60);
2190
+ const seconds = elapsed % 60;
2191
+ statusDiv.innerHTML = `<span style="color: #f44336; font-weight: bold;">● Recording: ${minutes}:${seconds.toString().padStart(2, '0')}</span>`;
2192
+ }, 1000);
2193
+ } else {
2194
+ statusDiv.textContent = 'Error: ' + (response?.error || 'Failed to start recording');
2195
+ recordBtn.disabled = false;
2196
+ recordBtn.textContent = 'Start Recording';
2197
+ }
2198
+ });
2199
+ }
2200
+
2201
+ // Stop recording
2202
+ function stopRecording() {
2203
+ const recordBtn = document.getElementById('chrome-pilot-record-btn');
2204
+ const statusDiv = document.getElementById('chrome-pilot-recording-status');
2205
+ const resultDiv = document.getElementById('chrome-pilot-recording-result');
2206
+
2207
+ recordBtn.disabled = true;
2208
+ recordBtn.textContent = 'Stopping...';
2209
+
2210
+ if (recordingTimer) {
2211
+ clearInterval(recordingTimer);
2212
+ recordingTimer = null;
2213
+ }
2214
+
2215
+ // Show processing message
2216
+ statusDiv.innerHTML = '<span style="color: #ff9800;">Processing recording...</span>';
2217
+
2218
+ // Request stop from background script
2219
+ chrome.runtime.sendMessage({
2220
+ action: 'stopRecordingFromContent'
2221
+ }, (response) => {
2222
+ if (chrome.runtime.lastError) {
2223
+ console.error('Error stopping recording:', chrome.runtime.lastError);
2224
+ statusDiv.textContent = 'Error: ' + chrome.runtime.lastError.message;
2225
+ recordBtn.disabled = false;
2226
+ recordBtn.textContent = 'Stop Recording';
2227
+ return;
2228
+ }
2229
+
2230
+ // Update status to show upload in progress
2231
+ if (response && response.success) {
2232
+ statusDiv.innerHTML = '<span style="color: #2196F3;">Uploading recording...</span>';
2233
+ // The recordingComplete message will show the ID when upload is done
2234
+ }
2235
+
2236
+ isRecording = false;
2237
+ recordBtn.disabled = false;
2238
+ recordBtn.textContent = 'Start Recording';
2239
+ recordBtn.style.background = '#f44336';
2240
+
2241
+ // Stop performance monitoring when recording ends
2242
+ if (performanceMonitor) {
2243
+ performanceMonitor.stopMonitoring();
2244
+ console.log('[ChromeDebug MCP] Performance monitoring stopped with recording');
2245
+ }
2246
+ });
2247
+ }
2248
+
2249
+ // Show frame capture flash indicator
2250
+ function showFrameFlash() {
2251
+ if (!frameFlashIndicator) {
2252
+ frameFlashIndicator = document.createElement('div');
2253
+ frameFlashIndicator.style.cssText = `
2254
+ position: fixed;
2255
+ top: 0;
2256
+ left: 0;
2257
+ width: 100vw;
2258
+ height: 100vh;
2259
+ background: rgba(255, 255, 255, 0.3);
2260
+ pointer-events: none;
2261
+ z-index: 999999;
2262
+ opacity: 0;
2263
+ transition: opacity 0.1s ease-in-out;
2264
+ `;
2265
+ document.body.appendChild(frameFlashIndicator);
2266
+ }
2267
+
2268
+ // Flash effect
2269
+ frameFlashIndicator.style.opacity = '1';
2270
+ setTimeout(() => {
2271
+ frameFlashIndicator.style.opacity = '0';
2272
+ }, 100);
2273
+ }
2274
+
2275
+ // ============================================================================
2276
+ // FULL DATA RECORDING SYSTEM INTEGRATION
2277
+ // ============================================================================
2278
+
2279
+ // Initialize full data recording system (DISABLED)
2280
+ /*
2281
+ async function initializeFullDataRecordingSystem() {
2282
+ try {
2283
+ console.log('[ChromeDebug MCP] Initializing full data recording system...');
2284
+
2285
+ // Initialize core components
2286
+ if (typeof DataBuffer !== 'undefined') {
2287
+ dataBuffer = new DataBuffer();
2288
+ await dataBuffer.init();
2289
+ }
2290
+
2291
+ // UploadManager is now centralized in background script
2292
+ // Content script will communicate via message passing
2293
+
2294
+ if (typeof PIIRedactor !== 'undefined') {
2295
+ piiRedactor = new PIIRedactor();
2296
+ }
2297
+
2298
+ if (typeof PerformanceMonitor !== 'undefined') {
2299
+ performanceMonitor = new PerformanceMonitor();
2300
+ performanceMonitor.init(dataBuffer, {
2301
+ onThrottleRequired: (alerts) => {
2302
+ console.warn('[ChromeDebug MCP] Performance throttling required:', JSON.stringify(alerts, null, 2));
2303
+ // Could automatically reduce instrumentation level
2304
+ },
2305
+ onPerformanceAlert: (alerts) => {
2306
+ console.warn('[ChromeDebug MCP] Performance alerts:', JSON.stringify(alerts, null, 2));
2307
+ }
2308
+ });
2309
+ }
2310
+
2311
+ // Initialize data collection components
2312
+ if (typeof DOMTracker !== 'undefined') {
2313
+ domTracker = new DOMTracker();
2314
+ }
2315
+
2316
+ if (typeof NetworkTracker !== 'undefined') {
2317
+ networkTracker = new NetworkTracker();
2318
+ }
2319
+
2320
+ if (typeof FunctionTracker !== 'undefined') {
2321
+ // Create FunctionTracker with onDataReady callback to persist data
2322
+ functionTracker = new FunctionTracker(
2323
+ performanceMonitor,
2324
+ piiRedactor,
2325
+ // onDataReady callback - called when function traces need to be persisted
2326
+ async (events) => {
2327
+ if (dataBuffer) {
2328
+ try {
2329
+ await dataBuffer.addBatch(events);
2330
+ console.log(`[Chrome Debug] Persisted ${events.length} function traces`);
2331
+ } catch (error) {
2332
+ console.error('[ChromeDebug MCP] Failed to persist function traces:', error);
2333
+ }
2334
+ }
2335
+ }
2336
+ );
2337
+ }
2338
+
2339
+ // Expose ChromePilotTracker API for manual instrumentation
2340
+ if (!window.ChromePilotTracker) {
2341
+ window.ChromePilotTracker = {
2342
+ isRecording: false,
2343
+ _functionTracker: functionTracker,
2344
+
2345
+ // Register a single function for tracing
2346
+ register: function(name, fn) {
2347
+ if (!this.isRecording || typeof fn !== 'function') {
2348
+ return fn; // Return original function if not recording
2349
+ }
2350
+
2351
+ if (!this._functionTracker) {
2352
+ console.warn('[ChromePilotTracker] Function tracker not available');
2353
+ return fn;
2354
+ }
2355
+
2356
+ // Use the existing wrapFunction from function-tracker
2357
+ return this._functionTracker.wrapFunction(fn, name, {
2358
+ isUserDefined: true,
2359
+ registeredManually: true
2360
+ });
2361
+ },
2362
+
2363
+ // Wrap an entire object/module
2364
+ wrap: function(obj, options = {}) {
2365
+ if (!this.isRecording || !obj || typeof obj !== 'object') {
2366
+ return obj; // Return original if not recording or invalid
2367
+ }
2368
+
2369
+ if (!this._functionTracker) {
2370
+ console.warn('[ChromePilotTracker] Function tracker not available');
2371
+ return obj;
2372
+ }
2373
+
2374
+ const self = this;
2375
+ const prefix = options.prefix || obj.constructor?.name || 'Object';
2376
+
2377
+ // Use Proxy to intercept method calls
2378
+ return new Proxy(obj, {
2379
+ get(target, prop, receiver) {
2380
+ const original = Reflect.get(target, prop, receiver);
2381
+
2382
+ // Only wrap functions
2383
+ if (typeof original === 'function') {
2384
+ const functionName = `${prefix}.${String(prop)}`;
2385
+
2386
+ // Check if this method should be ignored
2387
+ if (options.ignore && options.ignore.includes(String(prop))) {
2388
+ return original;
2389
+ }
2390
+
2391
+ // Wrap the function
2392
+ return self._functionTracker.wrapFunction(original, functionName, {
2393
+ isUserDefined: true,
2394
+ registeredManually: true,
2395
+ parentObject: prefix
2396
+ });
2397
+ }
2398
+
2399
+ return original;
2400
+ }
2401
+ });
2402
+ },
2403
+
2404
+ // Update recording status
2405
+ _setRecordingStatus: function(status) {
2406
+ this.isRecording = status;
2407
+ }
2408
+ };
2409
+
2410
+ console.log('[ChromeDebug MCP] ChromePilotTracker API exposed for manual instrumentation');
2411
+ }
2412
+
2413
+ console.log('[ChromeDebug MCP] Full data recording system initialized successfully');
2414
+ return true;
2415
+
2416
+ } catch (error) {
2417
+ console.error('[ChromeDebug MCP] Failed to initialize full data recording system:', error);
2418
+ return false;
2419
+ }
2420
+ }
2421
+
2422
+ // Start full data recording with user configuration (DISABLED)
2423
+ async function startFullDataRecording(config) {
2424
+ try {
2425
+ if (isFullDataRecordingActive) {
2426
+ console.warn('[ChromeDebug MCP] Full data recording already active');
2427
+ return;
2428
+ }
2429
+
2430
+ console.log('[ChromeDebug MCP] Starting full data recording with config:', config);
2431
+
2432
+ // Initialize system if not already done
2433
+ if (!dataBuffer) {
2434
+ const initialized = await initializeFullDataRecordingSystem();
2435
+ if (!initialized) {
2436
+ throw new Error('Failed to initialize recording system');
2437
+ }
2438
+ }
2439
+
2440
+ // Use the recording ID from config if provided, otherwise generate one
2441
+ const recordingId = config.recordingId || `recording_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2442
+ currentFullRecordingId = recordingId; // Store globally for stop function
2443
+
2444
+ // DataBuffer configuration is handled internally
2445
+ // Store the recording ID in chrome.storage for the dataBuffer to use
2446
+ if (dataBuffer && chrome.storage && chrome.storage.local) {
2447
+ await chrome.storage.local.set({ currentRecordingId: recordingId });
2448
+ }
2449
+
2450
+ // Data ready callback for all trackers
2451
+ const onDataReady = (events) => {
2452
+ if (dataBuffer) {
2453
+ events.forEach(event => dataBuffer.addEvent(event));
2454
+ }
2455
+ };
2456
+
2457
+ // Start performance monitoring (lazy loading - only when recording)
2458
+ if (performanceMonitor) {
2459
+ performanceMonitor.startMonitoring();
2460
+ console.log('[ChromeDebug MCP] Performance monitoring started');
2461
+ }
2462
+
2463
+ // Configure and start DOM tracking
2464
+ if (domTracker && config.captureTypes.domMutations) {
2465
+ domTracker.init({
2466
+ recordingId: recordingId,
2467
+ onDataReady: onDataReady,
2468
+ piiRedactor: piiRedactor,
2469
+ performanceMonitor: performanceMonitor,
2470
+ mutationThrottle: config.instrumentationLevel <= 2 ? 20 : 50 // Throttle for lower levels
2471
+ });
2472
+ domTracker.startTracking();
2473
+ console.log('[ChromeDebug MCP] DOM tracking started');
2474
+ }
2475
+
2476
+ // Configure and start network tracking
2477
+ if (networkTracker && config.captureTypes.networkRequests) {
2478
+ networkTracker.init({
2479
+ recordingId: recordingId,
2480
+ onDataReady: onDataReady,
2481
+ piiRedactor: piiRedactor,
2482
+ performanceMonitor: performanceMonitor,
2483
+ captureRequestBody: config.instrumentationLevel >= 3,
2484
+ captureResponseBody: config.instrumentationLevel >= 4,
2485
+ maxBodySize: config.instrumentationLevel >= 4 ? 50 * 1024 : 10 * 1024
2486
+ });
2487
+ networkTracker.startTracking();
2488
+ console.log('[ChromeDebug MCP] Network tracking started');
2489
+ }
2490
+
2491
+ // Configure and start function tracking
2492
+ if (functionTracker && config.captureTypes.functionCalls) {
2493
+ // Create a wrapper that sends data to background script for upload
2494
+ const functionTrackerDataReady = (data) => {
2495
+ if (Array.isArray(data) && data.length > 0) {
2496
+ // Send batch to background script for upload
2497
+ chrome.runtime.sendMessage({
2498
+ type: 'UPLOAD_BATCH',
2499
+ recordingId: recordingId,
2500
+ events: data
2501
+ }, (response) => {
2502
+ if (chrome.runtime.lastError) {
2503
+ console.error('[ChromeDebug MCP] Failed to send function traces to background:', chrome.runtime.lastError);
2504
+ } else {
2505
+ console.log(`[Chrome Debug] Sent ${data.length} function traces to background for upload`);
2506
+ }
2507
+ });
2508
+ }
2509
+ };
2510
+
2511
+ functionTracker.init({
2512
+ recordingId: recordingId,
2513
+ onDataReady: functionTrackerDataReady,
2514
+ piiRedactor: piiRedactor,
2515
+ performanceMonitor: performanceMonitor,
2516
+ maxCallDepth: config.instrumentationLevel <= 2 ? 5 : 10,
2517
+ trackReturnValues: config.instrumentationLevel >= 3,
2518
+ targetCategories: {
2519
+ userDefined: true,
2520
+ eventHandlers: config.instrumentationLevel >= 2,
2521
+ asyncFunctions: config.instrumentationLevel >= 3,
2522
+ domMethods: config.instrumentationLevel >= 4,
2523
+ nativeAPIs: config.instrumentationLevel >= 5
2524
+ }
2525
+ });
2526
+ functionTracker.startTracking();
2527
+
2528
+ // Update ChromePilotTracker status
2529
+ if (window.ChromePilotTracker) {
2530
+ window.ChromePilotTracker._setRecordingStatus(true);
2531
+ window.ChromePilotTracker._functionTracker = functionTracker;
2532
+ }
2533
+
2534
+ console.log('[ChromeDebug MCP] Function tracking started');
2535
+ }
2536
+
2537
+ // Start upload manager via background script
2538
+ chrome.runtime.sendMessage({
2539
+ type: 'START_UPLOAD_MANAGER'
2540
+ }, (response) => {
2541
+ if (chrome.runtime.lastError) {
2542
+ console.error('[ChromeDebug MCP] Error starting upload manager:', chrome.runtime.lastError.message);
2543
+ } else {
2544
+ console.log('[ChromeDebug MCP] Upload manager started via background script');
2545
+ }
2546
+ });
2547
+
2548
+ isFullDataRecordingActive = true;
2549
+
2550
+ // Full data recording indicator removed - functionality disabled
2551
+
2552
+ console.log('[ChromeDebug MCP] Full data recording started successfully:', recordingId);
2553
+
2554
+ } catch (error) {
2555
+ console.error('[ChromeDebug MCP] Failed to start full data recording:', error);
2556
+ throw error;
2557
+ }
2558
+ }
2559
+
2560
+ // Stop full data recording (DISABLED)
2561
+ async function stopFullDataRecording() {
2562
+ try {
2563
+ if (!isFullDataRecordingActive) {
2564
+ console.warn('[ChromeDebug MCP] Full data recording not active');
2565
+ return;
2566
+ }
2567
+
2568
+ console.log('[ChromeDebug MCP] Stopping full data recording...');
2569
+
2570
+ // Stop all trackers
2571
+ if (domTracker) {
2572
+ domTracker.stopTracking();
2573
+ console.log('[ChromeDebug MCP] DOM tracking stopped');
2574
+ }
2575
+
2576
+ if (networkTracker) {
2577
+ networkTracker.stopTracking();
2578
+ console.log('[ChromeDebug MCP] Network tracking stopped');
2579
+ }
2580
+
2581
+ if (functionTracker) {
2582
+ functionTracker.stopTracking();
2583
+
2584
+ // Update ChromePilotTracker status
2585
+ if (window.ChromePilotTracker) {
2586
+ window.ChromePilotTracker._setRecordingStatus(false);
2587
+ }
2588
+
2589
+ console.log('[ChromeDebug MCP] Function tracking stopped');
2590
+ }
2591
+
2592
+ // Stop performance monitoring
2593
+ if (performanceMonitor) {
2594
+ performanceMonitor.stopMonitoring();
2595
+ console.log('[ChromeDebug MCP] Performance monitoring stopped');
2596
+ }
2597
+
2598
+ // Create final batch for any remaining events
2599
+ if (dataBuffer && chrome.storage && chrome.storage.local) {
2600
+ try {
2601
+ // Get the current recording ID
2602
+ const result = await chrome.storage.local.get(['currentRecordingId']);
2603
+ if (result.currentRecordingId) {
2604
+ // Create a batch with any remaining events
2605
+ await dataBuffer.createBatch(result.currentRecordingId);
2606
+ console.log('[ChromeDebug MCP] Final batch created for remaining events');
2607
+ }
2608
+ } catch (error) {
2609
+ console.warn('[ChromeDebug MCP] Could not create final batch:', error);
2610
+ }
2611
+ }
2612
+
2613
+ // Finalize uploads via background script
2614
+ await new Promise((resolve) => {
2615
+ chrome.runtime.sendMessage({
2616
+ type: 'FINALIZE_UPLOADS',
2617
+ recordingId: currentFullRecordingId
2618
+ }, (response) => {
2619
+ if (chrome.runtime.lastError) {
2620
+ console.error('[ChromeDebug MCP] Error finalizing uploads:', chrome.runtime.lastError.message);
2621
+ } else {
2622
+ console.log('[ChromeDebug MCP] Upload manager stopped via background script');
2623
+ }
2624
+ resolve();
2625
+ });
2626
+ });
2627
+
2628
+ isFullDataRecordingActive = false;
2629
+ currentFullRecordingId = null; // Clear the recording ID
2630
+
2631
+ // Full data recording indicator removed - functionality disabled
2632
+
2633
+ console.log('[ChromeDebug MCP] Full data recording stopped successfully');
2634
+
2635
+ } catch (error) {
2636
+ console.error('[ChromeDebug MCP] Failed to stop full data recording:', error);
2637
+ throw error;
2638
+ }
2639
+ }
2640
+
2641
+
2642
+ // Full data recording indicator functions removed - functionality disabled
2643
+ */
2644
+
2645
+ // Screen Capture Visual Feedback System
2646
+ class ScreenCaptureVisualFeedback {
2647
+ constructor() {
2648
+ this.actionCount = 0;
2649
+ this.isRecording = false;
2650
+ this.actionCounterElement = null;
2651
+ this.recordingIndicatorElement = null;
2652
+ this.activeHighlights = new Set();
2653
+ this.countdownInterval = null;
2654
+
2655
+ // Bind methods to preserve context
2656
+ this.handleStartScreenCapture = this.handleStartScreenCapture.bind(this);
2657
+ this.handleScreenCaptureInteraction = this.handleScreenCaptureInteraction.bind(this);
2658
+ this.handleFrameCaptured = this.handleFrameCaptured.bind(this);
2659
+ this.handleStopScreenCapture = this.handleStopScreenCapture.bind(this);
2660
+
2661
+ this.init();
2662
+ }
2663
+
2664
+ init() {
2665
+ // Listen for screen capture events
2666
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
2667
+ switch (message.type) {
2668
+ case 'start-screen-capture-tracking':
2669
+ this.handleStartScreenCapture(message);
2670
+ break;
2671
+ case 'screen-capture-interaction':
2672
+ this.handleScreenCaptureInteraction(message);
2673
+ break;
2674
+ case 'screen-capture-frame-captured':
2675
+ this.handleFrameCaptured(message);
2676
+ break;
2677
+ case 'stop-screen-capture-tracking':
2678
+ this.handleStopScreenCapture(message);
2679
+ break;
2680
+ }
2681
+ });
2682
+ }
2683
+
2684
+ handleStartScreenCapture(message) {
2685
+ this.isRecording = true;
2686
+ this.actionCount = 0;
2687
+ this.showRecordingIndicator();
2688
+ // Action counter is now part of the unified indicator, initialized to 0 in HTML
2689
+ }
2690
+
2691
+ handleScreenCaptureInteraction(interaction) {
2692
+ if (!this.isRecording) return;
2693
+
2694
+ this.actionCount++;
2695
+ this.showActionCounter(this.actionCount);
2696
+
2697
+ // Show click highlight for click interactions
2698
+ if (interaction.type === 'click' && interaction.x !== undefined && interaction.y !== undefined) {
2699
+ this.showClickHighlight(interaction.x, interaction.y);
2700
+ }
2701
+
2702
+ // Send interaction data to background
2703
+ chrome.runtime.sendMessage({
2704
+ type: 'screen-capture-interaction-logged',
2705
+ interaction: {
2706
+ type: interaction.type,
2707
+ x: interaction.x,
2708
+ y: interaction.y,
2709
+ timestamp: Date.now(),
2710
+ target: interaction.target,
2711
+ value: interaction.value
2712
+ }
2713
+ });
2714
+ }
2715
+
2716
+ handleFrameCaptured(message) {
2717
+ // Frame captured - could update recording indicator if needed
2718
+ if (this.recordingIndicatorElement) {
2719
+ // Add a subtle pulse effect to indicate frame capture
2720
+ this.recordingIndicatorElement.style.animation = 'screen-capture-pulse 0.3s ease-in-out';
2721
+ setTimeout(() => {
2722
+ if (this.recordingIndicatorElement) {
2723
+ this.recordingIndicatorElement.style.animation = '';
2724
+ }
2725
+ }, 300);
2726
+ }
2727
+ }
2728
+
2729
+ handleStopScreenCapture(message) {
2730
+ this.isRecording = false;
2731
+ this.hideRecordingIndicator();
2732
+ this.hideActionCounter();
2733
+ this.cleanup();
2734
+ }
2735
+
2736
+ showClickHighlight(x, y) {
2737
+ const highlight = document.createElement('div');
2738
+ highlight.className = 'screen-capture-click-highlight';
2739
+ highlight.style.left = `${x}px`;
2740
+ highlight.style.top = `${y}px`;
2741
+
2742
+ document.body.appendChild(highlight);
2743
+ this.activeHighlights.add(highlight);
2744
+
2745
+ // Remove highlight after animation
2746
+ setTimeout(() => {
2747
+ if (highlight && highlight.parentNode) {
2748
+ highlight.parentNode.removeChild(highlight);
2749
+ }
2750
+ this.activeHighlights.delete(highlight);
2751
+ }, 500);
2752
+ }
2753
+
2754
+ showActionCounter(count) {
2755
+ // Update the inline action count in the unified indicator
2756
+ if (this.recordingIndicatorElement) {
2757
+ const actionBadge = this.recordingIndicatorElement.querySelector('.action-count-badge');
2758
+ if (actionBadge) {
2759
+ actionBadge.textContent = `${count} actions`;
2760
+ }
2761
+ }
2762
+ }
2763
+
2764
+ hideActionCounter() {
2765
+ // Action counter is now part of unified indicator, handled by hideRecordingIndicator
2766
+ }
2767
+
2768
+ showRecordingIndicator() {
2769
+ if (!this.recordingIndicatorElement) {
2770
+ // Add required CSS styles if not already added
2771
+ if (!document.getElementById('screen-capture-styles')) {
2772
+ const style = document.createElement('style');
2773
+ style.id = 'screen-capture-styles';
2774
+ style.textContent = `
2775
+ @keyframes pulse {
2776
+ 0% { opacity: 1; }
2777
+ 50% { opacity: 0.5; }
2778
+ 100% { opacity: 1; }
2779
+ }
2780
+ @keyframes screen-capture-pulse {
2781
+ 0% { opacity: 1; transform: scale(1); }
2782
+ 50% { opacity: 0.7; transform: scale(1.1); }
2783
+ 100% { opacity: 1; transform: scale(1); }
2784
+ }
2785
+ .screen-capture-click-highlight {
2786
+ position: fixed;
2787
+ width: 40px;
2788
+ height: 40px;
2789
+ border: 3px solid #4CAF50;
2790
+ border-radius: 50%;
2791
+ background: rgba(76, 175, 80, 0.3);
2792
+ transform: translate(-50%, -50%);
2793
+ pointer-events: none;
2794
+ z-index: 2147483646;
2795
+ animation: screen-capture-ripple 0.5s ease-out;
2796
+ }
2797
+ @keyframes screen-capture-ripple {
2798
+ 0% {
2799
+ transform: translate(-50%, -50%) scale(0);
2800
+ opacity: 1;
2801
+ }
2802
+ 100% {
2803
+ transform: translate(-50%, -50%) scale(1);
2804
+ opacity: 0;
2805
+ }
2806
+ }
2807
+ `;
2808
+ document.head.appendChild(style);
2809
+ }
2810
+
2811
+ this.recordingIndicatorElement = document.createElement('div');
2812
+ this.recordingIndicatorElement.className = 'screen-capture-recording-indicator-unified';
2813
+
2814
+ // Use inline styles to match workflow recording exactly
2815
+ Object.assign(this.recordingIndicatorElement.style, {
2816
+ position: 'fixed',
2817
+ bottom: '80px', // Keep different from workflow to avoid collision
2818
+ left: '20px',
2819
+ background: 'rgba(0, 0, 0, 0.9)',
2820
+ color: 'white',
2821
+ padding: '12px 20px',
2822
+ borderRadius: '25px',
2823
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif',
2824
+ fontSize: '14px',
2825
+ fontWeight: '500',
2826
+ zIndex: '2147483647',
2827
+ display: 'flex',
2828
+ alignItems: 'center',
2829
+ gap: '8px',
2830
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
2831
+ backdropFilter: 'blur(10px)',
2832
+ WebkitBackdropFilter: 'blur(10px)',
2833
+ pointerEvents: 'none',
2834
+ userSelect: 'none'
2835
+ });
2836
+
2837
+ // Start with countdown
2838
+ this.startCountdownSequence();
2839
+ document.body.appendChild(this.recordingIndicatorElement);
2840
+ }
2841
+
2842
+ this.recordingIndicatorElement.style.display = 'flex';
2843
+ }
2844
+
2845
+ startCountdownSequence() {
2846
+ let count = 3;
2847
+
2848
+ // Initial countdown display
2849
+ this.recordingIndicatorElement.innerHTML = `
2850
+ <span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
2851
+ <span>Starting in ${count}...</span>
2852
+ <span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
2853
+ `;
2854
+
2855
+ this.countdownInterval = setInterval(() => {
2856
+ count--;
2857
+ if (count > 0) {
2858
+ // Update countdown
2859
+ this.recordingIndicatorElement.innerHTML = `
2860
+ <span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
2861
+ <span>Starting in ${count}...</span>
2862
+ <span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
2863
+ `;
2864
+ } else {
2865
+ // Switch to recording display
2866
+ this.recordingIndicatorElement.innerHTML = `
2867
+ <span style="color: #4CAF50; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
2868
+ <span>Recording Screen Capture...</span>
2869
+ <span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
2870
+ `;
2871
+ clearInterval(this.countdownInterval);
2872
+ this.countdownInterval = null;
2873
+ }
2874
+ }, 1000);
2875
+ }
2876
+
2877
+ hideRecordingIndicator() {
2878
+ // Clear countdown interval if running
2879
+ if (this.countdownInterval) {
2880
+ clearInterval(this.countdownInterval);
2881
+ this.countdownInterval = null;
2882
+ }
2883
+
2884
+ if (this.recordingIndicatorElement) {
2885
+ // CRITICAL FIX: Remove from DOM instead of just hiding
2886
+ if (this.recordingIndicatorElement.parentNode) {
2887
+ this.recordingIndicatorElement.parentNode.removeChild(this.recordingIndicatorElement);
2888
+ }
2889
+ this.recordingIndicatorElement = null;
2890
+ }
2891
+ }
2892
+
2893
+ cleanup() {
2894
+ // Clear countdown interval if running
2895
+ if (this.countdownInterval) {
2896
+ clearInterval(this.countdownInterval);
2897
+ this.countdownInterval = null;
2898
+ }
2899
+
2900
+ // Remove all active highlights
2901
+ this.activeHighlights.forEach(highlight => {
2902
+ if (highlight && highlight.parentNode) {
2903
+ highlight.parentNode.removeChild(highlight);
2904
+ }
2905
+ });
2906
+ this.activeHighlights.clear();
2907
+
2908
+ // Hide indicators
2909
+ this.hideRecordingIndicator();
2910
+
2911
+ // Reset action count
2912
+ this.actionCount = 0;
2913
+ }
2914
+ }
2915
+
2916
+ // Initialize Screen Capture Visual Feedback System
2917
+ if (window === window.top) {
2918
+ window.screenCaptureVisualFeedback = new ScreenCaptureVisualFeedback();
2919
+ }
2920
+
2921
+ // Initialize basic workflow recording components (lightweight)
2922
+ setTimeout(() => {
2923
+ // Only initialize if we're not in an iframe and basic functions are needed
2924
+ if (window === window.top) {
2925
+ // Initialize essential globals for workflow recording
2926
+ if (typeof window.chromePilotWorkflowData === 'undefined') {
2927
+ window.chromePilotWorkflowData = {
2928
+ isRecording: false,
2929
+ sessionId: null,
2930
+ actions: [],
2931
+ startTime: null
2932
+ };
2933
+ }
2934
+
2935
+ // Initialize FunctionTracker for workflow recording if not already available
2936
+ if (!window.ChromePilotTracker && typeof FunctionTracker !== 'undefined') {
2937
+ // Create a lightweight FunctionTracker for workflow recording
2938
+ const workflowFunctionTracker = new FunctionTracker();
2939
+
2940
+ // Expose ChromePilotTracker API for manual instrumentation
2941
+ window.ChromePilotTracker = {
2942
+ isRecording: false,
2943
+ _functionTracker: workflowFunctionTracker,
2944
+
2945
+ // Register a single function for tracing
2946
+ register: function(name, fn) {
2947
+ if (!this.isRecording || typeof fn !== 'function') {
2948
+ return fn; // Return original function if not recording
2949
+ }
2950
+
2951
+ if (!this._functionTracker) {
2952
+ console.warn('[ChromePilotTracker] Function tracker not available');
2953
+ return fn;
2954
+ }
2955
+
2956
+ // Use the existing wrapFunction from function-tracker
2957
+ return this._functionTracker.wrapFunction(fn, name, {
2958
+ isUserDefined: true,
2959
+ registeredManually: true
2960
+ });
2961
+ },
2962
+
2963
+ // Wrap an entire object/module
2964
+ wrap: function(obj, options = {}) {
2965
+ if (!this.isRecording || !obj || typeof obj !== 'object') {
2966
+ return obj; // Return original if not recording or invalid
2967
+ }
2968
+
2969
+ if (!this._functionTracker) {
2970
+ console.warn('[ChromePilotTracker] Function tracker not available');
2971
+ return obj;
2972
+ }
2973
+
2974
+ const self = this;
2975
+ const prefix = options.prefix || obj.constructor?.name || 'Object';
2976
+
2977
+ // Use Proxy to intercept method calls
2978
+ return new Proxy(obj, {
2979
+ get(target, prop, receiver) {
2980
+ const original = Reflect.get(target, prop, receiver);
2981
+
2982
+ // Only wrap functions
2983
+ if (typeof original === 'function') {
2984
+ const functionName = `${prefix}.${String(prop)}`;
2985
+
2986
+ // Check if this method should be ignored
2987
+ if (options.ignore && options.ignore.includes(String(prop))) {
2988
+ return original;
2989
+ }
2990
+
2991
+ // Wrap the function
2992
+ return self._functionTracker.wrapFunction(original, functionName, {
2993
+ isUserDefined: true,
2994
+ registeredManually: true,
2995
+ parentObject: prefix
2996
+ });
2997
+ }
2998
+
2999
+ return original;
3000
+ }
3001
+ });
3002
+ },
3003
+
3004
+ // Update recording status
3005
+ _setRecordingStatus: function(status) {
3006
+ this.isRecording = status;
3007
+ }
3008
+ };
3009
+
3010
+ console.log('[ChromeDebug MCP] ChromePilotTracker initialized for workflow recording');
3011
+ }
3012
+
3013
+ // Initialize minimal workflow recording support
3014
+ console.log('[ChromeDebug MCP] Basic workflow recording ready (lightweight mode)');
3015
+ }
3016
+ }, 500);
3017
+
3018
+ // Full data recording system will be initialized on-demand when recording begins
3019
+ // This prevents unnecessary resource usage on every page load
3020
+
3021
+ } // End of initializeChromePilot function
3022
+ })(); // End of IIFE wrapper