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.
- vibe_surf/__init__.py +12 -0
- vibe_surf/_version.py +34 -0
- vibe_surf/agents/__init__.py +0 -0
- vibe_surf/agents/browser_use_agent.py +1106 -0
- vibe_surf/agents/prompts/__init__.py +1 -0
- vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
- vibe_surf/agents/report_writer_agent.py +360 -0
- vibe_surf/agents/vibe_surf_agent.py +1632 -0
- vibe_surf/backend/__init__.py +0 -0
- vibe_surf/backend/api/__init__.py +3 -0
- vibe_surf/backend/api/activity.py +243 -0
- vibe_surf/backend/api/config.py +740 -0
- vibe_surf/backend/api/files.py +322 -0
- vibe_surf/backend/api/models.py +257 -0
- vibe_surf/backend/api/task.py +300 -0
- vibe_surf/backend/database/__init__.py +13 -0
- vibe_surf/backend/database/manager.py +129 -0
- vibe_surf/backend/database/models.py +164 -0
- vibe_surf/backend/database/queries.py +922 -0
- vibe_surf/backend/database/schemas.py +100 -0
- vibe_surf/backend/llm_config.py +182 -0
- vibe_surf/backend/main.py +137 -0
- vibe_surf/backend/migrations/__init__.py +16 -0
- vibe_surf/backend/migrations/init_db.py +303 -0
- vibe_surf/backend/migrations/seed_data.py +236 -0
- vibe_surf/backend/shared_state.py +601 -0
- vibe_surf/backend/utils/__init__.py +7 -0
- vibe_surf/backend/utils/encryption.py +164 -0
- vibe_surf/backend/utils/llm_factory.py +225 -0
- vibe_surf/browser/__init__.py +8 -0
- vibe_surf/browser/agen_browser_profile.py +130 -0
- vibe_surf/browser/agent_browser_session.py +416 -0
- vibe_surf/browser/browser_manager.py +296 -0
- vibe_surf/browser/utils.py +790 -0
- vibe_surf/browser/watchdogs/__init__.py +0 -0
- vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
- vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
- vibe_surf/chrome_extension/background.js +558 -0
- vibe_surf/chrome_extension/config.js +48 -0
- vibe_surf/chrome_extension/content.js +284 -0
- vibe_surf/chrome_extension/dev-reload.js +47 -0
- vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
- vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
- vibe_surf/chrome_extension/icons/logo.png +0 -0
- vibe_surf/chrome_extension/manifest.json +53 -0
- vibe_surf/chrome_extension/popup.html +134 -0
- vibe_surf/chrome_extension/scripts/api-client.js +473 -0
- vibe_surf/chrome_extension/scripts/main.js +491 -0
- vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
- vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
- vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
- vibe_surf/chrome_extension/sidepanel.html +347 -0
- vibe_surf/chrome_extension/styles/animations.css +471 -0
- vibe_surf/chrome_extension/styles/components.css +670 -0
- vibe_surf/chrome_extension/styles/main.css +2307 -0
- vibe_surf/chrome_extension/styles/settings.css +1100 -0
- vibe_surf/cli.py +357 -0
- vibe_surf/controller/__init__.py +0 -0
- vibe_surf/controller/file_system.py +53 -0
- vibe_surf/controller/mcp_client.py +68 -0
- vibe_surf/controller/vibesurf_controller.py +616 -0
- vibe_surf/controller/views.py +37 -0
- vibe_surf/llm/__init__.py +21 -0
- vibe_surf/llm/openai_compatible.py +237 -0
- vibesurf-0.1.0.dist-info/METADATA +97 -0
- vibesurf-0.1.0.dist-info/RECORD +70 -0
- vibesurf-0.1.0.dist-info/WHEEL +5 -0
- vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
- vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
- 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
|