vibesurf 0.1.0__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 (70) hide show
  1. vibe_surf/__init__.py +12 -0
  2. vibe_surf/_version.py +34 -0
  3. vibe_surf/agents/__init__.py +0 -0
  4. vibe_surf/agents/browser_use_agent.py +1106 -0
  5. vibe_surf/agents/prompts/__init__.py +1 -0
  6. vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
  7. vibe_surf/agents/report_writer_agent.py +360 -0
  8. vibe_surf/agents/vibe_surf_agent.py +1632 -0
  9. vibe_surf/backend/__init__.py +0 -0
  10. vibe_surf/backend/api/__init__.py +3 -0
  11. vibe_surf/backend/api/activity.py +243 -0
  12. vibe_surf/backend/api/config.py +740 -0
  13. vibe_surf/backend/api/files.py +322 -0
  14. vibe_surf/backend/api/models.py +257 -0
  15. vibe_surf/backend/api/task.py +300 -0
  16. vibe_surf/backend/database/__init__.py +13 -0
  17. vibe_surf/backend/database/manager.py +129 -0
  18. vibe_surf/backend/database/models.py +164 -0
  19. vibe_surf/backend/database/queries.py +922 -0
  20. vibe_surf/backend/database/schemas.py +100 -0
  21. vibe_surf/backend/llm_config.py +182 -0
  22. vibe_surf/backend/main.py +137 -0
  23. vibe_surf/backend/migrations/__init__.py +16 -0
  24. vibe_surf/backend/migrations/init_db.py +303 -0
  25. vibe_surf/backend/migrations/seed_data.py +236 -0
  26. vibe_surf/backend/shared_state.py +601 -0
  27. vibe_surf/backend/utils/__init__.py +7 -0
  28. vibe_surf/backend/utils/encryption.py +164 -0
  29. vibe_surf/backend/utils/llm_factory.py +225 -0
  30. vibe_surf/browser/__init__.py +8 -0
  31. vibe_surf/browser/agen_browser_profile.py +130 -0
  32. vibe_surf/browser/agent_browser_session.py +416 -0
  33. vibe_surf/browser/browser_manager.py +296 -0
  34. vibe_surf/browser/utils.py +790 -0
  35. vibe_surf/browser/watchdogs/__init__.py +0 -0
  36. vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
  37. vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
  38. vibe_surf/chrome_extension/background.js +558 -0
  39. vibe_surf/chrome_extension/config.js +48 -0
  40. vibe_surf/chrome_extension/content.js +284 -0
  41. vibe_surf/chrome_extension/dev-reload.js +47 -0
  42. vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
  43. vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
  44. vibe_surf/chrome_extension/icons/logo.png +0 -0
  45. vibe_surf/chrome_extension/manifest.json +53 -0
  46. vibe_surf/chrome_extension/popup.html +134 -0
  47. vibe_surf/chrome_extension/scripts/api-client.js +473 -0
  48. vibe_surf/chrome_extension/scripts/main.js +491 -0
  49. vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
  50. vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
  51. vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
  52. vibe_surf/chrome_extension/sidepanel.html +347 -0
  53. vibe_surf/chrome_extension/styles/animations.css +471 -0
  54. vibe_surf/chrome_extension/styles/components.css +670 -0
  55. vibe_surf/chrome_extension/styles/main.css +2307 -0
  56. vibe_surf/chrome_extension/styles/settings.css +1100 -0
  57. vibe_surf/cli.py +357 -0
  58. vibe_surf/controller/__init__.py +0 -0
  59. vibe_surf/controller/file_system.py +53 -0
  60. vibe_surf/controller/mcp_client.py +68 -0
  61. vibe_surf/controller/vibesurf_controller.py +616 -0
  62. vibe_surf/controller/views.py +37 -0
  63. vibe_surf/llm/__init__.py +21 -0
  64. vibe_surf/llm/openai_compatible.py +237 -0
  65. vibesurf-0.1.0.dist-info/METADATA +97 -0
  66. vibesurf-0.1.0.dist-info/RECORD +70 -0
  67. vibesurf-0.1.0.dist-info/WHEEL +5 -0
  68. vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
  69. vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
  70. vibesurf-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,296 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import pdb
