@dynamicu/chromedebug-mcp 2.7.0 → 2.7.2

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.
@@ -180,7 +180,7 @@ class FrameEditor {
180
180
  try {
181
181
  // If in browser-only mode, load from IndexedDB directly
182
182
  if (this.browserOnly) {
183
- console.log(`[INFO] Frame Editor: Loading browser-only recording from IndexedDB...`);
183
+ // console.log(`[INFO] Frame Editor: Loading browser-only recording from IndexedDB...`);
184
184
  const result = await chrome.runtime.sendMessage({
185
185
  action: 'getBrowserRecordingFrames',
186
186
  sessionId: this.sessionId
@@ -188,7 +188,7 @@ class FrameEditor {
188
188
 
189
189
  if (result && result.success && result.frames) {
190
190
  this.frames = result.frames;
191
- console.log(`[INFO] Frame Editor: Loaded ${this.frames.length} frames from IndexedDB`);
191
+ // console.log(`[INFO] Frame Editor: Loaded ${this.frames.length} frames from IndexedDB`);
192
192
  } else {
193
193
  throw new Error('Failed to load browser-only recording from IndexedDB');
194
194
  }
@@ -207,7 +207,7 @@ class FrameEditor {
207
207
  const ports = CHROMEDEBUG_CONFIG?.ports?.slice(0, 5) || [3001, 3000, 3028]; // Use configured ports
208
208
  let found = false;
209
209
 
210
- console.log(`[INFO] Frame Editor: Fetching session ${this.sessionId} from HTTP server...`);
210
+ // console.log(`[INFO] Frame Editor: Fetching session ${this.sessionId} from HTTP server...`);
211
211
 
212
212
  for (const port of ports) {
213
213
  try {
@@ -216,25 +216,25 @@ class FrameEditor {
216
216
  const data = await response.json();
217
217
  this.frames = data.frames || [];
218
218
  found = true;
219
- console.log(`[INFO] Frame Editor: Loaded ${this.frames.length} frames from HTTP server on port ${port}`);
219
+ // console.log(`[INFO] Frame Editor: Loaded ${this.frames.length} frames from HTTP server on port ${port}`);
220
220
  break;
221
221
  }
222
222
  } catch (e) {
223
223
  // Try next port
224
- console.log(`[DEBUG] Frame Editor: Failed to fetch from port ${port}:`, e.message);
224
+ // console.log(`[DEBUG] Frame Editor: Failed to fetch from port ${port}:`, e.message);
225
225
  }
226
226
  }
227
227
 
228
228
  // Fall back to Chrome storage if HTTP server unavailable
229
229
  if (!found) {
230
- console.log(`[INFO] Frame Editor: HTTP server unavailable, falling back to Chrome storage...`);
230
+ // console.log(`[INFO] Frame Editor: HTTP server unavailable, falling back to Chrome storage...`);
231
231
  const storageKey = this.sessionId;
232
232
  const result = await chrome.storage.local.get([storageKey]);
233
233
  const sessionData = result[storageKey];
234
234
 
235
235
  if (sessionData) {
236
236
  this.frames = sessionData.frames || [];
237
- console.log(`[INFO] Frame Editor: Loaded ${this.frames.length} frames from Chrome storage (logs may not be available)`);
237
+ // console.log(`[INFO] Frame Editor: Loaded ${this.frames.length} frames from Chrome storage (logs may not be available)`);
238
238
  } else {
239
239
  throw new Error('Session not found in HTTP server or Chrome storage');
240
240
  }
@@ -258,7 +258,7 @@ class FrameEditor {
258
258
  const ports = CHROMEDEBUG_CONFIG?.ports?.slice(0, 5) || [3001, 3000, 3028];
259
259
  let found = false;
260
260
 
261
- console.log(`[INFO] Frame Editor: Fetching workflow ${this.sessionId} from HTTP server...`);
261
+ // console.log(`[INFO] Frame Editor: Fetching workflow ${this.sessionId} from HTTP server...`);
262
262
 
263
263
  for (const port of ports) {
264
264
  try {
@@ -266,11 +266,11 @@ class FrameEditor {
266
266
  if (response.ok) {
267
267
  this.workflowData = await response.json();
268
268
  found = true;
269
- console.log(`[INFO] Frame Editor: Loaded workflow with ${this.workflowData.actions?.length || 0} actions from port ${port}`);
269
+ // console.log(`[INFO] Frame Editor: Loaded workflow with ${this.workflowData.actions?.length || 0} actions from port ${port}`);
270
270
  break;
271
271
  }
272
272
  } catch (e) {
273
- console.log(`[DEBUG] Frame Editor: Failed to fetch workflow from port ${port}:`, e.message);
273
+ // console.log(`[DEBUG] Frame Editor: Failed to fetch workflow from port ${port}:`, e.message);
274
274
  }
275
275
  }
276
276
 
@@ -374,7 +374,7 @@ class FrameEditor {
374
374
  timestamp: frame.timestamp,
375
375
  allKeys: Object.keys(frame)
376
376
  };
377
- console.log('[DEBUG] createFrameElement - First frame:', JSON.stringify(debugInfo, null, 2));
377
+ // console.log('[DEBUG] createFrameElement - First frame:', JSON.stringify(debugInfo, null, 2));
378
378
  }
379
379
 
380
380
  const div = document.createElement('div');
@@ -423,7 +423,7 @@ class FrameEditor {
423
423
  // Debug log count
424
424
  const logCount = frame.logs ? frame.logs.length : 0;
425
425
  if (index === 0) {
426
- console.log(`[DEBUG] Frame ${index} log count: ${logCount}`);
426
+ // console.log(`[DEBUG] Frame ${index} log count: ${logCount}`);
427
427
  }
428
428
 
429
429
  logSummary.innerHTML = `
@@ -647,10 +647,10 @@ class FrameEditor {
647
647
  }
648
648
 
649
649
  async loadFrameImage(index) {
650
- console.log(`[DEBUG] loadFrameImage called for ${this.isWorkflow ? 'action' : 'frame'} ${index}`);
650
+ // console.log(`[DEBUG] loadFrameImage called for ${this.isWorkflow ? 'action' : 'frame'} ${index}`);
651
651
 
652
652
  if (this.loadedImages.has(index)) {
653
- console.log(`[DEBUG] ${this.isWorkflow ? 'Action' : 'Frame'} ${index} already loaded, skipping`);
653
+ // console.log(`[DEBUG] ${this.isWorkflow ? 'Action' : 'Frame'} ${index} already loaded, skipping`);
654
654
  return;
655
655
  }
656
656
 
@@ -658,7 +658,7 @@ class FrameEditor {
658
658
  if (this.isWorkflow) {
659
659
  const action = this.workflowData?.actions?.[index];
660
660
  if (!action || !action.screenshot) {
661
- console.log(`[DEBUG] Action ${index} has no screenshot`);
661
+ // console.log(`[DEBUG] Action ${index} has no screenshot`);
662
662
  return;
663
663
  }
664
664
 
@@ -672,8 +672,8 @@ class FrameEditor {
672
672
  thumbnail.appendChild(img);
673
673
  this.loadedImages.set(index, true);
674
674
 
675
- // Enable click to enlarge
676
- thumbnail.addEventListener('click', () => this.showEnlargedImage(action.screenshot, `Action ${index}`));
675
+ // Add click handler to open modal (same as screen recordings)
676
+ thumbnail.addEventListener('click', () => this.openFrameModal(index));
677
677
  };
678
678
  return;
679
679
  }
@@ -697,7 +697,7 @@ class FrameEditor {
697
697
  return;
698
698
  }
699
699
 
700
- console.log(`[DEBUG] Frame ${index} loading image, imageData prefix:`, frame.imageData.substring(0, 50));
700
+ // console.log(`[DEBUG] Frame ${index} loading image, imageData prefix:`, frame.imageData.substring(0, 50));
701
701
 
702
702
  const thumbnail = document.querySelector(`.frame-thumbnail[data-index="${index}"]`);
703
703
  if (!thumbnail) {
@@ -713,7 +713,7 @@ class FrameEditor {
713
713
  };
714
714
 
715
715
  img.onload = () => {
716
- console.log(`[DEBUG] Image loaded successfully for frame ${index}`);
716
+ // console.log(`[DEBUG] Image loaded successfully for frame ${index}`);
717
717
  thumbnail.innerHTML = '';
718
718
  thumbnail.appendChild(img);
719
719
  this.loadedImages.set(index, true);
@@ -823,18 +823,28 @@ class FrameEditor {
823
823
  }
824
824
 
825
825
  openFrameModal(index) {
826
- const frame = this.frames[index];
827
- if (!frame || !frame.imageData) return;
828
-
829
826
  const modal = document.getElementById('frameModal');
830
827
  const modalTitle = document.getElementById('modalTitle');
831
828
  const modalImage = document.getElementById('modalImage');
832
-
833
- modalTitle.textContent = `Frame ${index} - ${(frame.timestamp / 1000).toFixed(1)}s`;
834
- modalImage.src = frame.imageData;
835
-
829
+
830
+ if (this.isWorkflow) {
831
+ // Handle workflow action
832
+ const action = this.workflowData?.actions?.[index];
833
+ if (!action || !action.screenshot) return;
834
+
835
+ modalTitle.textContent = `Action ${index} - ${action.type || 'Unknown'}`;
836
+ modalImage.src = action.screenshot;
837
+ } else {
838
+ // Handle regular frame
839
+ const frame = this.frames[index];
840
+ if (!frame || !frame.imageData) return;
841
+
842
+ modalTitle.textContent = `Frame ${index} - ${(frame.timestamp / 1000).toFixed(1)}s`;
843
+ modalImage.src = frame.imageData;
844
+ }
845
+
836
846
  modal.classList.add('visible');
837
-
847
+
838
848
  // Add keyboard listener for ESC key
839
849
  document.addEventListener('keydown', this.handleModalKeydown);
840
850
  }
@@ -853,7 +863,68 @@ class FrameEditor {
853
863
  }
854
864
  }
855
865
 
866
+ // Copy text to clipboard with toast notification
867
+ async copyToClipboard(text, successMessage) {
868
+ try {
869
+ await navigator.clipboard.writeText(text);
870
+ this.showToast(successMessage);
871
+ } catch (err) {
872
+ // Fallback for older browsers or permission issues
873
+ this.showToast('Failed to copy - check permissions');
874
+ }
875
+ }
876
+
877
+ // Show toast notification
878
+ showToast(message) {
879
+ // Remove existing toast if any
880
+ const existing = document.querySelector('.toast');
881
+ if (existing) existing.remove();
882
+
883
+ const toast = document.createElement('div');
884
+ toast.className = 'toast';
885
+ toast.textContent = message;
886
+ document.body.appendChild(toast);
887
+
888
+ // Remove after animation completes
889
+ setTimeout(() => toast.remove(), 2000);
890
+ }
891
+
892
+ // Generate AI prompt based on recording type
893
+ generateAIPrompt() {
894
+ if (this.isWorkflow) {
895
+ const actionCount = this.workflowData?.actions?.length || 0;
896
+ const logCount = this.workflowData?.logs?.length || 0;
897
+ return `Analyze workflow recording: ${this.sessionId}
898
+ - Actions: ${actionCount}, Console Logs: ${logCount}
899
+ - Use MCP tool: get_workflow_recording or get_workflow_summary`;
900
+ } else {
901
+ const frameCount = this.frames.length;
902
+ return `Analyze screen recording: ${this.sessionId}
903
+ - Frames: ${frameCount}
904
+ - Use MCP tool: get_frame_session_info`;
905
+ }
906
+ }
907
+
856
908
  setupEventListeners() {
909
+ // Copy ID button
910
+ document.getElementById('copyIdBtn').addEventListener('click', () => {
911
+ if (this.sessionId) {
912
+ this.copyToClipboard(this.sessionId, 'Session ID copied!');
913
+ } else {
914
+ this.showToast('No session ID available');
915
+ }
916
+ });
917
+
918
+ // Copy Prompt button
919
+ document.getElementById('copyPromptBtn').addEventListener('click', () => {
920
+ if (this.sessionId) {
921
+ const prompt = this.generateAIPrompt();
922
+ this.copyToClipboard(prompt, 'Prompt copied!');
923
+ } else {
924
+ this.showToast('No session available');
925
+ }
926
+ });
927
+
857
928
  document.getElementById('selectAllBtn').addEventListener('click', () => {
858
929
  const itemCount = this.getItemCount();
859
930
  for (let i = 0; i < itemCount; i++) {
@@ -1039,7 +1110,7 @@ class FrameEditor {
1039
1110
  });
1040
1111
 
1041
1112
  if (response.ok) {
1042
- console.log(`Server notified on port ${port}`);
1113
+ // console.log(`Server notified on port ${port}`);
1043
1114
  break;
1044
1115
  }
1045
1116
  } catch (e) {
@@ -1156,7 +1227,7 @@ class FrameEditor {
1156
1227
  saveBtn.textContent = originalText;
1157
1228
  saveBtn.disabled = false;
1158
1229
 
1159
- console.log(`Workflow downloaded successfully: ${actionsToExport.length} actions, ${screenshotCount} screenshots, ${filteredLogs.length} logs`);
1230
+ // console.log(`Workflow downloaded successfully: ${actionsToExport.length} actions, ${screenshotCount} screenshots, ${filteredLogs.length} logs`);
1160
1231
 
1161
1232
  } catch (error) {
1162
1233
  console.error('Error creating ZIP:', error);
@@ -1365,7 +1436,7 @@ https://github.com/anthropics/chrome-debug
1365
1436
  saveBtn.textContent = originalText;
1366
1437
  saveBtn.disabled = false;
1367
1438
 
1368
- console.log(`Frames downloaded successfully: ${framesToExport.length} frames, ${screenshotCount} screenshots`);
1439
+ // console.log(`Frames downloaded successfully: ${framesToExport.length} frames, ${screenshotCount} screenshots`);
1369
1440
 
1370
1441
  } catch (error) {
1371
1442
  console.error('Error creating ZIP:', error);
@@ -105,12 +105,12 @@ class FunctionTracker {
105
105
  this.targetCategories = { ...this.targetCategories, ...options.targetCategories };
106
106
  }
107
107
 
108
- console.log('[FunctionTracker] Initialized with options:', {
109
- recordingId: this.recordingId,
110
- maxCallDepth: this.maxCallDepth,
111
- trackReturnValues: this.trackReturnValues,
112
- targetCategories: this.targetCategories
113
- });
108
+ // console.log('[FunctionTracker] Initialized with options:', {
109
+ // recordingId: this.recordingId,
110
+ // maxCallDepth: this.maxCallDepth,
111
+ // trackReturnValues: this.trackReturnValues,
112
+ // targetCategories: this.targetCategories
113
+ // });
114
114
  }
115
115
 
116
116
  startTracking() {
@@ -150,7 +150,7 @@ class FunctionTracker {
150
150
  // Set up pagehide listener for final flush
151
151
  window.addEventListener('pagehide', this.boundFlush);
152
152
 
153
- console.log('[FunctionTracker] Started function execution tracking');
153
+ // console.log('[FunctionTracker] Started function execution tracking');
154
154
 
155
155
  // Record performance event
156
156
  if (this.performanceMonitor) {
@@ -183,7 +183,7 @@ class FunctionTracker {
183
183
  this.flushBuffer(); // Flush any remaining data
184
184
 
185
185
  this.isTracking = false;
186
- console.log('[FunctionTracker] Stopped function execution tracking');
186
+ // console.log('[FunctionTracker] Stopped function execution tracking');
187
187
 
188
188
  // Record performance event
189
189
  if (this.performanceMonitor) {
@@ -781,7 +781,7 @@ class FunctionTracker {
781
781
  const events = [...this.executionBuffer];
782
782
  this.executionBuffer = [];
783
783
 
784
- console.log(`[FunctionTracker] Flushing ${events.length} execution traces`);
784
+ // console.log(`[FunctionTracker] Flushing ${events.length} execution traces`);
785
785
 
786
786
  // Send data via callback
787
787
  if (this.onDataReady) {
@@ -819,7 +819,7 @@ class FunctionTracker {
819
819
 
820
820
  try {
821
821
  this.onDataReady(eventsToFlush);
822
- console.log(`[FunctionTracker] Flushed ${eventsToFlush.length} execution traces`);
822
+ // console.log(`[FunctionTracker] Flushed ${eventsToFlush.length} execution traces`);
823
823
  } catch (error) {
824
824
  console.error('[FunctionTracker] Error in onDataReady callback for function traces:', error);
825
825
  // Optionally add events back for retry, but be careful of infinite loops
@@ -2,7 +2,7 @@
2
2
  // Handles reliable batch uploads with retry logic and compression
3
3
 
4
4
  // Version will be provided by background.js which imports this file
5
- console.log('[UploadManager] Loaded version: 2.0.4-BUILD-20250119');
5
+ // console.log('[UploadManager] Loaded version: 2.0.4-BUILD-20250119');
6
6
 
7
7
  // Cleanup any existing stale intervals to prevent multiple instances
8
8
  if (typeof window !== 'undefined' && window._chromePilotUploadIntervals) {
@@ -43,7 +43,7 @@ class UploadManager {
43
43
  // Only start periodic upload check if server is available
44
44
  this.startPeriodicUpload();
45
45
 
46
- console.log('[UploadManager] Initialized with server:', this.serverUrl);
46
+ // console.log('[UploadManager] Initialized with server:', this.serverUrl);
47
47
  return true;
48
48
  }
49
49
 
@@ -124,7 +124,7 @@ class UploadManager {
124
124
  if (!this.serverUrl) {
125
125
  this.serverUrl = await this.findAvailableServer();
126
126
  if (!this.serverUrl) {
127
- console.log('[UploadManager] No server available, skipping upload');
127
+ // console.log('[UploadManager] No server available, skipping upload');
128
128
  return;
129
129
  }
130
130
  }
@@ -136,14 +136,14 @@ class UploadManager {
136
136
  return;
137
137
  }
138
138
 
139
- console.log(`[UploadManager] Processing ${batches.length} pending batches`);
139
+ // console.log(`[UploadManager] Processing ${batches.length} pending batches`);
140
140
 
141
141
  for (const batch of batches) {
142
142
  const success = await this.uploadBatch(batch);
143
143
 
144
144
  if (success) {
145
145
  await this.dataBuffer.markBatchUploaded(batch.id);
146
- console.log(`[UploadManager] Batch ${batch.id} uploaded successfully`);
146
+ // console.log(`[UploadManager] Batch ${batch.id} uploaded successfully`);
147
147
  } else {
148
148
  console.warn(`[UploadManager] Failed to upload batch ${batch.id}`);
149
149
  }
@@ -204,7 +204,7 @@ class UploadManager {
204
204
  if (response.status >= 500 && attemptNumber < this.retryAttempts) {
205
205
  // Server error, retry with exponential backoff
206
206
  const delay = Math.min(this.retryDelay * Math.pow(2, attemptNumber - 1), this.maxRetryDelay);
207
- console.log(`[UploadManager] Server error, retrying in ${delay}ms...`);
207
+ // console.log(`[UploadManager] Server error, retrying in ${delay}ms...`);
208
208
  await this.sleep(delay);
209
209
  return await this.uploadBatch(batch, attemptNumber + 1);
210
210
  }
@@ -215,7 +215,7 @@ class UploadManager {
215
215
 
216
216
  if (attemptNumber < this.retryAttempts) {
217
217
  const delay = Math.min(this.retryDelay * Math.pow(2, attemptNumber - 1), this.maxRetryDelay);
218
- console.log(`[UploadManager] Network error, retrying in ${delay}ms...`);
218
+ // console.log(`[UploadManager] Network error, retrying in ${delay}ms...`);
219
219
  await this.sleep(delay);
220
220
  return await this.uploadBatch(batch, attemptNumber + 1);
221
221
  }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamicu/chromedebug-mcp",
3
- "version": "2.7.0",
3
+ "version": "2.7.2",
4
4
  "description": "ChromeDebug MCP - MCP server that provides full control over a Chrome browser instance for debugging and automation with AI assistants like Claude Code",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/database.js CHANGED
@@ -132,9 +132,20 @@ class ChromeDebugDatabase {
132
132
  // Migrate from old location if needed
133
133
  this.migrateIfNeeded();
134
134
 
135
+ // Check if database exists and is corrupted
136
+ if (fs.existsSync(this.dbPath)) {
137
+ const integrityStatus = this.checkDatabaseIntegrity();
138
+ if (!integrityStatus.ok) {
139
+ logger.warn(`[Database] Database corruption detected: ${integrityStatus.error}`);
140
+ this.handleCorruptedDatabase();
141
+ }
142
+ }
143
+
135
144
  // Open database connection
136
145
  this.db = new Database(this.dbPath);
137
146
  this.db.pragma('journal_mode = WAL'); // Enable WAL mode for better concurrent access
147
+ this.db.pragma('synchronous = NORMAL'); // Balance between safety and performance
148
+ this.db.pragma('foreign_keys = ON'); // Enforce foreign key constraints
138
149
 
139
150
  // Create tables
140
151
  this.createTables();
@@ -142,6 +153,64 @@ class ChromeDebugDatabase {
142
153
  logger.debug(`ChromeDebug MCP database initialized at: ${this.dbPath}`);
143
154
  }
144
155
 
156
+ /**
157
+ * Check database integrity before opening
158
+ * @returns {{ ok: boolean, error?: string }}
159
+ */
160
+ checkDatabaseIntegrity() {
161
+ try {
162
+ const tempDb = new Database(this.dbPath, { readonly: true });
163
+ const result = tempDb.pragma('integrity_check');
164
+ tempDb.close();
165
+
166
+ if (result && result[0] && result[0].integrity_check === 'ok') {
167
+ return { ok: true };
168
+ } else {
169
+ const errorMsg = result?.[0]?.integrity_check || 'Unknown integrity error';
170
+ return { ok: false, error: errorMsg };
171
+ }
172
+ } catch (error) {
173
+ return { ok: false, error: error.message };
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Handle corrupted database by backing it up and creating a fresh one
179
+ */
180
+ handleCorruptedDatabase() {
181
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
182
+ const backupPath = `${this.dbPath}.corrupted_${timestamp}`;
183
+
184
+ logger.warn(`[Database] Backing up corrupted database to: ${backupPath}`);
185
+
186
+ try {
187
+ // Move corrupted database and associated files
188
+ if (fs.existsSync(this.dbPath)) {
189
+ fs.renameSync(this.dbPath, backupPath);
190
+ }
191
+ if (fs.existsSync(`${this.dbPath}-shm`)) {
192
+ fs.renameSync(`${this.dbPath}-shm`, `${backupPath}-shm`);
193
+ }
194
+ if (fs.existsSync(`${this.dbPath}-wal`)) {
195
+ fs.renameSync(`${this.dbPath}-wal`, `${backupPath}-wal`);
196
+ }
197
+
198
+ logger.info(`[Database] Corrupted database backed up. A fresh database will be created.`);
199
+ } catch (error) {
200
+ logger.error(`[Database] Failed to backup corrupted database: ${error.message}`);
201
+ // Try to delete instead of rename
202
+ try {
203
+ fs.unlinkSync(this.dbPath);
204
+ if (fs.existsSync(`${this.dbPath}-shm`)) fs.unlinkSync(`${this.dbPath}-shm`);
205
+ if (fs.existsSync(`${this.dbPath}-wal`)) fs.unlinkSync(`${this.dbPath}-wal`);
206
+ logger.info(`[Database] Corrupted database removed. A fresh database will be created.`);
207
+ } catch (deleteError) {
208
+ logger.error(`[Database] Failed to remove corrupted database: ${deleteError.message}`);
209
+ throw new Error(`Cannot recover from database corruption: ${deleteError.message}`);
210
+ }
211
+ }
212
+ }
213
+
145
214
  createTables() {
146
215
  // Recordings table for frame capture sessions
147
216
  this.db.exec(`
@@ -1,5 +1,10 @@
1
1
  import Joi from 'joi';
2
2
 
3
+ // Valid console log levels - must match what Chrome extension captures
4
+ // Chrome extension captures: log, error, warn, info, debug, trace, table, dir, group, groupEnd, time, timeEnd, count
5
+ // See: chrome-extension/console-interception-library.js line 228
6
+ const VALID_LOG_LEVELS = ['log', 'info', 'warn', 'error', 'debug', 'trace', 'table', 'dir', 'group', 'groupEnd', 'time', 'timeEnd', 'count'];
7
+
3
8
  // Common validation patterns
4
9
  const patterns = {
5
10
  sessionId: Joi.string().pattern(/^[a-zA-Z0-9_-]+$/).min(1).max(100),
@@ -31,17 +36,18 @@ export const workflowRecordingSchema = Joi.object({
31
36
  text: Joi.string().allow('').optional(),
32
37
  placeholder: Joi.string().optional(),
33
38
  index: Joi.number().optional(),
34
- // Enhanced click tracking fields
39
+ // Enhanced click tracking fields - accept both strings and objects for flexibility
40
+ // (matches screenInteractionsSchema pattern - extension sends objects, database handles serialization)
35
41
  element_html: Joi.string().max(10240).optional(),
36
42
  elementHTML: Joi.string().max(10240).optional(), // camelCase variant
37
- component_data: Joi.string().max(3072).optional(),
38
- componentData: Joi.string().max(3072).optional(), // camelCase variant
39
- event_handlers: Joi.string().max(2048).optional(),
40
- eventHandlers: Joi.string().max(2048).optional(), // camelCase variant
41
- element_state: Joi.string().max(2048).optional(),
42
- elementState: Joi.string().max(2048).optional(), // camelCase variant
43
- performance_metrics: Joi.string().max(1024).optional(),
44
- performanceMetrics: Joi.string().max(1024).optional(), // camelCase variant
43
+ component_data: Joi.alternatives().try(Joi.string().max(3072), Joi.object()).optional(),
44
+ componentData: Joi.alternatives().try(Joi.string().max(3072), Joi.object()).optional(), // camelCase variant
45
+ event_handlers: Joi.alternatives().try(Joi.string().max(2048), Joi.object()).optional(),
46
+ eventHandlers: Joi.alternatives().try(Joi.string().max(2048), Joi.object()).optional(), // camelCase variant
47
+ element_state: Joi.alternatives().try(Joi.string().max(2048), Joi.object()).optional(),
48
+ elementState: Joi.alternatives().try(Joi.string().max(2048), Joi.object()).optional(), // camelCase variant
49
+ performance_metrics: Joi.alternatives().try(Joi.string().max(1024), Joi.object()).optional(),
50
+ performanceMetrics: Joi.alternatives().try(Joi.string().max(1024), Joi.object()).optional(), // camelCase variant
45
51
  // Function trace fields
46
52
  component: Joi.string().optional(),
47
53
  args: Joi.alternatives().try(Joi.array(), Joi.string()).optional(),
@@ -52,7 +58,7 @@ export const workflowRecordingSchema = Joi.object({
52
58
  ).required(),
53
59
  logs: Joi.array().items(
54
60
  Joi.object({
55
- level: Joi.string().valid('log', 'info', 'warn', 'error', 'debug').required(),
61
+ level: Joi.string().valid(...VALID_LOG_LEVELS).required(),
56
62
  message: Joi.string().required(),
57
63
  timestamp: Joi.number().required(),
58
64
  args: Joi.array().optional()
@@ -92,7 +98,7 @@ export const frameBatchSchema = Joi.object({
92
98
  index: Joi.number().integer().min(0).optional(), // Chrome extension sends this
93
99
  logs: Joi.array().items(
94
100
  Joi.object({
95
- level: Joi.string().valid('log', 'info', 'warn', 'error', 'debug').required(),
101
+ level: Joi.string().valid(...VALID_LOG_LEVELS).required(),
96
102
  message: Joi.string().required(),
97
103
  timestamp: Joi.number().required(),
98
104
  args: Joi.array().optional()
@@ -120,7 +126,7 @@ export const associateLogsSchema = Joi.object({
120
126
  sessionId: patterns.sessionId.required(),
121
127
  logs: Joi.array().items(
122
128
  Joi.object({
123
- level: Joi.string().valid('log', 'info', 'warn', 'error', 'debug').required(),
129
+ level: Joi.string().valid(...VALID_LOG_LEVELS).required(),
124
130
  message: Joi.string().required(),
125
131
  timestamp: Joi.number().required(),
126
132
  args: Joi.array().optional()
@@ -133,7 +139,7 @@ export const streamLogsSchema = Joi.object({
133
139
  sessionId: patterns.sessionId.required(),
134
140
  logs: Joi.array().items(
135
141
  Joi.object({
136
- level: Joi.string().valid('log', 'info', 'warn', 'error', 'debug', 'trace', 'table', 'dir', 'group', 'groupEnd', 'time', 'timeEnd', 'count').required(),
142
+ level: Joi.string().valid(...VALID_LOG_LEVELS).required(),
137
143
  message: Joi.string().required(),
138
144
  timestamp: Joi.number().required(),
139
145
  sequence: Joi.number().integer().min(0).required(),