vibesurf 0.1.10__py3-none-any.whl → 0.1.11__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 vibesurf might be problematic. Click here for more details.

Files changed (51) hide show
  1. vibe_surf/_version.py +2 -2
  2. vibe_surf/agents/browser_use_agent.py +68 -45
  3. vibe_surf/agents/prompts/report_writer_prompt.py +73 -0
  4. vibe_surf/agents/prompts/vibe_surf_prompt.py +85 -172
  5. vibe_surf/agents/report_writer_agent.py +380 -226
  6. vibe_surf/agents/vibe_surf_agent.py +879 -825
  7. vibe_surf/agents/views.py +130 -0
  8. vibe_surf/backend/api/activity.py +3 -1
  9. vibe_surf/backend/api/browser.py +9 -5
  10. vibe_surf/backend/api/config.py +8 -5
  11. vibe_surf/backend/api/files.py +59 -50
  12. vibe_surf/backend/api/models.py +2 -2
  13. vibe_surf/backend/api/task.py +45 -12
  14. vibe_surf/backend/database/manager.py +24 -18
  15. vibe_surf/backend/database/queries.py +199 -192
  16. vibe_surf/backend/database/schemas.py +1 -1
  17. vibe_surf/backend/main.py +4 -2
  18. vibe_surf/backend/shared_state.py +28 -35
  19. vibe_surf/backend/utils/encryption.py +3 -1
  20. vibe_surf/backend/utils/llm_factory.py +41 -36
  21. vibe_surf/browser/agent_browser_session.py +0 -4
  22. vibe_surf/browser/browser_manager.py +14 -8
  23. vibe_surf/browser/utils.py +5 -3
  24. vibe_surf/browser/watchdogs/dom_watchdog.py +0 -45
  25. vibe_surf/chrome_extension/background.js +4 -0
  26. vibe_surf/chrome_extension/scripts/api-client.js +13 -0
  27. vibe_surf/chrome_extension/scripts/file-manager.js +27 -71
  28. vibe_surf/chrome_extension/scripts/session-manager.js +21 -3
  29. vibe_surf/chrome_extension/scripts/ui-manager.js +831 -48
  30. vibe_surf/chrome_extension/sidepanel.html +21 -4
  31. vibe_surf/chrome_extension/styles/activity.css +365 -5
  32. vibe_surf/chrome_extension/styles/input.css +139 -0
  33. vibe_surf/cli.py +4 -22
  34. vibe_surf/common.py +35 -0
  35. vibe_surf/llm/openai_compatible.py +148 -93
  36. vibe_surf/logger.py +99 -0
  37. vibe_surf/{controller/vibesurf_tools.py → tools/browser_use_tools.py} +233 -219
  38. vibe_surf/tools/file_system.py +415 -0
  39. vibe_surf/{controller → tools}/mcp_client.py +4 -3
  40. vibe_surf/tools/report_writer_tools.py +21 -0
  41. vibe_surf/tools/vibesurf_tools.py +657 -0
  42. vibe_surf/tools/views.py +120 -0
  43. {vibesurf-0.1.10.dist-info → vibesurf-0.1.11.dist-info}/METADATA +6 -2
  44. {vibesurf-0.1.10.dist-info → vibesurf-0.1.11.dist-info}/RECORD +49 -43
  45. vibe_surf/controller/file_system.py +0 -53
  46. vibe_surf/controller/views.py +0 -37
  47. /vibe_surf/{controller → tools}/__init__.py +0 -0
  48. {vibesurf-0.1.10.dist-info → vibesurf-0.1.11.dist-info}/WHEEL +0 -0
  49. {vibesurf-0.1.10.dist-info → vibesurf-0.1.11.dist-info}/entry_points.txt +0 -0
  50. {vibesurf-0.1.10.dist-info → vibesurf-0.1.11.dist-info}/licenses/LICENSE +0 -0
  51. {vibesurf-0.1.10.dist-info → vibesurf-0.1.11.dist-info}/top_level.txt +0 -0
@@ -58,7 +58,7 @@ class McpServerConfig(BaseModel):
58
58
  mcpServers: Dict[str, McpServerParams] = Field(default_factory=dict)
59
59
 
60
60
  class ControllerConfiguration(BaseModel):
61
- """Schema for Task.mcp_server_config JSON field (legacy controller config)"""
61
+ """Schema for Task.mcp_server_config JSON field (legacy tools config)"""
62
62
 
