camel-ai 0.2.74a4__py3-none-any.whl → 0.2.75__py3-none-any.whl

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.

Potentially problematic release.


This version of camel-ai might be problematic. Click here for more details.

Files changed (81) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +149 -95
  3. camel/configs/__init__.py +3 -0
  4. camel/configs/nebius_config.py +103 -0
  5. camel/interpreters/e2b_interpreter.py +34 -1
  6. camel/models/__init__.py +2 -0
  7. camel/models/aiml_model.py +1 -16
  8. camel/models/anthropic_model.py +6 -19
  9. camel/models/aws_bedrock_model.py +1 -16
  10. camel/models/azure_openai_model.py +1 -16
  11. camel/models/base_model.py +0 -12
  12. camel/models/cohere_model.py +1 -16
  13. camel/models/crynux_model.py +1 -16
  14. camel/models/deepseek_model.py +1 -16
  15. camel/models/gemini_model.py +1 -16
  16. camel/models/groq_model.py +1 -17
  17. camel/models/internlm_model.py +1 -16
  18. camel/models/litellm_model.py +1 -16
  19. camel/models/lmstudio_model.py +1 -17
  20. camel/models/mistral_model.py +1 -16
  21. camel/models/model_factory.py +2 -0
  22. camel/models/modelscope_model.py +1 -16
  23. camel/models/moonshot_model.py +6 -22
  24. camel/models/nebius_model.py +83 -0
  25. camel/models/nemotron_model.py +0 -5
  26. camel/models/netmind_model.py +1 -16
  27. camel/models/novita_model.py +1 -16
  28. camel/models/nvidia_model.py +1 -16
  29. camel/models/ollama_model.py +4 -19
  30. camel/models/openai_compatible_model.py +0 -3
  31. camel/models/openai_model.py +1 -22
  32. camel/models/openrouter_model.py +1 -17
  33. camel/models/ppio_model.py +1 -16
  34. camel/models/qianfan_model.py +1 -16
  35. camel/models/qwen_model.py +1 -16
  36. camel/models/reka_model.py +1 -16
  37. camel/models/samba_model.py +0 -32
  38. camel/models/sglang_model.py +1 -16
  39. camel/models/siliconflow_model.py +1 -16
  40. camel/models/stub_model.py +0 -4
  41. camel/models/togetherai_model.py +1 -16
  42. camel/models/vllm_model.py +1 -16
  43. camel/models/volcano_model.py +0 -17
  44. camel/models/watsonx_model.py +1 -16
  45. camel/models/yi_model.py +1 -16
  46. camel/models/zhipuai_model.py +1 -16
  47. camel/societies/workforce/prompts.py +1 -8
  48. camel/societies/workforce/task_channel.py +120 -27
  49. camel/societies/workforce/workforce.py +35 -3
  50. camel/toolkits/__init__.py +0 -2
  51. camel/toolkits/github_toolkit.py +104 -17
  52. camel/toolkits/hybrid_browser_toolkit/config_loader.py +3 -0
  53. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +260 -5
  54. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +288 -37
  55. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +3 -1
  56. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +209 -41
  57. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +22 -3
  58. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +28 -1
  59. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +101 -0
  60. camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
  61. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
  62. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
  63. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +312 -3
  64. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
  65. camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
  66. camel/toolkits/math_toolkit.py +64 -10
  67. camel/toolkits/mcp_toolkit.py +39 -14
  68. camel/toolkits/note_taking_toolkit.py +3 -4
  69. camel/toolkits/openai_image_toolkit.py +55 -24
  70. camel/toolkits/search_toolkit.py +153 -29
  71. camel/types/__init__.py +2 -2
  72. camel/types/enums.py +54 -10
  73. camel/types/openai_types.py +2 -2
  74. camel/types/unified_model_type.py +5 -0
  75. camel/utils/mcp.py +2 -2
  76. camel/utils/token_counting.py +18 -3
  77. {camel_ai-0.2.74a4.dist-info → camel_ai-0.2.75.dist-info}/METADATA +9 -15
  78. {camel_ai-0.2.74a4.dist-info → camel_ai-0.2.75.dist-info}/RECORD +80 -79
  79. camel/toolkits/openai_agent_toolkit.py +0 -135
  80. {camel_ai-0.2.74a4.dist-info → camel_ai-0.2.75.dist-info}/WHEEL +0 -0
  81. {camel_ai-0.2.74a4.dist-info → camel_ai-0.2.75.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
- import { Page, Browser, BrowserContext, chromium } from 'playwright';
1
+ import { Page, Browser, BrowserContext, chromium, ConsoleMessage } from 'playwright';
2
2
  import { BrowserToolkitConfig, SnapshotResult, SnapshotElement, ActionResult, TabInfo, BrowserAction, DetailedTiming } from './types';
3
3
  import { ConfigLoader, StealthConfig } from './config-loader';
4
4
 
@@ -6,18 +6,43 @@ export class HybridBrowserSession {
6
6
  private browser: Browser | null = null;
7
7
  private context: BrowserContext | null = null;
8
8
  private pages: Map<string, Page> = new Map();
9
+ private consoleLogs: Map<string, ConsoleMessage[]> = new Map();
9
10
  private currentTabId: string | null = null;
10
11
  private tabCounter = 0;
11
12
  private configLoader: ConfigLoader;
12
13
  private scrollPosition: { x: number; y: number } = {x: 0, y: 0};
13
14
  private hasNavigatedBefore = false; // Track if we've navigated before
15
+ private logLimit: number;
14
16
 
15
17
  constructor(config: BrowserToolkitConfig = {}) {
16
18
  // Use ConfigLoader's fromPythonConfig to handle conversion properly
17
19
  this.configLoader = ConfigLoader.fromPythonConfig(config);
20
+ // Load browser configuration for console log limit, default to 1000
21
+ this.logLimit = this.configLoader.getBrowserConfig().consoleLogLimit || 1000;
18
22
  }
19
23
 
20
- async ensureBrowser(): Promise<void> {
24
+ private registerNewPage(tabId: string, page: Page): void {
25
+ // Register page and logs with tabId
26
+ this.pages.set(tabId, page);
27
+ this.consoleLogs.set(tabId, []);
28
+ // Set up console log listener for the page
29
+ page.on('console', (msg: ConsoleMessage) => {
30
+ const logs = this.consoleLogs.get(tabId);
31
+ if (logs) {
32
+ logs.push(msg);
33
+ if (logs.length > this.logLimit) {
34
+ logs.shift();
35
+ }
36
+ }
37
+ });
38
+
39
+ // Clean logs on page close
40
+ page.on('close', () => {
41
+ this.consoleLogs.delete(tabId);
42
+ });
43
+ }
44
+
45
+ async ensureBrowser(): Promise<void> {
21
46
  if (this.browser) {
22
47
  return;
23
48
  }
@@ -47,21 +72,19 @@ export class HybridBrowserSession {
47
72
  this.context = await this.browser.newContext(contextOptions);
48
73
  }
49
74
 
50
- // Handle existing pages
51
75
  const pages = this.context.pages();
52
76
  if (pages.length > 0) {
53
- // Map existing pages - for CDP, only use pages with about:blank URL
77
+ // Map existing pages - for CDP, find ONE available blank page
54
78
  let availablePageFound = false;
55
79
  for (const page of pages) {
56
80
  const pageUrl = page.url();
57
- // In CDP mode, only consider pages with about:blank as available
58
- if (pageUrl === 'about:blank') {
81
+ if (this.isBlankPageUrl(pageUrl)) {
59
82
  const tabId = this.generateTabId();
60
- this.pages.set(tabId, page);
61
- if (!this.currentTabId) {
62
- this.currentTabId = tabId;
63
- availablePageFound = true;
64
- }
83
+ this.registerNewPage(tabId, page);
84
+ this.currentTabId = tabId;
85
+ availablePageFound = true;
86
+ console.log(`[CDP] Registered blank page as initial tab: ${tabId}, URL: ${pageUrl}`);
87
+ break; // Only register ONE page initially
65
88
  }
66
89
  }
67
90
 
@@ -97,7 +120,7 @@ export class HybridBrowserSession {
97
120
  const pages = this.context.pages();
98
121
  if (pages.length > 0) {
99
122
  const initialTabId = this.generateTabId();
100
- this.pages.set(initialTabId, pages[0]);
123
+ this.registerNewPage(initialTabId, pages[0]);
101
124
  this.currentTabId = initialTabId;
102
125
  }
103
126
  } else {
@@ -115,7 +138,7 @@ export class HybridBrowserSession {
115
138
 
116
139
  const initialPage = await this.context.newPage();
117
140
  const initialTabId = this.generateTabId();
118
- this.pages.set(initialTabId, initialPage);
141
+ this.registerNewPage(initialTabId, initialPage);
119
142
  this.currentTabId = initialTabId;
120
143
  }
121
144
  }
@@ -132,13 +155,57 @@ export class HybridBrowserSession {
132
155
  return `${browserConfig.tabIdPrefix}${String(++this.tabCounter).padStart(browserConfig.tabCounterPadding, '0')}`;
133
156
  }
134
157
 
158
+ private isBlankPageUrl(url: string): boolean {
159
+ // Unified blank page detection logic used across the codebase
160
+ const browserConfig = this.configLoader.getBrowserConfig();
161
+ return (
162
+ // Standard about:blank variations (prefix match for query params)
163
+ url === 'about:blank' ||
164
+ url.startsWith('about:blank?') ||
165
+ // Configured blank page URLs (exact match for compatibility)
166
+ browserConfig.blankPageUrls.includes(url) ||
167
+ // Empty URL
168
+ url === '' ||
169
+ // Data URLs (often used for blank pages)
170
+ url.startsWith(browserConfig.dataUrlPrefix || 'data:')
171
+ );
172
+ }
173
+
135
174
  async getCurrentPage(): Promise<Page> {
136
175
  if (!this.currentTabId || !this.pages.has(this.currentTabId)) {
176
+ // In CDP mode, try to create a new page if none exists
177
+ const browserConfig = this.configLoader.getBrowserConfig();
178
+ if (browserConfig.connectOverCdp && this.context) {
179
+ console.log('[CDP] No active page found, attempting to create new page...');
180
+ try {
181
+ const newPage = await this.context.newPage();
182
+ const newTabId = this.generateTabId();
183
+ this.registerNewPage(newTabId, newPage);
184
+ this.currentTabId = newTabId;
185
+
186
+ // Set page timeouts
187
+ newPage.setDefaultNavigationTimeout(browserConfig.navigationTimeout);
188
+ newPage.setDefaultTimeout(browserConfig.navigationTimeout);
189
+
190
+ console.log(`[CDP] Created new page with tab ID: ${newTabId}`);
191
+ return newPage;
192
+ } catch (error) {
193
+ console.error('[CDP] Failed to create new page:', error);
194
+ throw new Error('No active page available and failed to create new page in CDP mode');
195
+ }
196
+ }
137
197
  throw new Error('No active page available');
138
198
  }
139
199
  return this.pages.get(this.currentTabId)!;
140
200
  }
141
201
 
202
+ async getCurrentLogs(): Promise<ConsoleMessage[]> {
203
+ if (!this.currentTabId || !this.consoleLogs.has(this.currentTabId)) {
204
+ return [];
205
+ }
206
+ return this.consoleLogs.get(this.currentTabId) || [];
207
+ }
208
+
142
209
  /**
143
210
  * Get current scroll position from the page
144
211
  */
@@ -343,7 +410,7 @@ export class HybridBrowserSession {
343
410
 
344
411
  // Generate tab ID for the new page
345
412
  const newTabId = this.generateTabId();
346
- this.pages.set(newTabId, newPage);
413
+ this.registerNewPage(newTabId, newPage);
347
414
 
348
415
  // Set up page properties
349
416
  const browserConfig = this.configLoader.getBrowserConfig();
@@ -384,25 +451,67 @@ export class HybridBrowserSession {
384
451
 
385
452
  /**
386
453
  * Simplified type implementation using Playwright's aria-ref selector
454
+ * Supports both single and multiple input operations
387
455
  */
388
- private async performType(page: Page, ref: string, text: string): Promise<{ success: boolean; error?: string }> {
456
+ private async performType(page: Page, ref: string | undefined, text: string | undefined, inputs?: Array<{ ref: string; text: string }>): Promise<{ success: boolean; error?: string; details?: Record<string, any> }> {
389
457
  try {
390
458
  // Ensure we have the latest snapshot
391
459
  await (page as any)._snapshotForAI();
392
460
 
393
- // Use Playwright's aria-ref selector
394
- const selector = `aria-ref=${ref}`;
395
- const element = await page.locator(selector).first();
396
-
397
- const exists = await element.count() > 0;
398
- if (!exists) {
399
- return { success: false, error: `Element with ref ${ref} not found` };
461
+ // Handle multiple inputs if provided
462
+ if (inputs && inputs.length > 0) {
463
+ const results: Record<string, { success: boolean; error?: string }> = {};
464
+
465
+ for (const input of inputs) {
466
+ const selector = `aria-ref=${input.ref}`;
467
+ const element = await page.locator(selector).first();
468
+
469
+ const exists = await element.count() > 0;
470
+ if (!exists) {
471
+ results[input.ref] = { success: false, error: `Element with ref ${input.ref} not found` };
472
+ continue;
473
+ }
474
+
475
+ try {
476
+ // Type text using Playwright's built-in fill method
477
+ await element.fill(input.text);
478
+ results[input.ref] = { success: true };
479
+ } catch (error) {
480
+ results[input.ref] = { success: false, error: `Type failed: ${error}` };
481
+ }
482
+ }
483
+
484
+ // Check if all inputs were successful
485
+ const allSuccess = Object.values(results).every(r => r.success);
486
+ const errors = Object.entries(results)
487
+ .filter(([_, r]) => !r.success)
488
+ .map(([ref, r]) => `${ref}: ${r.error}`)
489
+ .join('; ');
490
+
491
+ return {
492
+ success: allSuccess,
493
+ error: allSuccess ? undefined : `Some inputs failed: ${errors}`,
494
+ details: results
495
+ };
400
496
  }
401
497
 
402
- // Type text using Playwright's built-in fill method
403
- await element.fill(text);
498
+ // Handle single input (backward compatibility)
499
+ if (ref && text !== undefined) {
500
+ const selector = `aria-ref=${ref}`;
501
+ const element = await page.locator(selector).first();
502
+
503
+ const exists = await element.count() > 0;
504
+ if (!exists) {
505
+ return { success: false, error: `Element with ref ${ref} not found` };
506
+ }
507
+
508
+ // Type text using Playwright's built-in fill method
509
+ await element.fill(text);
510
+
511
+ return { success: true };
512
+ }
404
513
 
405
- return { success: true };
514
+ return { success: false, error: 'No valid input provided' };
406
515
  } catch (error) {
407
516
  return { success: false, error: `Type failed: ${error}` };
408
517
  }
@@ -434,7 +543,97 @@ export class HybridBrowserSession {
434
543
  }
435
544
  }
436
545
 
546
+ /**
547
+ * Simplified mouse control implementation
548
+ */
549
+ private async performMouseControl(page: Page, control: string, x: number, y: number): Promise<{ success: boolean; error?: string }> {
550
+ try {
551
+ const viewport = page.viewportSize();
552
+ if (!viewport) {
553
+ return { success: false, error: 'Viewport size not available from page.' };
554
+ }
555
+ if (x < 0 || y < 0 || x > viewport.width || y > viewport.height) {
556
+ return { success: false, error: `Invalid coordinates, outside viewport bounds: (${x}, ${y})` };
557
+ }
558
+ switch (control) {
559
+ case 'click': {
560
+ await page.mouse.click(x, y);
561
+ break;
562
+ }
563
+ case 'right_click': {
564
+ await page.mouse.click(x, y, { button: 'right' });
565
+ break;
566
+ }
567
+ case 'dblclick': {
568
+ await page.mouse.dblclick(x, y);
569
+ break;
570
+ }
571
+ default:
572
+ return { success: false, error: `Invalid control action: ${control}` };
573
+ }
574
+
575
+ return { success: true };
576
+ } catch (error) {
577
+ return { success: false, error: `Mouse action failed: ${error}` };
578
+ }
579
+ }
580
+
581
+ /**
582
+ * Enhanced mouse drag and drop implementation using ref IDs
583
+ */
584
+ private async performMouseDrag(page: Page, fromRef: string, toRef: string): Promise<{ success: boolean; error?: string }> {
585
+ try {
586
+ // Ensure we have the latest snapshot
587
+ await (page as any)._snapshotForAI();
588
+
589
+ // Get elements using Playwright's aria-ref selector
590
+ const fromSelector = `aria-ref=${fromRef}`;
591
+ const toSelector = `aria-ref=${toRef}`;
592
+
593
+ const fromElement = await page.locator(fromSelector).first();
594
+ const toElement = await page.locator(toSelector).first();
595
+
596
+ // Check if elements exist
597
+ const fromExists = await fromElement.count() > 0;
598
+ const toExists = await toElement.count() > 0;
599
+
600
+ if (!fromExists) {
601
+ return { success: false, error: `Source element with ref ${fromRef} not found` };
602
+ }
603
+
604
+ if (!toExists) {
605
+ return { success: false, error: `Target element with ref ${toRef} not found` };
606
+ }
607
+
608
+ // Get the center coordinates of both elements
609
+ const fromBox = await fromElement.boundingBox();
610
+ const toBox = await toElement.boundingBox();
611
+
612
+ if (!fromBox) {
613
+ return { success: false, error: `Could not get bounding box for source element with ref ${fromRef}` };
614
+ }
615
+
616
+ if (!toBox) {
617
+ return { success: false, error: `Could not get bounding box for target element with ref ${toRef}` };
618
+ }
619
+
620
+ const fromX = fromBox.x + fromBox.width / 2;
621
+ const fromY = fromBox.y + fromBox.height / 2;
622
+ const toX = toBox.x + toBox.width / 2;
623
+ const toY = toBox.y + toBox.height / 2;
624
+
625
+ // Perform the drag operation
626
+ await page.mouse.move(fromX, fromY);
627
+ await page.mouse.down();
628
+ // Destination coordinates
629
+ await page.mouse.move(toX, toY);
630
+ await page.mouse.up();
437
631
 
632
+ return { success: true };
633
+ } catch (error) {
634
+ return { success: false, error: `Mouse drag action failed: ${error}` };
635
+ }
636
+ }
438
637
 
439
638
  async executeAction(action: BrowserAction): Promise<ActionResult> {
440
639
  const startTime = Date.now();
@@ -450,6 +649,8 @@ export class HybridBrowserSession {
450
649
  // No need to pre-fetch snapshot - each action method handles this
451
650
 
452
651
  let newTabId: string | undefined;
652
+ let customMessage: string | undefined;
653
+ let actionDetails: Record<string, any> | undefined;
453
654
 
454
655
  switch (action.type) {
455
656
  case 'click': {
@@ -474,12 +675,20 @@ export class HybridBrowserSession {
474
675
  elementSearchTime = Date.now() - elementSearchStart;
475
676
  const typeStart = Date.now();
476
677
 
477
- const typeResult = await this.performType(page, action.ref, action.text);
678
+ const typeResult = await this.performType(page, action.ref, action.text, action.inputs);
478
679
 
479
680
  if (!typeResult.success) {
480
681
  throw new Error(`Type failed: ${typeResult.error}`);
481
682
  }
482
683
 
684
+ // Set custom message and details if multiple inputs were used
685
+ if (typeResult.details) {
686
+ const successCount = Object.values(typeResult.details).filter((r: any) => r.success).length;
687
+ const totalCount = Object.keys(typeResult.details).length;
688
+ customMessage = `Typed text into ${successCount}/${totalCount} elements`;
689
+ actionDetails = typeResult.details;
690
+ }
691
+
483
692
  actionExecutionTime = Date.now() - typeStart;
484
693
  break;
485
694
  }
@@ -519,6 +728,40 @@ export class HybridBrowserSession {
519
728
  actionExecutionTime = Date.now() - enterStart;
520
729
  break;
521
730
  }
731
+
732
+ case 'mouse_control': {
733
+ elementSearchTime = Date.now() - elementSearchStart;
734
+ const mouseControlStart = Date.now();
735
+ const mouseControlResult = await this.performMouseControl(page, action.control, action.x, action.y);
736
+
737
+ if (!mouseControlResult.success) {
738
+ throw new Error(`Action failed: ${mouseControlResult.error}`);
739
+ }
740
+ actionExecutionTime = Date.now() - mouseControlStart;
741
+ break;
742
+ }
743
+
744
+ case 'mouse_drag': {
745
+ elementSearchTime = Date.now() - elementSearchStart;
746
+ const mouseDragStart = Date.now();
747
+ const mouseDragResult = await this.performMouseDrag(page, action.from_ref, action.to_ref);
748
+
749
+ if (!mouseDragResult.success) {
750
+ throw new Error(`Action failed: ${mouseDragResult.error}`);
751
+ }
752
+ actionExecutionTime = Date.now() - mouseDragStart;
753
+ break;
754
+ }
755
+
756
+ case 'press_key': {
757
+ elementSearchTime = Date.now() - elementSearchStart;
758
+ const keyPressStart = Date.now();
759
+ // concatenate keys with '+' for key combinations
760
+ const keys = action.keys.join('+');
761
+ await page.keyboard.press(keys);
762
+ actionExecutionTime = Date.now() - keyPressStart;
763
+ break;
764
+ }
522
765
 
523
766
  default:
524
767
  throw new Error(`Unknown action type: ${(action as any).type}`);
@@ -533,7 +776,7 @@ export class HybridBrowserSession {
533
776
 
534
777
  return {
535
778
  success: true,
536
- message: `Action ${action.type} executed successfully`,
779
+ message: customMessage || `Action ${action.type} executed successfully`,
537
780
  timing: {
538
781
  total_time_ms: totalTime,
539
782
  element_search_time_ms: elementSearchTime,
@@ -543,6 +786,7 @@ export class HybridBrowserSession {
543
786
  network_idle_time_ms: stabilityResult.networkIdleTime,
544
787
  },
545
788
  ...(newTabId && { newTabId }), // Include new tab ID if present
789
+ ...(actionDetails && { details: actionDetails }), // Include action details if present
546
790
  };
547
791
  } catch (error) {
548
792
  const totalTime = Date.now() - startTime;
@@ -584,16 +828,23 @@ export class HybridBrowserSession {
584
828
 
585
829
  try {
586
830
  // Get current page to check if it's blank
587
- const currentPage = await this.getCurrentPage();
588
- const currentUrl = currentPage.url();
831
+ let currentPage: Page;
832
+ let currentUrl: string;
833
+
834
+ try {
835
+ currentPage = await this.getCurrentPage();
836
+ currentUrl = currentPage.url();
837
+ } catch (error: any) {
838
+ // If no active page is available, getCurrentPage() will create one in CDP mode
839
+ console.log('[visitPage] Failed to get current page:', error);
840
+ throw new Error(`No active page available: ${error?.message || error}`);
841
+ }
589
842
 
590
843
  // Check if current page is blank or if this is the first navigation
591
844
  const browserConfig = this.configLoader.getBrowserConfig();
592
- const isBlankPage = (
593
- browserConfig.blankPageUrls.includes(currentUrl) ||
594
- currentUrl === browserConfig.defaultStartUrl ||
595
- currentUrl.startsWith(browserConfig.dataUrlPrefix) // data URLs are often used for blank pages
596
- );
845
+
846
+ // Use unified blank page detection
847
+ const isBlankPage = this.isBlankPageUrl(currentUrl) || currentUrl === browserConfig.defaultStartUrl;
597
848
 
598
849
  const shouldUseCurrentTab = isBlankPage || !this.hasNavigatedBefore;
599
850
 
@@ -648,10 +899,10 @@ export class HybridBrowserSession {
648
899
  const pageUrl = page.url();
649
900
  // Check if this page is not already tracked and is blank
650
901
  const isTracked = Array.from(this.pages.values()).includes(page);
651
- if (!isTracked && pageUrl === 'about:blank') {
902
+ if (!isTracked && this.isBlankPageUrl(pageUrl)) {
652
903
  newPage = page;
653
904
  newTabId = this.generateTabId();
654
- this.pages.set(newTabId, newPage);
905
+ this.registerNewPage(newTabId, newPage);
655
906
  break;
656
907
  }
657
908
  }
@@ -663,7 +914,7 @@ export class HybridBrowserSession {
663
914
  // Non-CDP mode: create new page as usual
664
915
  newPage = await this.context.newPage();
665
916
  newTabId = this.generateTabId();
666
- this.pages.set(newTabId, newPage);
917
+ this.registerNewPage(newTabId, newPage);
667
918
  }
668
919
 
669
920
  // Set up page properties
@@ -30,6 +30,7 @@ export interface BrowserConfig {
30
30
  // Tab management
31
31
  tabIdPrefix: string;
32
32
  tabCounterPadding: number;
33
+ consoleLogLimit: number;
33
34
 
34
35
  // Scroll and positioning
35
36
  scrollPositionScale: number;
@@ -113,9 +114,10 @@ function getDefaultBrowserConfig(): BrowserConfig {
113
114
  clickTimeout: 3000,
114
115
  tabIdPrefix: 'tab-',
115
116
  tabCounterPadding: 3,
117
+ consoleLogLimit: 1000,
116
118
  scrollPositionScale: 0.1,
117
119
  navigationDelay: 100,
118
- blankPageUrls: ['about:blank', ''],
120
+ blankPageUrls: [],
119
121
  dataUrlPrefix: 'data:',
120
122
  domContentLoadedState: 'domcontentloaded',
121
123
  networkIdleState: 'networkidle',