6
+ import threading
7
+ from typing import Dict, List, Optional, Set, TYPE_CHECKING
8
+
9
+ from browser_use.browser.session import CDPClient
10
+ from browser_use.browser.views import TabInfo
11
+ from cdp_use.cdp.target.types import TargetInfo
12
+ from bubus import EventBus
13
+
14
+ from vibe_surf.browser.agent_browser_session import AgentBrowserSession
15
+
16
+ if TYPE_CHECKING:
17
+ from browser_use.browser.session import BrowserSession
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class BrowserManager:
23
+ """Manages isolated browser sessions for multiple agents with enhanced security."""
24
+
25
+ def __init__(self, main_browser_session: BrowserSession):
26
+ self.main_browser_session = main_browser_session
27
+
28
+ # Store a list of sessions for each agent
29
+ self._agent_sessions: Dict[str, AgentBrowserSession] = {}
30
+
31
+ @property
32
+ def _root_cdp_client(self) -> Optional[CDPClient]:
33
+ """Get the root CDP client from the shared browser session."""
34
+ return getattr(self.main_browser_session, '_cdp_client_root', None)
35
+
36
+ async def _get_root_cdp_client(self) -> CDPClient:
37
+ """Get the shared root CDP client from browser session."""
38
+ if self._root_cdp_client is None:
39
+ # Ensure the browser session is connected
40
+ if not hasattr(self.main_browser_session,
41
+ '_cdp_client_root') or self.main_browser_session._cdp_client_root is None:
42
+ await self.main_browser_session.connect()
43
+ return self._root_cdp_client
44
+
45
+ async def register_agent(
46
+ self, agent_id: str, target_id: Optional[str] = None
47
+ ) -> AgentBrowserSession:
48
+ """
49
+ Register an agent and return its primary isolated browser session.
50
+ An agent can only be registered once.
51
+ """
52
+ if agent_id in self._agent_sessions:
53
+ logger.info(f"Agent {agent_id} is already registered.")
54
+ agent_session = self._agent_sessions[agent_id]
55
+ else:
56
+ agent_session = AgentBrowserSession(
57
+ id=agent_id,
58
+ cdp_url=self.main_browser_session.cdp_url,
59
+ browser_profile=self.main_browser_session.browser_profile,
60
+ main_browser_session=self.main_browser_session,
61
+ )
62
+ agent_session._cdp_client_root = await self._get_root_cdp_client()
63
+ logger.info(f"🚀 Starting agent session for {agent_id} to initialize watchdogs...")
64
+ await agent_session.start()
65
+
66
+ self._agent_sessions[agent_id] = agent_session
67
+ await self.assign_target_to_agent(agent_id, target_id)
68
+ return agent_session
69
+
70
+ async def assign_target_to_agent(
71
+ self, agent_id: str, target_id: Optional[str] = None
72
+ ) -> bool:
73
+ """Assign a target to an agent, creating a new session for it with security validation."""
74
+ # Validate agent exists
75
+ if agent_id not in self._agent_sessions:
76
+ logger.warning(f"Agent '{agent_id}' is not registered.")
77
+ return False
78
+
79
+ agent_session = self._agent_sessions[agent_id]
80
+
81
+ # Validate target assignment
82
+ 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
88
+
89
+ # Get or create available target
90
+ if target_id is None:
91
+ new_target = await self.main_browser_session.cdp_client.send.Target.createTarget(
92
+ params={'url': 'about:blank'})
93
+ target_id = new_target["targetId"]
94
+
95
+ await agent_session.connect_agent(target_id=target_id)
96
+ return True
97
+
98
+ async def unassign_target(self, target_id: str) -> bool:
99
+ """Assign a target to an agent, creating a new session for it with security validation."""
100
+ if not target_id:
101
+ logger.warning(f"Please provide valid target id: {target_id}")
102
+ return False
103
+ target_id_owner = self.get_target_owner(target_id)
104
+ if target_id_owner is None:
105
+ logger.warning(f"Target id: {target_id} does not belong to any agent.")
106
+ return False
107
+ agent_session = self._agent_sessions[target_id_owner]
108
+ target_cdp_session = agent_session.get_cdp_session_pool().pop(target_id, None)
109
+ if target_cdp_session is not None:
110
+ target_cdp_session.disconnect()
111
+ return True
112
+
113
+ async def unregister_agent(self, agent_id: str, close_tabs: bool = False):
114
+ """Clean up all resources for an agent with enhanced security cleanup."""
115
+ if agent_id not in self._agent_sessions:
116
+ logger.warning(f"Agent '{agent_id}' is not registered.")
117
+ return
118
+
119
+ agent_session = self._agent_sessions.pop(agent_id, None)
120
+ root_client = self.main_browser_session.cdp_client
121
+ if close_tabs:
122
+ for target_id in agent_session.get_cdp_session_pool():
123
+ try:
124
+ logger.info(f"Close target id: {target_id}")
125
+ await root_client.send.Target.closeTarget(params={'targetId': target_id})
126
+ except Exception as e:
127
+ # Log error if closing tab fails, but continue cleanup
128
+ logger.warning(f"Error closing target {target_id}: {e}")
129
+
130
+ # Disconnect the agent's CDP session regardless
131
+ await agent_session.disconnect_agent()
132
+ await agent_session.stop()
133
+
134
+ def get_agent_sessions(self, agent_id: str) -> Optional[AgentBrowserSession]:
135
+ """Get all sessions (pages) for an agent."""
136
+ return self._agent_sessions.get(agent_id, None)
137
+
138
+ def get_active_agents(self) -> List[str]:
139
+ """List all active agent IDs."""
140
+ return list(self._agent_sessions.keys())
141
+
142
+ def get_agent_target_ids(self, agent_id: str) -> List[str]:
143
+ """Get all target IDs assigned to a specific agent."""
144
+ agent_session = self.get_agent_sessions(agent_id)
145
+ if agent_session is None:
146
+ return []
147
+ else:
148
+ return list(agent_session.get_cdp_session_pool().keys())
149
+
150
+ def get_target_owner(self, target_id: str) -> Optional[str]:
151
+ """Get the agent ID that owns a specific target."""
152
+ for agent_id in self._agent_sessions:
153
+ agent_target_ids = self.get_agent_target_ids(agent_id)
154
+ if target_id in agent_target_ids:
155
+ return agent_id
156
+ return None
157
+
158
+ async def close(self) -> None:
159
+ """Close all agent sessions but preserve the shared browser session."""
160
+ # Unregister all agents first
161
+ agent_ids = list(self._agent_sessions.keys())
162
+ for agent_id in agent_ids:
163
+ try:
164
+ await self.unregister_agent(agent_id, True)
165
+ await asyncio.sleep(1)
166
+ except Exception as e:
167
+ logger.warning(f"Error during agent {agent_id} cleanup: {e}")
168
+
169
+ # Note: We don't close the root browser session here as it's managed externally
170
+
171
+ async def __aenter__(self) -> "BrowserManager":
172
+ """Async context manager entry."""
173
+ return self
174
+
175
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
176
+ """Async context manager exit."""
177
+ await self.close()
178
+
179
+ async def _is_target_focused(self, target_id: str) -> bool:
180
+ """Check if a given target has focus using multiple detection methods."""
181
+ client = self.main_browser_session.cdp_client
182
+ session_id = None
183
+
184
+ try:
185
+ # Use document.visibilityState and document.hasFocus()
186
+ attach_result = await client.send.Target.attachToTarget(
187
+ params={"targetId": target_id, "flatten": True}
188
+ )
189
+ session_id = attach_result["sessionId"]
190
+
191
+ # Check both visibility and focus
192
+ combined_script = """
193
+ ({
194
+ hasFocus: document.hasFocus(),
195
+ visibilityState: document.visibilityState,
196
+ hidden: document.hidden,
197
+ activeElement: document.activeElement ? document.activeElement.tagName : null,
198
+ timestamp: Date.now()
199
+ })
200
+ """
201
+
202
+ eval_result = await client.send.Runtime.evaluate(
203
+ params={
204
+ "expression": combined_script,
205
+ "returnByValue": True
206
+ },
207
+ session_id=session_id
208
+ )
209
+
210
+ # Detach immediately after checking
211
+ await client.send.Target.detachFromTarget(
212
+ params={"sessionId": session_id}
213
+ )
214
+ session_id = None
215
+
216
+ if "result" in eval_result and "value" in eval_result["result"]:
217
+ focus_data = eval_result["result"]["value"]
218
+ has_focus = focus_data.get("hasFocus", False)
219
+ visibility_state = focus_data.get("visibilityState", "")
220
+ is_hidden = focus_data.get("hidden", True)
221
+
222
+ # A target is considered focused if:
223
+ # 1. Document has focus OR
224
+ # 2. Document is visible (not hidden)
225
+ is_focused = has_focus or (visibility_state == "visible" and not is_hidden)
226
+ return is_focused
227
+ else:
228
+ return False
229
+
230
+ except Exception:
231
+ if session_id:
232
+ try:
233
+ await client.send.Target.detachFromTarget(
234
+ params={"sessionId": session_id}
235
+ )
236
+ except Exception:
237
+ pass # Ignore cleanup errors
238
+ return False
239
+
240
+ async def _get_active_target(self) -> str:
241
+ """Get current focused target, or an available target, or create a new one."""
242
+ client = self.main_browser_session.cdp_client
243
+ targets_info = await client.send.Target.getTargets()
244
+ page_targets = [t for t in targets_info["targetInfos"] if t["type"] == "page"]
245
+
246
+ # 1. Check for a focused page among ALL pages (not just unassigned)
247
+ for target in page_targets:
248
+ target_id = target["targetId"]
249
+ try:
250
+ is_focused = await self._is_target_focused(target_id)
251
+ if is_focused:
252
+ return target_id
253
+ except Exception as e:
254
+ continue # Skip invalid targets
255
+
256
+ # 2. If no pages are available, create a new one
257
+ if page_targets:
258
+ target_id = page_targets[-1]["targetId"]
259
+ else:
260
+ new_target = await client.send.Target.createTarget(params={'url': 'about:blank'})
261
+ target_id = new_target["targetId"]
262
+ await self.main_browser_session.get_or_create_cdp_session(target_id, focus=False)
263
+ return target_id
264
+
265
+ async def _get_activate_tab_info(self) -> Optional[TabInfo]:
266
+ """Get tab information for the currently active target."""
267
+ try:
268
+ # Get the active target ID
269
+ active_target_id = await self._get_active_target()
270
+
271
+ # Get target information from CDP
272
+ client = self.main_browser_session.cdp_client
273
+ targets_info = await client.send.Target.getTargets()
274
+
275
+ # Find the active target in the targets list
276
+ for target in targets_info["targetInfos"]:
277
+ if target["targetId"] == active_target_id and target["type"] == "page":
278
+ # Get additional target info for title if needed
279
+ try:
280
+ target_info = await client.send.Target.getTargetInfo(
281
+ params={'targetId': active_target_id}
282
+ )
283
+ target_details = target_info.get('targetInfo', target)
284
+ except Exception:
285
+ target_details = target
286
+
287
+ # Create TabInfo object
288
+ return TabInfo(
289
+ url=target_details.get('url', ''),
290
+ title=target_details.get('title', ''),
291
+ target_id=active_target_id
292
+ )
293
+
294
+ return None
295
+ except Exception:
296
+ return None