63
63
  # Action control
64
64
  exclude_actions: List[str] = Field(default_factory=list)
vibe_surf/backend/main.py CHANGED
@@ -25,8 +25,10 @@ from .api.browser import router as browser_router
25
25
  from . import shared_state
26
26
 
27
27
  # Configure logging
28
- logging.basicConfig(level=logging.INFO)
29
- logger = logging.getLogger(__name__)
28
+
29
+ from vibe_surf.logger import get_logger
30
+
31
+ logger = get_logger(__name__)
30
32
 
31
33
  app = FastAPI(
32
34
  title="VibeSurf Backend API",
@@ -15,7 +15,8 @@ from pathlib import Path
15
15
 
16
16
  # VibeSurf components
17
17
  from vibe_surf.agents.vibe_surf_agent import VibeSurfAgent
18
- from vibe_surf.controller.vibesurf_tools import VibeSurfController
18
+ from vibe_surf.tools.browser_use_tools import BrowserUseTools
19
+ from vibe_surf.tools.vibesurf_tools import VibeSurfTools
19
20
  from vibe_surf.browser.browser_manager import BrowserManager
20
21
  from browser_use.llm.base import BaseChatModel
21
22
  from browser_use.llm.openai.chat import ChatOpenAI
@@ -29,7 +30,7 @@ logger = logging.getLogger(__name__)
29
30
  # Global VibeSurf components
30
31
  vibesurf_agent: Optional[VibeSurfAgent] = None
31
32
  browser_manager: Optional[BrowserManager] = None
32
- controller: Optional[VibeSurfController] = None
33
+ vibesurf_tools: Optional[VibeSurfTools] = None
33
34
  llm: Optional[BaseChatModel] = None
34
35
  db_manager: Optional['DatabaseManager'] = None
35
36
 
@@ -50,10 +51,13 @@ active_task: Optional[Dict[str, Any]] = None
50
51
 
51
52
  def get_all_components():
52
53
  """Get all components as a dictionary"""
54
+ global vibesurf_agent, browser_manager, vibesurf_tools, llm, db_manager
55
+ global workspace_dir, browser_execution_path, browser_user_data, active_mcp_server, envs
56
+
53
57
  return {
54
58
  "vibesurf_agent": vibesurf_agent,
55
59
  "browser_manager": browser_manager,
56
- "controller": controller,
60
+ "tools": vibesurf_tools,
57
61
  "llm": llm,
58
62
  "db_manager": db_manager,
59
63
  "workspace_dir": workspace_dir,
@@ -67,15 +71,15 @@ def get_all_components():
67
71
 
68
72
  def set_components(**kwargs):
69
73
  """Update global components"""
70
- global vibesurf_agent, browser_manager, controller, llm, db_manager
74
+ global vibesurf_agent, browser_manager, vibesurf_tools, llm, db_manager
71
75
  global workspace_dir, browser_execution_path, browser_user_data, active_mcp_server, envs
72
76
 
73
77
  if "vibesurf_agent" in kwargs:
74
78
  vibesurf_agent = kwargs["vibesurf_agent"]
75
79
  if "browser_manager" in kwargs:
76
80
  browser_manager = kwargs["browser_manager"]
77
- if "controller" in kwargs:
78
- controller = kwargs["controller"]
81
+ if "tools" in kwargs:
82
+ vibesurf_tools = kwargs["tools"]
79
83
  if "llm" in kwargs:
80
84
  llm = kwargs["llm"]
81
85
  if "db_manager" in kwargs:
@@ -223,8 +227,8 @@ def clear_active_task():
223
227
 
224
228
 
225
229
  async def _check_and_update_mcp_servers(db_session):
226
- """Check if MCP server configuration has changed and update controller if needed"""
227
- global controller, active_mcp_server
230
+ """Check if MCP server configuration has changed and update tools if needed"""
231
+ global vibesurf_tools, active_mcp_server
228
232
 
229
233
  try:
230
234
  if not db_session:
@@ -238,21 +242,21 @@ async def _check_and_update_mcp_servers(db_session):
238
242
 
239
243
  # Compare with shared state
240
244
  if current_active_servers != active_mcp_server:
241
- logger.info(f"MCP server configuration changed. Updating controller...")
245
+ logger.info(f"MCP server configuration changed. Updating tools...")
242
246
  logger.info(f"Old config: {active_mcp_server}")
243
247
  logger.info(f"New config: {current_active_servers}")
244
248
 
245
249
  # Update shared state
246
250
  active_mcp_server = current_active_servers.copy()
247
251
 
248
- # Create new MCP server config for controller
252
+ # Create new MCP server config for tools
249
253
  mcp_server_config = await _build_mcp_server_config(active_profiles)
250
254
 
251
255
  # Unregister old MCP clients and register new ones
252
- if controller:
253
- await controller.unregister_mcp_clients()
254
- controller.mcp_server_config = mcp_server_config
255
- await controller.register_mcp_clients()
256
+ if vibesurf_tools:
257
+ await vibesurf_tools.unregister_mcp_clients()
258
+ vibesurf_tools.mcp_server_config = mcp_server_config
259
+ await vibesurf_tools.register_mcp_clients()
256
260
  logger.info("✅ Controller MCP configuration updated successfully")
257
261
 
258
262
  except Exception as e:
@@ -310,25 +314,13 @@ async def _load_active_mcp_servers():
310
314
 
311
315
  async def initialize_vibesurf_components():
312
316
  """Initialize VibeSurf components from environment variables and default LLM profile"""
313
- global vibesurf_agent, browser_manager, controller, llm, db_manager
317
+ global vibesurf_agent, browser_manager, vibesurf_tools, llm, db_manager
314
318
  global workspace_dir, browser_execution_path, browser_user_data, envs
319
+ from vibe_surf import common
315
320
 
316
321
  try:
317
322
  # Load environment variables
318
- env_workspace_dir = os.getenv("VIBESURF_WORKSPACE", "")
319
- if not env_workspace_dir or not env_workspace_dir.strip():
320
- # Set default workspace directory based on OS
321
- if platform.system() == "Windows":
322
- default_workspace = os.path.join(os.environ.get("APPDATA", ""), "VibeSurf")
323
- elif platform.system() == "Darwin": # macOS
324
- default_workspace = os.path.join(os.path.expanduser("~"), "Library", "Application Support", "VibeSurf")
325
- else: # Linux and others
326
- default_workspace = os.path.join(os.path.expanduser("~"), ".vibesurf")
327
- workspace_dir = default_workspace
328
- else:
329
- workspace_dir = env_workspace_dir
330
- workspace_dir = os.path.abspath(workspace_dir)
331
- os.makedirs(workspace_dir, exist_ok=True)
323
+ workspace_dir = common.get_workspace_dir()
332
324
  logger.info("WorkSpace directory: {}".format(workspace_dir))
333
325
 
334
326
  # Load environment configuration from envs.json
@@ -438,19 +430,19 @@ async def initialize_vibesurf_components():
438
430
  # Load active MCP servers from database
439
431
  mcp_server_config = await _load_active_mcp_servers()
440
432
 
441
- # Initialize vibesurf controller with MCP server config
442
- controller = VibeSurfController(mcp_server_config=mcp_server_config)
433
+ # Initialize vibesurf tools with MCP server config
434
+ vibesurf_tools = VibeSurfTools(mcp_server_config=mcp_server_config)
443
435
 
444
436
  # Register MCP clients if there are any active MCP servers
445
437
  if mcp_server_config and mcp_server_config.get("mcpServers"):
446
- await controller.register_mcp_clients()
438
+ await vibesurf_tools.register_mcp_clients()
447
439
  logger.info(f"✅ Registered {len(mcp_server_config['mcpServers'])} MCP servers")
448
440
 
449
441
  # Initialize VibeSurfAgent
450
442
  vibesurf_agent = VibeSurfAgent(
451
443
  llm=llm,
452
444
  browser_manager=browser_manager,
453
- controller=controller,
445
+ tools=vibesurf_tools,
454
446
  workspace_dir=workspace_dir
455
447
  )
456
448
 
@@ -532,8 +524,9 @@ async def update_llm_from_profile(profile_name: str):
532
524
 
533
525
  # Update global state
534
526
  llm = new_llm
535
- if vibesurf_agent:
536
- vibesurf_agent.llm = new_llm
527
+ if vibesurf_agent and vibesurf_agent.token_cost_service:
528
+ # FIX: Register new LLM with token cost service to maintain tracking
529
+ vibesurf_agent.llm = vibesurf_agent.token_cost_service.register_llm(new_llm)
537
530
 
538
531
  logger.info(f"✅ LLM updated to profile: {profile_name}")
539
532
  return True
@@ -15,7 +15,9 @@ from getmac import get_mac_address
15
15
 
16
16
  import logging
17
17
 
18
- logger = logging.getLogger(__name__)
18
+ from vibe_surf.logger import get_logger
19
+
20
+ logger = get_logger(__name__)
19
21
 
20
22
 
21
23
  def derive_key(machine_id: str, salt: bytes = None) -> bytes:
@@ -6,26 +6,29 @@ from typing import Optional
6
6
  import logging
7
7
  from ..llm_config import get_supported_providers, is_provider_supported
8
8
 
9
- logger = logging.getLogger(__name__)
9
+ from vibe_surf.logger import get_logger
10
+
11
+ logger = get_logger(__name__)
12
+
10
13
 
11
14
  def create_llm_from_profile(llm_profile):
12
15
  """Create LLM instance from LLMProfile database record (dict or object)"""
13
16
  try:
14
17
  # Import LLM classes from browser_use and vibe_surf
15
18
  from browser_use.llm import (
16
- ChatOpenAI, ChatAnthropic, ChatGoogle, ChatAzureOpenAI,
17
- ChatGroq, ChatOllama, ChatOpenRouter, ChatDeepSeek,
19
+ ChatOpenAI, ChatAnthropic, ChatGoogle, ChatAzureOpenAI,
20
+ ChatGroq, ChatOllama, ChatOpenRouter, ChatDeepSeek,
18
21
  ChatAWSBedrock, ChatAnthropicBedrock
19
22
  )
20
23
  from vibe_surf.llm import ChatOpenAICompatible
21
-
24
+
22
25
  # Handle both dict and object access patterns
23
26
  def get_attr(obj, key, default=None):
24
27
  if isinstance(obj, dict):
25
28
  return obj.get(key, default)
26
29
  else:
27
30
  return getattr(obj, key, default)
28
-
31
+
29
32
  provider = get_attr(llm_profile, 'provider')
30
33
  model = get_attr(llm_profile, 'model')
31
34
  api_key = get_attr(llm_profile, 'api_key') # Should already be decrypted by queries
@@ -36,11 +39,11 @@ def create_llm_from_profile(llm_profile):
36
39
  frequency_penalty = get_attr(llm_profile, 'frequency_penalty')
37
40
  seed = get_attr(llm_profile, 'seed')
38
41
  provider_config = get_attr(llm_profile, 'provider_config', {})
39
-
42
+
40
43
  # Validate provider
41
44
  if not is_provider_supported(provider):
42
45
  raise ValueError(f"Unsupported provider: {provider}. Supported: {get_supported_providers()}")
43
-
46
+
44
47
  # Define provider-specific parameter support
45
48
  provider_param_support = {
46
49
  "openai": ["temperature"],
@@ -55,11 +58,11 @@ def create_llm_from_profile(llm_profile):
55
58
  "anthropic_bedrock": ["temperature"],
56
59
  "openai_compatible": ["temperature"]
57
60
  }
58
-
61
+
59
62
  # Build common parameters based on provider support
60
63
  supported_params = provider_param_support.get(provider, [])
61
64
  common_params = {}
62
-
65
+
63
66
  if temperature is not None and "temperature" in supported_params:
64
67
  common_params["temperature"] = temperature
65
68
  if max_tokens is not None and "max_tokens" in supported_params:
@@ -70,11 +73,11 @@ def create_llm_from_profile(llm_profile):
70
73
  common_params["frequency_penalty"] = frequency_penalty
71
74
  if seed is not None and "seed" in supported_params:
72
75
  common_params["seed"] = seed
73
-
76
+
74
77
  # Add provider-specific config if available
75
78
  if provider_config:
76
79
  common_params.update(provider_config)
77
-
80
+
78
81
  # Create LLM instance based on provider
79
82
  if provider == "openai":
80
83
  params = {
@@ -85,21 +88,21 @@ def create_llm_from_profile(llm_profile):
85
88
  if base_url:
86
89
  params["base_url"] = base_url
87
90
  return ChatOpenAI(**params)
88
-
91
+
89
92
  elif provider == "anthropic":
90
93
  return ChatAnthropic(
91
94
  model=model,
92
95
  api_key=api_key,
93
96
  **common_params
94
97
  )
95
-
98
+
96
99
  elif provider == "google":
97
100
  return ChatGoogle(
98
101
  model=model,
99
102
  api_key=api_key,
100
103
  **common_params
101
104
  )
102
-
105
+
103
106
  elif provider == "azure_openai":
104
107
  if not base_url:
105
108
  raise ValueError("Azure OpenAI requires base_url (azure_endpoint)")
@@ -110,14 +113,14 @@ def create_llm_from_profile(llm_profile):
110
113
  azure_endpoint=base_url,
111
114
  **common_params
112
115
  )
113
-
116
+
114
117
  elif provider == "groq":
115
118
  return ChatGroq(
116
119
  model=model,
117
120
  api_key=api_key,
118
121
  **common_params
119
122
  )
120
-
123
+
121
124
  elif provider == "ollama":
122
125
  params = {
123
126
  "model": model,
@@ -128,21 +131,21 @@ def create_llm_from_profile(llm_profile):
128
131
  else:
129
132
  params["host"] = "http://localhost:11434" # Default Ollama URL
130
133
  return ChatOllama(**params)
131
-
134
+
132
135
  elif provider == "openrouter":
133
136
  return ChatOpenRouter(
134
137
  model=model,
135
138
  api_key=api_key,
136
139
  **common_params
137
140
  )
138
-
141
+
139
142
  elif provider == "deepseek":
140
143
  return ChatDeepSeek(
141
144
  model=model,
142
145
  api_key=api_key,
143
146
  **common_params
144
147
  )
145
-
148
+
146
149
  elif provider == "aws_bedrock":
147
150
  params = {
148
151
  "model": model,
@@ -157,7 +160,7 @@ def create_llm_from_profile(llm_profile):
157
160
  if 'aws_region' not in params:
158
161
  params["aws_region"] = "us-east-1"
159
162
  return ChatAWSBedrock(**params)
160
-
163
+
161
164
  elif provider == "anthropic_bedrock":
162
165
  params = {
163
166
  "model": model,
@@ -170,7 +173,7 @@ def create_llm_from_profile(llm_profile):
170
173
  if "region_name" in provider_config:
171
174
  params["region_name"] = provider_config["region_name"]
172
175
  return ChatAnthropicBedrock(**params)
173
-
176
+
174
177
  elif provider == "openai_compatible":
175
178
  if not base_url:
176
179
  raise ValueError("OpenAI Compatible provider requires base_url")
@@ -180,61 +183,63 @@ def create_llm_from_profile(llm_profile):
180
183
  base_url=base_url,
181
184
  **common_params
182
185
  )
183
-
186
+
184
187
  else:
185
188
  raise ValueError(f"Unsupported provider: {provider}")
186
-
189
+
187
190
  except Exception as e:
188
191
  logger.error(f"Failed to create LLM from profile: {e}")
189
192
  raise RuntimeError(f"Failed to create LLM from profile: {str(e)}")
190
193
 
194
+
191
195
  def validate_llm_configuration(provider: str, model: str, api_key: str, base_url: Optional[str] = None):
192
196
  """Validate LLM configuration parameters"""
193
197
  if not provider:
194
198
  raise ValueError("Provider is required")
195
-
199
+
196
200
  if not model:
197
201
  raise ValueError("Model is required")
198
-
202
+
199
203
  if not is_provider_supported(provider):
200
204
  raise ValueError(f"Unsupported provider: {provider}. Supported: {get_supported_providers()}")
201
-
205
+
202
206
  # Provider-specific validation
203
207
  from ..llm_config import get_provider_metadata
204
208
  metadata = get_provider_metadata(provider)
205
-
209
+
206
210
  if metadata.get("requires_api_key", True) and not api_key:
207
211
  raise ValueError(f"API key is required for provider: {provider}")
208
-
212
+
209
213
  if metadata.get("requires_base_url", False) and not base_url:
210
214
  raise ValueError(f"Base URL is required for provider: {provider}")
211
-
215
+
212
216
  return True
213
217
 
218
+
214
219
  def get_llm_creation_parameters(provider: str):
215
220
  """Get the required and optional parameters for creating an LLM instance"""
216
221
  from ..llm_config import get_provider_metadata
217
-
222
+
218
223
  if not is_provider_supported(provider):
219
224
  raise ValueError(f"Unsupported provider: {provider}")
220
-
225
+
221
226
  metadata = get_provider_metadata(provider)
222
-
227
+
223
228
  required_params = ["model"]
224
229
  optional_params = ["temperature", "max_tokens", "top_p", "frequency_penalty", "seed"]
225
-
230
+
226
231
  if metadata.get("requires_api_key", True):
227
232
  required_params.append("api_key")
228
-
233
+
229
234
  if metadata.get("requires_base_url", False):
230
235
  required_params.append("base_url")
231
236
  elif metadata.get("supports_base_url", False):
232
237
  optional_params.append("base_url")
233
-
238
+
234
239
  # Special cases for AWS Bedrock
235
240
  if provider in ["aws_bedrock", "anthropic_bedrock"]:
236
241
  required_params.extend(["aws_secret_access_key", "region_name"])
237
-
242
+
238
243
  return {
239
244
  "required": required_params,
240
245
  "optional": optional_params,
@@ -720,10 +720,6 @@ class AgentBrowserSession(BrowserSession):
720
720
  # Wait for reload
721
721
  await asyncio.sleep(1.0)
722
722
 
723
- # Note: We don't clear cached state here - let the next state fetch rebuild as needed
724
-
725
- # Navigation is handled by BrowserSession via events
726
-
727
723
  self.logger.info('🔄 Target refreshed')
728
724
  except Exception as e:
729
725
  raise
@@ -16,7 +16,9 @@ from vibe_surf.browser.agent_browser_session import AgentBrowserSession
16
16
  if TYPE_CHECKING:
17
17
  from browser_use.browser.session import BrowserSession
18
18
 
19
- logger = logging.getLogger(__name__)
19
+ from vibe_surf.logger import get_logger
20
+
21
+ logger = get_logger(__name__)
20
22
 
21
23
 
22
24
  class BrowserManager:
@@ -80,11 +82,17 @@ class BrowserManager:
80
82
 
81
83
  # Validate target assignment
82
84
  if target_id:
83
- target_id_owner = self.get_target_owner(target_id)
84
- if target_id_owner and target_id_owner != agent_id:
85
- logger.warning(
86
- f"Target id: {target_id} belongs to {target_id_owner}. You cannot assign it to {target_id_owner}.")
87
- return False
85
+ try:
86
+ target_id = await self.main_browser_session.get_target_id_from_tab_id(target_id)
87
+ except Exception:
88
+ logger.warning(f"Target ID '{target_id}' not found.")
89
+ target_id = None
90
+ if target_id:
91
+ target_id_owner = self.get_target_owner(target_id)
92
+ if target_id_owner and target_id_owner != agent_id:
93
+ logger.warning(
94
+ f"Target id: {target_id} belongs to {target_id_owner}. You cannot assign it to {target_id_owner}.")
95
+ return False
88
96
 
89
97
  # Get or create available target
90
98
  if target_id is None:
@@ -166,8 +174,6 @@ class BrowserManager:
166
174
  except Exception as e:
167
175
  logger.warning(f"Error during agent {agent_id} cleanup: {e}")
168
176
 
169
- # Note: We don't close the root browser session here as it's managed externally
170
-
171
177
  async def __aenter__(self) -> "BrowserManager":
172
178
  """Async context manager entry."""
173
179
  return self
@@ -22,7 +22,9 @@ import numpy as np
22
22
  from typing import Optional, Tuple, List, Any
23
23
  import io
24
24
 
25
- logger = logging.getLogger(__name__)
25
+ from vibe_surf.logger import get_logger
26
+
27
+ logger = get_logger(__name__)
26
28
 
27
29
 
28
30
  # List of common font file names (Prioritize preferred ones first)
@@ -601,7 +603,7 @@ def highlight_screenshot(screenshot_base64: str, elements: List[List[Any]]) -> s
601
603
 
602
604
  # --- Default if all corners overlap (Logic unchanged) ---
603
605
  if not found_non_overlapping_spot:
604
- logger.debug(f"Could not avoid label overlap for index {highlight_index}. Defaulting to top-left.")
606
+ # logger.debug(f"Could not avoid label overlap for index {highlight_index}. Defaulting to top-left.")
605
607
  chosen_label_bg_box, potential_text_ref_pos = calculate_label_placement(
606
608
  corner='top_left', # Default corner from original code
607
609
  outline_box=draw_box_outline,
@@ -618,7 +620,7 @@ def highlight_screenshot(screenshot_base64: str, elements: List[List[Any]]) -> s
618
620
  potential_text_ref_pos[0] + w_padding // 2,
619
621
  potential_text_ref_pos[1] + h_padding // 2) # ** OFFSET FROM ORIGINAL CODE **
620
622
  else:
621
- logger.debug(f"Default top-left placement failed for index {highlight_index}. Skipping label.")
623
+ # logger.debug(f"Default top-left placement failed for index {highlight_index}. Skipping label.")
622
624
  chosen_label_bg_box = None
623
625
  chosen_text_pos = None
624
626
 
@@ -72,51 +72,6 @@ class CustomDOMWatchdog(DOMWatchdog):
72
72
  self.logger.debug(f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Tabs info: {tabs_info}')
73
73
 
74
74
  try:
75
- # Fast path for empty pages
76
- # if not_a_meaningful_website:
77
- # self.logger.debug(f'⚡ Skipping BuildDOMTree for empty target: {page_url}')
78
- # self.logger.info(f'📸 Not taking screenshot for empty page: {page_url} (non-http/https URL)')
79
- #
80
- # # Create minimal DOM state
81
- # content = SerializedDOMState(_root=None, selector_map={})
82
- #
83
- # # Skip screenshot for empty pages
84
- # screenshot_b64 = None
85
- #
86
- # # Try to get page info from CDP, fall back to defaults if unavailable
87
- # try:
88
- # page_info = await self._get_page_info()
89
- # except Exception as e:
90
- # self.logger.debug(f'Failed to get page info from CDP for empty page: {e}, using fallback')
91
- # # Use default viewport dimensions
92
- # viewport = self.browser_session.browser_profile.viewport or {'width': 1280, 'height': 720}
93
- # page_info = PageInfo(
94
- # viewport_width=viewport['width'],
95
- # viewport_height=viewport['height'],
96
- # page_width=viewport['width'],
97
- # page_height=viewport['height'],
98
- # scroll_x=0,
99
- # scroll_y=0,
100
- # pixels_above=0,
101
- # pixels_below=0,
102
- # pixels_left=0,
103
- # pixels_right=0,
104
- # )
105
- #
106
- # return BrowserStateSummary(
107
- # dom_state=content,
108
- # url=page_url,
109
- # title='Empty Tab',
110
- # tabs=tabs_info,
111
- # screenshot=screenshot_b64,
112
- # page_info=page_info,
113
- # pixels_above=0,
114
- # pixels_below=0,
115
- # browser_errors=[],
116
- # is_pdf_viewer=False,
117
- # recent_events=self._get_recent_events_str() if include_recent_events else None,
118
- # )
119
-
120
75
  # Execute DOM building and screenshot capture in parallel
121
76
  dom_task = None
122
77
  screenshot_task = None
@@ -202,6 +202,10 @@ class VibeSurfBackground {
202
202
  result = await this.openFileSystem(message.data?.filePath);
203
203
  break;
204
204
 
205
+ case 'GET_ALL_TABS':
206
+ result = await this.getAllTabs();
207
+ break;
208
+
205
209
  default:
206
210
  console.warn('[VibeSurf] Unknown message type:', message.type);
207
211
  result = { error: 'Unknown message type' };
@@ -235,6 +235,10 @@ class VibeSurfAPIClient {
235
235
  return this.post('/tasks/stop', { reason });
236
236
  }
237
237
 
238
+ async addNewTask(newTask) {
239
+ return this.post('/tasks/add-new-task', { reason: newTask });
240
+ }
241
+
238
242
  // Activity APIs
239
243
  async getTaskInfo(taskId) {
240
244
  return this.get(`/activity/${taskId}`);
@@ -416,6 +420,15 @@ class VibeSurfAPIClient {
416
420
  return this.put('/config/controller', configData);
417
421
  }
418
422
 
423
+ // Browser APIs
424
+ async getActiveBrowserTab() {
425
+ return this.get('/browser/active-tab');
426
+ }
427
+
428
+ async getAllBrowserTabs() {
429
+ return this.get('/browser/all-tabs');
430
+ }
431
+
419
432
  // Utility methods
420
433
  delay(ms) {
421
434
  return new Promise(resolve => setTimeout(resolve, ms));