@dynamicu/chromedebug-mcp 2.6.4 → 2.6.7

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.
@@ -0,0 +1,256 @@
1
+ /**
2
+ * BrowserRecordingManager - Manage recordings stored in IndexedDB
3
+ *
4
+ * Handles recording lifecycle for browser-only mode:
5
+ * - Stores frames and metadata in IndexedDB
6
+ * - Exports recordings as ZIP files
7
+ * - Manages cleanup and deletion
8
+ */
9
+
10
+ class BrowserRecordingManager {
11
+ constructor(db) {
12
+ this.db = db; // DataBuffer instance
13
+ this.activeRecordings = new Map(); // sessionId -> recording metadata
14
+ }
15
+
16
+ /**
17
+ * Start a new browser recording
18
+ * @param {string} sessionId - Unique session identifier
19
+ * @param {Object} metadata - Recording metadata (tabId, url, timestamp, etc.)
20
+ * @returns {Promise<{success: boolean, sessionId: string}>}
21
+ */
22
+ async startRecording(sessionId, metadata = {}) {
23
+ try {
24
+ // Check if recording already exists (cleanup from previous incomplete session)
25
+ const existingRecording = await this.db.getBrowserRecording(sessionId);
26
+ if (existingRecording) {
27
+ console.warn(`[BrowserRecordingManager] Cleaning up existing recording: ${sessionId}`);
28
+ await this.deleteRecording(sessionId);
29
+ }
30
+
31
+ const recording = {
32
+ sessionId,
33
+ startTime: Date.now(),
34
+ tabId: metadata.tabId,
35
+ url: metadata.url,
36
+ title: metadata.title || 'Browser Recording',
37
+ frameCount: 0,
38
+ status: 'recording',
39
+ metadata
40
+ };
41
+
42
+ // Store recording metadata in IndexedDB
43
+ await this.db.addBrowserRecording(recording);
44
+
45
+ // Track active recording
46
+ this.activeRecordings.set(sessionId, recording);
47
+
48
+ return {
49
+ success: true,
50
+ sessionId,
51
+ startTime: recording.startTime
52
+ };
53
+ } catch (error) {
54
+ console.error('Failed to start browser recording:', {
55
+ name: error.name,
56
+ message: error.message,
57
+ stack: error.stack,
58
+ error: error
59
+ });
60
+ return {
61
+ success: false,
62
+ sessionId,
63
+ error: `${error.name}: ${error.message}`
64
+ };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Add a frame to an active recording
70
+ * @param {string} sessionId
71
+ * @param {Object} frameData - {frameIndex, screenshot, logs, timestamp}
72
+ * @returns {Promise<boolean>}
73
+ */
74
+ async addFrame(sessionId, frameData) {
75
+ try {
76
+ const frame = {
77
+ sessionId,
78
+ frameIndex: frameData.frameIndex,
79
+ screenshot: frameData.screenshot, // Base64 data URL
80
+ logs: frameData.logs || [],
81
+ timestamp: frameData.timestamp || Date.now(),
82
+ absoluteTimestamp: frameData.absoluteTimestamp // Preserve absolute timestamp for log association
83
+ };
84
+
85
+ await this.db.addBrowserFrame(frame);
86
+
87
+ // Update frame count
88
+ const recording = this.activeRecordings.get(sessionId);
89
+ if (recording) {
90
+ recording.frameCount++;
91
+ }
92
+
93
+ return true;
94
+ } catch (error) {
95
+ console.error('Failed to add frame:', error);
96
+ return false;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Stop recording and finalize
102
+ * @param {string} sessionId
103
+ * @returns {Promise<{success: boolean, frameCount: number, duration: number}>}
104
+ */
105
+ async stopRecording(sessionId) {
106
+ try {
107
+ const recording = this.activeRecordings.get(sessionId);
108
+ if (!recording) {
109
+ throw new Error(`Recording ${sessionId} not found`);
110
+ }
111
+
112
+ const endTime = Date.now();
113
+ const duration = endTime - recording.startTime;
114
+
115
+ // Update recording status
116
+ recording.status = 'completed';
117
+ recording.endTime = endTime;
118
+ recording.duration = duration;
119
+
120
+ await this.db.updateBrowserRecording(sessionId, {
121
+ status: 'completed',
122
+ endTime,
123
+ duration,
124
+ frameCount: recording.frameCount
125
+ });
126
+
127
+ this.activeRecordings.delete(sessionId);
128
+
129
+ return {
130
+ success: true,
131
+ sessionId,
132
+ frameCount: recording.frameCount,
133
+ duration
134
+ };
135
+ } catch (error) {
136
+ console.error('Failed to stop recording:', error);
137
+ return {
138
+ success: false,
139
+ sessionId,
140
+ error: error.message
141
+ };
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Export recording as downloadable ZIP blob
147
+ * @param {string} sessionId
148
+ * @returns {Promise<{blob: Blob, filename: string}>}
149
+ */
150
+ async exportRecording(sessionId) {
151
+ try {
152
+ // Get recording metadata
153
+ const recording = await this.db.getBrowserRecording(sessionId);
154
+ if (!recording) {
155
+ throw new Error(`Recording ${sessionId} not found`);
156
+ }
157
+
158
+ // Get all frames for this session
159
+ const frames = await this.db.getBrowserFrames(sessionId);
160
+
161
+ // Create JSON manifest
162
+ const manifest = {
163
+ sessionId,
164
+ title: recording.title,
165
+ url: recording.url,
166
+ startTime: recording.startTime,
167
+ endTime: recording.endTime,
168
+ duration: recording.duration,
169
+ frameCount: frames.length,
170
+ exportedAt: Date.now()
171
+ };
172
+
173
+ // Build export data structure
174
+ const exportData = {
175
+ manifest,
176
+ recording,
177
+ frames: frames.map(frame => ({
178
+ frameIndex: frame.frameIndex,
179
+ timestamp: frame.timestamp,
180
+ screenshot: frame.screenshot,
181
+ logs: frame.logs
182
+ }))
183
+ };
184
+
185
+ // Convert to JSON string
186
+ const jsonString = JSON.stringify(exportData, null, 2);
187
+
188
+ // Create blob
189
+ const blob = new Blob([jsonString], { type: 'application/json' });
190
+
191
+ // Generate filename
192
+ const date = new Date(recording.startTime).toISOString().split('T')[0];
193
+ const filename = `chrome-pilot-recording-${date}-${sessionId.slice(-8)}.json`;
194
+
195
+ return {
196
+ blob,
197
+ filename,
198
+ size: blob.size,
199
+ frameCount: frames.length
200
+ };
201
+ } catch (error) {
202
+ console.error('Failed to export recording:', error);
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Delete a recording and all its frames
209
+ * @param {string} sessionId
210
+ * @returns {Promise<{success: boolean, deletedFrames: number}>}
211
+ */
212
+ async deleteRecording(sessionId) {
213
+ try {
214
+ // Delete all frames
215
+ const deletedFrames = await this.db.deleteBrowserFrames(sessionId);
216
+
217
+ // Delete recording metadata
218
+ await this.db.deleteBrowserRecording(sessionId);
219
+
220
+ // Remove from active recordings if present
221
+ this.activeRecordings.delete(sessionId);
222
+
223
+ return {
224
+ success: true,
225
+ sessionId,
226
+ deletedFrames
227
+ };
228
+ } catch (error) {
229
+ console.error('Failed to delete recording:', error);
230
+ return {
231
+ success: false,
232
+ sessionId,
233
+ error: error.message
234
+ };
235
+ }
236
+ }
237
+
238
+ /**
239
+ * List all recordings
240
+ * @returns {Promise<Array>}
241
+ */
242
+ async listRecordings() {
243
+ try {
244
+ return await this.db.getAllBrowserRecordings();
245
+ } catch (error) {
246
+ console.error('Failed to list recordings:', error);
247
+ return [];
248
+ }
249
+ }
250
+
251
+ }
252
+
253
+ // Export for use in other extension files
254
+ if (typeof module !== 'undefined' && module.exports) {
255
+ module.exports = BrowserRecordingManager;
256
+ }
@@ -398,25 +398,25 @@ function getComponentInfo(element) {
398
398
  // Idempotent protection for cleanup function
399
399
  let cleanupExecuted = false;
400
400
 
401
- function cleanup() {
401
+ async function cleanup() {
402
402
  // Idempotent protection - only run cleanup once
403
403
  if (cleanupExecuted) {
404
404
  console.log('[ChromeDebug MCP] Cleanup already executed, skipping');
405
405
  return;
406
406
  }
407
-
407
+
408
408
  cleanupExecuted = true;
409
409
  console.log('[ChromeDebug MCP] Executing cleanup...');
410
-
410
+
411
411
  // Clean up recording indicators and event listeners
412
412
  try {
413
413
  // Legacy recording indicator cleanup removed - now handled by ScreenCaptureVisualFeedback
414
-
414
+
415
415
  // Stop workflow recording if active
416
416
  if (typeof isWorkflowRecording !== 'undefined' && isWorkflowRecording) {
417
417
  console.log('[ChromeDebug MCP] Stopping workflow recording during cleanup');
418
418
  if (typeof stopWorkflowRecording === 'function') {
419
- stopWorkflowRecording();
419
+ await stopWorkflowRecording();
420
420
  }
421
421
  }
422
422
 
@@ -498,8 +498,13 @@ if (isExtensionValid()) {
498
498
  sendResponse({ success: true });
499
499
  } else if (request.action === 'stopWorkflowRecording') {
500
500
  console.log('[ChromeDebug MCP] Stopping workflow recording');
501
- const workflow = stopWorkflowRecording();
502
- sendResponse({ success: true, workflow: workflow });
501
+ stopWorkflowRecording().then(workflow => {
502
+ sendResponse({ success: true, workflow: workflow });
503
+ }).catch(error => {
504
+ console.error('[ChromeDebug MCP] Error stopping workflow recording:', error);
505
+ sendResponse({ success: false, error: error.message });
506
+ });
507
+ return true; // Keep channel open for async response
503
508
  } else if (request.action === 'createRestorePoint') {
504
509
  // Create restore point for current workflow
505
510
  createRestorePoint(request.actionIndex || workflowActions.length).then(restorePointId => {
@@ -852,27 +857,72 @@ let isWorkflowRecording = false;
852
857
  let workflowActions = [];
853
858
  let workflowRecordingIndicator = null;
854
859
  let workflowScreenshotSettings = null;
860
+ let lastScreenshotTime = 0;
861
+ let pendingScreenshots = []; // Track pending screenshot captures
862
+
863
+ // Async screenshot capture without blocking click recording
864
+ function captureScreenshotAsync(action, actionIndex) {
865
+ // Create the async capture promise
866
+ const capturePromise = (async () => {
867
+ try {
868
+ // Chrome API rate limit: ~1-2 screenshots per second
869
+ // We enforce 600ms minimum between screenshots to stay under the limit
870
+ const MIN_SCREENSHOT_INTERVAL = 600; // milliseconds
871
+ const now = Date.now();
872
+ const timeSinceLastScreenshot = now - lastScreenshotTime;
873
+
874
+ if (timeSinceLastScreenshot < MIN_SCREENSHOT_INTERVAL) {
875
+ // Wait until we can safely capture
876
+ const waitTime = MIN_SCREENSHOT_INTERVAL - timeSinceLastScreenshot;
877
+ await new Promise(resolve => setTimeout(resolve, waitTime));
878
+ }
879
+
880
+ const screenshotData = await captureWorkflowScreenshot();
881
+
882
+ if (screenshotData) {
883
+ // Add screenshot to the action object (it's already in the array)
884
+ action.screenshot_data = screenshotData;
885
+ lastScreenshotTime = Date.now(); // Update timestamp after successful capture
886
+ }
887
+ } catch (error) {
888
+ console.error(`Error capturing screenshot for action ${actionIndex}:`, error);
889
+ }
890
+ })();
891
+
892
+ // Track this promise in pendingScreenshots
893
+ pendingScreenshots.push(capturePromise);
894
+
895
+ // Remove from pending when done (success or failure)
896
+ capturePromise.finally(() => {
897
+ const index = pendingScreenshots.indexOf(capturePromise);
898
+ if (index > -1) {
899
+ pendingScreenshots.splice(index, 1);
900
+ }
901
+ });
902
+
903
+ return capturePromise;
904
+ }
855
905
 
856
906
  // Helper function to capture screenshot
857
907
  async function captureWorkflowScreenshot() {
858
908
  if (!workflowScreenshotSettings || !workflowScreenshotSettings.enabled) {
859
909
  return null;
860
910
  }
861
-
911
+
862
912
  try {
863
913
  // Send message to background script to capture screenshot
864
914
  const response = await chrome.runtime.sendMessage({
865
915
  action: 'captureWorkflowScreenshot',
866
916
  settings: workflowScreenshotSettings
867
917
  });
868
-
918
+
869
919
  if (response && response.success) {
870
920
  return response.screenshotData;
871
921
  }
872
922
  } catch (error) {
873
- console.error('[ChromeDebug MCP] Error capturing screenshot:', error);
923
+ console.error('Error in captureWorkflowScreenshot:', error);
874
924
  }
875
-
925
+
876
926
  return null;
877
927
  }
878
928
 
@@ -1024,16 +1074,16 @@ async function shouldEnhanceCapture() {
1024
1074
  }
1025
1075
 
1026
1076
  // Record click action
1027
- async function recordClick(event) {
1077
+ function recordClick(event) {
1028
1078
  if (!isWorkflowRecording) {
1029
1079
  console.log('[ChromePilot] Click ignored - not recording');
1030
1080
  return;
1031
1081
  }
1032
-
1082
+
1033
1083
  console.log('[ChromePilot] Recording click event');
1034
-
1084
+
1035
1085
  let element = event.target;
1036
-
1086
+
1037
1087
  // If clicking on an SVG element or its children, find the parent button/link
1038
1088
  if (element.tagName === 'svg' || element.tagName === 'path' || element.tagName === 'g' || element.tagName === 'circle' || element.tagName === 'rect') {
1039
1089
  const clickableParent = element.closest('button, a, [role="button"], [onclick]');
@@ -1042,7 +1092,7 @@ async function recordClick(event) {
1042
1092
  element = clickableParent;
1043
1093
  }
1044
1094
  }
1045
-
1095
+
1046
1096
  const action = {
1047
1097
  type: 'click',
1048
1098
  selector: getUniqueSelector(element),
@@ -1057,92 +1107,92 @@ async function recordClick(event) {
1057
1107
  action.text = element.textContent.trim();
1058
1108
  }
1059
1109
 
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...');
1110
+ // Record the action immediately (synchronously) - don't wait for anything
1111
+ workflowActions.push(action);
1112
+ const actionIndex = workflowActions.length - 1;
1065
1113
 
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
- }
1114
+ // Enhanced click tracking data (async, non-blocking)
1115
+ // Check setting and apply enhanced data asynchronously without blocking
1116
+ shouldEnhanceCapture().then(enhanceCapture => {
1117
+ if (enhanceCapture) {
1118
+ try {
1119
+ console.log('[ChromePilot] Capturing enhanced click data asynchronously...');
1071
1120
 
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 };
1121
+ // Capture enhanced element data (only include fields with meaningful values)
1122
+ const capturedHTML = captureElementHTML(element);
1123
+ if (capturedHTML) {
1124
+ action.element_html = capturedHTML;
1080
1125
  }
1081
- }
1082
1126
 
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 };
1127
+ const componentData = getEnhancedComponentInfo(element);
1128
+ if (componentData) {
1129
+ try {
1130
+ JSON.stringify(componentData); // Test if serializable
1131
+ action.component_data = componentData;
1132
+ } catch (e) {
1133
+ console.error('[ChromePilot] Component data cannot be serialized:', e.message);
1134
+ action.component_data = { error: 'Serialization failed', type: typeof componentData };
1135
+ }
1091
1136
  }
1092
- }
1093
1137
 
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 };
1138
+ const eventHandlers = extractEventHandlers(element);
1139
+ if (eventHandlers) {
1140
+ try {
1141
+ JSON.stringify(eventHandlers); // Test if serializable
1142
+ action.event_handlers = eventHandlers;
1143
+ } catch (e) {
1144
+ console.error('[ChromePilot] Event handlers cannot be serialized:', e.message);
1145
+ action.event_handlers = { error: 'Serialization failed', type: typeof eventHandlers };
1146
+ }
1102
1147
  }
1103
- }
1104
1148
 
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 };
1149
+ const elementState = captureElementState(element);
1150
+ if (elementState) {
1151
+ try {
1152
+ JSON.stringify(elementState); // Test if serializable
1153
+ action.element_state = elementState;
1154
+ } catch (e) {
1155
+ console.error('[ChromePilot] Element state cannot be serialized:', e.message);
1156
+ action.element_state = { error: 'Serialization failed', type: typeof elementState };
1157
+ }
1113
1158
  }
1114
- }
1115
1159
 
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
1160
+ const perfMetrics = getPerformanceMetrics();
1161
+ if (perfMetrics) {
1162
+ try {
1163
+ JSON.stringify(perfMetrics); // Test if serializable
1164
+ action.performance_metrics = perfMetrics;
1165
+ } catch (e) {
1166
+ console.error('[ChromePilot] Performance metrics cannot be serialized:', e.message);
1167
+ action.performance_metrics = { error: 'Serialization failed', type: typeof perfMetrics };
1168
+ }
1169
+ }
1170
+
1171
+ console.log('[ChromePilot] Enhanced click data captured successfully');
1172
+ } catch (error) {
1173
+ console.warn('[ChromePilot] Error capturing enhanced click data:', error);
1174
+ // Continue with basic click recording if enhanced capture fails
1175
+ }
1176
+ } else {
1177
+ console.log('[ChromePilot] Enhanced click capture disabled - using basic capture only');
1120
1178
  }
1121
- } else {
1122
- console.log('[ChromePilot] Enhanced click capture disabled - using basic capture only');
1123
- }
1124
-
1125
- // Capture screenshot if enabled
1179
+ }).catch(error => {
1180
+ console.error('[ChromePilot] Error checking enhanced capture setting:', error);
1181
+ });
1182
+
1183
+ // Capture screenshot asynchronously without blocking
1126
1184
  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);
1185
+ // Don't await - let screenshot capture happen in background
1186
+ captureScreenshotAsync(action, actionIndex);
1134
1187
  }
1135
-
1136
- workflowActions.push(action);
1137
- console.log('[ChromePilot] Action recorded, total actions:', workflowActions.length);
1138
1188
  updateWorkflowActionCount();
1139
-
1189
+
1140
1190
  // Visual feedback
1141
1191
  flashElement(element);
1142
1192
  }
1143
1193
 
1144
1194
  // Record input changes
1145
- async function recordInput(event) {
1195
+ function recordInput(event) {
1146
1196
  if (!isWorkflowRecording) return;
1147
1197
 
1148
1198
  const element = event.target;
@@ -1163,20 +1213,13 @@ async function recordInput(event) {
1163
1213
  if (element.placeholder) {
1164
1214
  action.placeholder = element.placeholder;
1165
1215
  }
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
-
1216
+
1217
+ // NOTE: Screenshots are only captured for clicks, not input events
1218
+ // This prevents multiple screenshot attempts for a single user action
1219
+
1177
1220
  workflowActions.push(action);
1178
1221
  updateWorkflowActionCount();
1179
-
1222
+
1180
1223
  // Visual feedback
1181
1224
  flashElement(element);
1182
1225
  }
@@ -1256,6 +1299,8 @@ function startWorkflowRecording(screenshotSettings) {
1256
1299
  isWorkflowRecording = true;
1257
1300
  workflowActions = [];
1258
1301
  workflowScreenshotSettings = screenshotSettings;
1302
+ lastScreenshotTime = 0; // Reset rate limit timer
1303
+ pendingScreenshots = []; // Clear any pending screenshots from previous recording
1259
1304
  createWorkflowRecordingIndicator();
1260
1305
 
1261
1306
  console.log('[ChromePilot] Workflow recording started with settings:', screenshotSettings);
@@ -1326,11 +1371,20 @@ function startWorkflowRecording(screenshotSettings) {
1326
1371
  }
1327
1372
 
1328
1373
  // Stop workflow recording
1329
- function stopWorkflowRecording() {
1374
+ async function stopWorkflowRecording() {
1330
1375
  isWorkflowRecording = false;
1331
1376
  removeWorkflowRecordingIndicator();
1332
-
1377
+
1333
1378
  console.log(`[ChromePilot] Stopping recording. Captured ${workflowActions.length} actions`);
1379
+
1380
+ // Wait for all pending screenshots to complete
1381
+ if (pendingScreenshots.length > 0) {
1382
+ console.log(`[SCREENSHOT-DEBUG] Waiting for ${pendingScreenshots.length} pending screenshots to complete...`);
1383
+ await Promise.all(pendingScreenshots);
1384
+ console.log('[SCREENSHOT-DEBUG] All pending screenshots completed!');
1385
+ } else {
1386
+ console.log('[SCREENSHOT-DEBUG] No pending screenshots to wait for');
1387
+ }
1334
1388
 
1335
1389
  // Disconnect from FunctionTracker and disable recording
1336
1390
  if (window.ChromePilotTracker && window.ChromePilotTracker._functionTracker) {
@@ -1387,6 +1441,22 @@ function stopWorkflowRecording() {
1387
1441
  functionTraces = [...functionTraces, ...embeddedTraces];
1388
1442
  }
1389
1443
 
1444
+ // Log screenshot data before returning
1445
+ console.log('[SCREENSHOT-DEBUG] stopWorkflowRecording - Preparing to return workflow');
1446
+ console.log('[SCREENSHOT-DEBUG] stopWorkflowRecording - Total actions:', workflowActions.length);
1447
+
1448
+ const actionsWithScreenshots = workflowActions.filter(a => a.screenshot_data);
1449
+ console.log('[SCREENSHOT-DEBUG] stopWorkflowRecording - Actions WITH screenshot_data:', actionsWithScreenshots.length);
1450
+
1451
+ workflowActions.forEach((action, index) => {
1452
+ console.log(`[SCREENSHOT-DEBUG] stopWorkflowRecording - Action ${index}:`, {
1453
+ type: action.type,
1454
+ hasScreenshotData: !!action.screenshot_data,
1455
+ screenshotDataLength: action.screenshot_data?.length,
1456
+ screenshotPreview: action.screenshot_data?.substring(0, 50)
1457
+ });
1458
+ });
1459
+
1390
1460
  // Return the recorded workflow with proper functionTraces field
1391
1461
  return {
1392
1462
  actions: workflowActions,