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,601 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared State Module
|
|
3
|
+
|
|
4
|
+
Contains global state variables shared between main.py and routers
|
|
5
|
+
to avoid circular import issues.
|
|
6
|
+
"""
|
|
7
|
+
import pdb
|
|
8
|
+
from typing import Optional, Dict, Any, List
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import json
|
|
13
|
+
import platform
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# VibeSurf components
|
|
17
|
+
from vibe_surf.agents.vibe_surf_agent import VibeSurfAgent
|
|
18
|
+
from vibe_surf.controller.vibesurf_controller import VibeSurfController
|
|
19
|
+
from vibe_surf.browser.browser_manager import BrowserManager
|
|
20
|
+
from browser_use.llm.base import BaseChatModel
|
|
21
|
+
from browser_use.llm.openai.chat import ChatOpenAI
|
|
22
|
+
from browser_use.browser import BrowserProfile
|
|
23
|
+
from vibe_surf.llm.openai_compatible import ChatOpenAICompatible
|
|
24
|
+
from vibe_surf.browser.agent_browser_session import AgentBrowserSession
|
|
25
|
+
from vibe_surf.browser.agen_browser_profile import AgentBrowserProfile
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# Global VibeSurf components
|
|
30
|
+
vibesurf_agent: Optional[VibeSurfAgent] = None
|
|
31
|
+
browser_manager: Optional[BrowserManager] = None
|
|
32
|
+
controller: Optional[VibeSurfController] = None
|
|
33
|
+
llm: Optional[BaseChatModel] = None
|
|
34
|
+
db_manager: Optional['DatabaseManager'] = None
|
|
35
|
+
|
|
36
|
+
# Environment variables
|
|
37
|
+
workspace_dir: str = ""
|
|
38
|
+
browser_execution_path: str = ""
|
|
39
|
+
browser_user_data: str = ""
|
|
40
|
+
|
|
41
|
+
# Global environment variables dictionary
|
|
42
|
+
envs: Dict[str, str] = {}
|
|
43
|
+
|
|
44
|
+
# MCP server management
|
|
45
|
+
active_mcp_server: Dict[str, str] = {} # Dict[mcp_id: mcp_server_name]
|
|
46
|
+
|
|
47
|
+
# Single task execution tracking
|
|
48
|
+
active_task: Optional[Dict[str, Any]] = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_all_components():
|
|
52
|
+
"""Get all components as a dictionary"""
|
|
53
|
+
return {
|
|
54
|
+
"vibesurf_agent": vibesurf_agent,
|
|
55
|
+
"browser_manager": browser_manager,
|
|
56
|
+
"controller": controller,
|
|
57
|
+
"llm": llm,
|
|
58
|
+
"db_manager": db_manager,
|
|
59
|
+
"workspace_dir": workspace_dir,
|
|
60
|
+
"browser_execution_path": browser_execution_path,
|
|
61
|
+
"browser_user_data": browser_user_data,
|
|
62
|
+
"active_mcp_server": active_mcp_server,
|
|
63
|
+
"active_task": active_task,
|
|
64
|
+
"envs": envs
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def set_components(**kwargs):
|
|
69
|
+
"""Update global components"""
|
|
70
|
+
global vibesurf_agent, browser_manager, controller, llm, db_manager
|
|
71
|
+
global workspace_dir, browser_execution_path, browser_user_data, active_mcp_server, envs
|
|
72
|
+
|
|
73
|
+
if "vibesurf_agent" in kwargs:
|
|
74
|
+
vibesurf_agent = kwargs["vibesurf_agent"]
|
|
75
|
+
if "browser_manager" in kwargs:
|
|
76
|
+
browser_manager = kwargs["browser_manager"]
|
|
77
|
+
if "controller" in kwargs:
|
|
78
|
+
controller = kwargs["controller"]
|
|
79
|
+
if "llm" in kwargs:
|
|
80
|
+
llm = kwargs["llm"]
|
|
81
|
+
if "db_manager" in kwargs:
|
|
82
|
+
db_manager = kwargs["db_manager"]
|
|
83
|
+
if "workspace_dir" in kwargs:
|
|
84
|
+
workspace_dir = kwargs["workspace_dir"]
|
|
85
|
+
if "browser_execution_path" in kwargs:
|
|
86
|
+
browser_execution_path = kwargs["browser_execution_path"]
|
|
87
|
+
if "browser_user_data" in kwargs:
|
|
88
|
+
browser_user_data = kwargs["browser_user_data"]
|
|
89
|
+
if "active_mcp_server" in kwargs:
|
|
90
|
+
active_mcp_server = kwargs["active_mcp_server"]
|
|
91
|
+
if "envs" in kwargs:
|
|
92
|
+
envs = kwargs["envs"]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def execute_task_background(
|
|
96
|
+
task_id: str,
|
|
97
|
+
session_id: str,
|
|
98
|
+
task: str,
|
|
99
|
+
llm_profile_name: str,
|
|
100
|
+
upload_files: Optional[List[str]] = None,
|
|
101
|
+
db_session=None
|
|
102
|
+
):
|
|
103
|
+
"""Background task execution function for single task with LLM profile support"""
|
|
104
|
+
global vibesurf_agent, active_task
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
# Check if MCP server configuration needs update
|
|
108
|
+
await _check_and_update_mcp_servers(db_session)
|
|
109
|
+
|
|
110
|
+
# Update active task status to running
|
|
111
|
+
active_task = {
|
|
112
|
+
"task_id": task_id,
|
|
113
|
+
"status": "running",
|
|
114
|
+
"session_id": session_id,
|
|
115
|
+
"task": task,
|
|
116
|
+
"llm_profile_name": llm_profile_name,
|
|
117
|
+
"workspace_dir": workspace_dir,
|
|
118
|
+
"upload_files": upload_files or [],
|
|
119
|
+
"active_mcp_servers": list(active_mcp_server.values()), # List of MCP server names
|
|
120
|
+
"start_time": datetime.now(),
|
|
121
|
+
"agent_id": task_id # Use task_id as agent_id for tracking
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
logger.info(f"Task {task_id} started for session {session_id} with profile {llm_profile_name}")
|
|
125
|
+
|
|
126
|
+
# Ensure correct workspace directory is set for this task
|
|
127
|
+
if vibesurf_agent:
|
|
128
|
+
vibesurf_agent.workspace_dir = workspace_dir
|
|
129
|
+
|
|
130
|
+
# Execute the task
|
|
131
|
+
result = await vibesurf_agent.run(
|
|
132
|
+
task=task,
|
|
133
|
+
upload_files=upload_files,
|
|
134
|
+
session_id=session_id
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Update task status to completed
|
|
138
|
+
if active_task and active_task.get("status") != "stopped":
|
|
139
|
+
active_task.update({
|
|
140
|
+
"status": "completed",
|
|
141
|
+
"result": result,
|
|
142
|
+
"end_time": datetime.now()
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
# Get session directory for report path
|
|
146
|
+
session_dir = os.path.join(workspace_dir, session_id)
|
|
147
|
+
report_path = None
|
|
148
|
+
|
|
149
|
+
# Look for generated report
|
|
150
|
+
reports_dir = os.path.join(session_dir, "reports")
|
|
151
|
+
if os.path.exists(reports_dir):
|
|
152
|
+
for file in os.listdir(reports_dir):
|
|
153
|
+
if file.endswith('.html'):
|
|
154
|
+
report_path = os.path.join(reports_dir, file)
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
# Save task to database
|
|
158
|
+
if db_session:
|
|
159
|
+
try:
|
|
160
|
+
from .database.queries import TaskQueries
|
|
161
|
+
await TaskQueries.update_task_completion(
|
|
162
|
+
db_session,
|
|
163
|
+
task_id=task_id,
|
|
164
|
+
task_result=result,
|
|
165
|
+
task_status=active_task.get("status", "completed"),
|
|
166
|
+
report_path=report_path
|
|
167
|
+
)
|
|
168
|
+
await db_session.commit()
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"Failed to update task in database: {e}")
|
|
171
|
+
|
|
172
|
+
logger.info(f"Task {task_id} completed for session {session_id}")
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"Task execution failed: {e}")
|
|
176
|
+
# Update task status to failed
|
|
177
|
+
if active_task and active_task.get("task_id") == task_id:
|
|
178
|
+
active_task.update({
|
|
179
|
+
"status": "failed",
|
|
180
|
+
"error": str(e),
|
|
181
|
+
"end_time": datetime.now()
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
# Save failed task to database
|
|
185
|
+
if db_session:
|
|
186
|
+
try:
|
|
187
|
+
from .database.queries import TaskQueries
|
|
188
|
+
await TaskQueries.update_task_completion(
|
|
189
|
+
db_session,
|
|
190
|
+
task_id=task_id,
|
|
191
|
+
task_result=None,
|
|
192
|
+
task_status="failed",
|
|
193
|
+
error_message=str(e)
|
|
194
|
+
)
|
|
195
|
+
await db_session.commit()
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.error(f"Failed to save failed task to database: {e}")
|
|
198
|
+
|
|
199
|
+
logger.error(f"Task {task_id} failed for session {session_id}: {e}")
|
|
200
|
+
finally:
|
|
201
|
+
# Clear active task when execution is complete (success or failure)
|
|
202
|
+
active_task = None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def is_task_running() -> bool:
|
|
206
|
+
"""Quick check if any task is currently running"""
|
|
207
|
+
global active_task
|
|
208
|
+
return active_task is not None and active_task.get("status") not in ["failed",
|
|
209
|
+
"completed",
|
|
210
|
+
"stopped"]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def get_active_task_info() -> Optional[Dict[str, Any]]:
|
|
214
|
+
"""Get current active task information"""
|
|
215
|
+
global active_task
|
|
216
|
+
return active_task.copy() if active_task else None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def clear_active_task():
|
|
220
|
+
"""Clear the active task (used when stopping)"""
|
|
221
|
+
global active_task
|
|
222
|
+
active_task = None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
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
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
if not db_session:
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
from .database.queries import McpProfileQueries
|
|
234
|
+
|
|
235
|
+
# Get current active MCP servers from database
|
|
236
|
+
active_profiles = await McpProfileQueries.get_active_profiles(db_session)
|
|
237
|
+
current_active_servers = {profile.mcp_id: profile.mcp_server_name for profile in active_profiles}
|
|
238
|
+
|
|
239
|
+
# Compare with shared state
|
|
240
|
+
if current_active_servers != active_mcp_server:
|
|
241
|
+
logger.info(f"MCP server configuration changed. Updating controller...")
|
|
242
|
+
logger.info(f"Old config: {active_mcp_server}")
|
|
243
|
+
logger.info(f"New config: {current_active_servers}")
|
|
244
|
+
|
|
245
|
+
# Update shared state
|
|
246
|
+
active_mcp_server = current_active_servers.copy()
|
|
247
|
+
|
|
248
|
+
# Create new MCP server config for controller
|
|
249
|
+
mcp_server_config = await _build_mcp_server_config(active_profiles)
|
|
250
|
+
|
|
251
|
+
# 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
|
+
logger.info("โ
Controller MCP configuration updated successfully")
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error(f"Failed to check and update MCP servers: {e}")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
async def _build_mcp_server_config(active_profiles) -> Dict[str, Any]:
|
|
263
|
+
"""Build MCP server configuration from active profiles"""
|
|
264
|
+
mcp_server_config = {
|
|
265
|
+
"mcpServers": {}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
for profile in active_profiles:
|
|
269
|
+
mcp_server_config["mcpServers"][profile.mcp_server_name] = json.loads(profile.mcp_server_params)
|
|
270
|
+
|
|
271
|
+
return mcp_server_config
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
async def _load_active_mcp_servers():
|
|
275
|
+
"""Load active MCP servers from database and return config"""
|
|
276
|
+
global db_manager, active_mcp_server
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
if not db_manager:
|
|
280
|
+
return {}
|
|
281
|
+
|
|
282
|
+
from .database.queries import McpProfileQueries
|
|
283
|
+
|
|
284
|
+
async for db in db_manager.get_session():
|
|
285
|
+
try:
|
|
286
|
+
# Get all active MCP profiles
|
|
287
|
+
active_profiles = await McpProfileQueries.get_active_profiles(db)
|
|
288
|
+
|
|
289
|
+
# Update shared state
|
|
290
|
+
active_mcp_server = {profile.mcp_id: profile.mcp_server_name for profile in active_profiles}
|
|
291
|
+
|
|
292
|
+
# Build MCP server config
|
|
293
|
+
mcp_server_config = await _build_mcp_server_config(active_profiles)
|
|
294
|
+
|
|
295
|
+
logger.info(f"โ
Loaded {len(active_profiles)} active MCP servers: {list(active_mcp_server.values())}")
|
|
296
|
+
|
|
297
|
+
return mcp_server_config
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.warning(f"Failed to load MCP servers from database: {e}")
|
|
301
|
+
return {}
|
|
302
|
+
finally:
|
|
303
|
+
break
|
|
304
|
+
|
|
305
|
+
except Exception as e:
|
|
306
|
+
logger.warning(f"Database not available for MCP server loading: {e}")
|
|
307
|
+
return {}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
async def initialize_vibesurf_components():
|
|
311
|
+
"""Initialize VibeSurf components from environment variables and default LLM profile"""
|
|
312
|
+
global vibesurf_agent, browser_manager, controller, llm, db_manager
|
|
313
|
+
global workspace_dir, browser_execution_path, browser_user_data, envs
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
# Load environment variables
|
|
317
|
+
env_workspace_dir = os.getenv("VIBESURF_WORKSPACE", "")
|
|
318
|
+
if not env_workspace_dir or not env_workspace_dir.strip():
|
|
319
|
+
# Set default workspace directory based on OS
|
|
320
|
+
if platform.system() == "Windows":
|
|
321
|
+
default_workspace = os.path.join(os.environ.get("APPDATA", ""), "VibeSurf")
|
|
322
|
+
elif platform.system() == "Darwin": # macOS
|
|
323
|
+
default_workspace = os.path.join(os.path.expanduser("~"), "Library", "Application Support", "VibeSurf")
|
|
324
|
+
else: # Linux and others
|
|
325
|
+
default_workspace = os.path.join(os.path.expanduser("~"), ".vibesurf")
|
|
326
|
+
workspace_dir = default_workspace
|
|
327
|
+
else:
|
|
328
|
+
workspace_dir = env_workspace_dir
|
|
329
|
+
workspace_dir = os.path.abspath(workspace_dir)
|
|
330
|
+
os.makedirs(workspace_dir, exist_ok=True)
|
|
331
|
+
logger.info("WorkSpace directory: {}".format(workspace_dir))
|
|
332
|
+
|
|
333
|
+
# Load environment configuration from envs.json
|
|
334
|
+
envs_file_path = os.path.join(workspace_dir, "envs.json")
|
|
335
|
+
try:
|
|
336
|
+
if os.path.exists(envs_file_path):
|
|
337
|
+
with open(envs_file_path, 'r', encoding='utf-8') as f:
|
|
338
|
+
envs = json.load(f)
|
|
339
|
+
logger.info(f"โ
Loaded environment configuration from {envs_file_path}")
|
|
340
|
+
|
|
341
|
+
# Set loaded environment variables to system environment
|
|
342
|
+
for key, value in envs.items():
|
|
343
|
+
if value: # Only set non-empty values
|
|
344
|
+
os.environ[key] = value
|
|
345
|
+
logger.info(f"๐ง Set environment variable: {key}")
|
|
346
|
+
else:
|
|
347
|
+
envs = {}
|
|
348
|
+
logger.info("๐ No existing envs.json found, initializing empty environment configuration")
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.warning(f"Failed to load envs.json: {e}, initializing empty environment configuration")
|
|
351
|
+
envs = {}
|
|
352
|
+
browser_execution_path = os.getenv("BROWSER_EXECUTION_PATH", "")
|
|
353
|
+
assert os.path.exists(browser_execution_path), "Please set the BROWSER_EXECUTION_PATH environment variable"
|
|
354
|
+
browser_user_data = os.getenv("BROWSER_USER_DATA", "")
|
|
355
|
+
if not browser_user_data:
|
|
356
|
+
browser_user_data = os.path.join(workspace_dir, "browser_user_data",
|
|
357
|
+
f"{os.path.basename(browser_execution_path)}-profile")
|
|
358
|
+
|
|
359
|
+
# Get VibeSurf extension path
|
|
360
|
+
vibesurf_extension = os.getenv("VIBESURF_EXTENSION", "")
|
|
361
|
+
if not vibesurf_extension.strip():
|
|
362
|
+
current_file = Path(__file__)
|
|
363
|
+
project_root = current_file.parent.parent.absolute()
|
|
364
|
+
vibesurf_extension = str(project_root / "chrome_extension")
|
|
365
|
+
assert os.path.exists(vibesurf_extension)
|
|
366
|
+
|
|
367
|
+
# Get backend URL
|
|
368
|
+
backend_port = os.getenv("VIBESURF_BACKEND_PORT", "9335")
|
|
369
|
+
if not backend_port or not backend_port.strip():
|
|
370
|
+
backend_port = "9335"
|
|
371
|
+
backend_port = int(backend_port)
|
|
372
|
+
|
|
373
|
+
backend_url = f'http://127.0.0.1:{backend_port}'
|
|
374
|
+
|
|
375
|
+
# Update envs dictionary with current environment variables
|
|
376
|
+
envs.update({
|
|
377
|
+
"BROWSER_EXECUTION_PATH": browser_execution_path,
|
|
378
|
+
"BROWSER_USER_DATA": browser_user_data,
|
|
379
|
+
"VIBESURF_EXTENSION": vibesurf_extension,
|
|
380
|
+
"VIBESURF_BACKEND_URL": backend_url
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
# Create directories if they don't exist
|
|
384
|
+
os.makedirs(workspace_dir, exist_ok=True)
|
|
385
|
+
|
|
386
|
+
# Initialize database manager after workspace_dir is set
|
|
387
|
+
from .database.manager import DatabaseManager
|
|
388
|
+
|
|
389
|
+
# Debug: Check environment variable value
|
|
390
|
+
env_database_url = os.getenv('VIBESURF_DATABASE_URL')
|
|
391
|
+
logger.info(f"๐ VIBESURF_DATABASE_URL environment variable: '{env_database_url}'")
|
|
392
|
+
logger.info(f"๐ workspace_dir: '{workspace_dir}'")
|
|
393
|
+
|
|
394
|
+
# Handle empty string environment variable properly
|
|
395
|
+
if env_database_url and env_database_url.strip():
|
|
396
|
+
database_url = env_database_url
|
|
397
|
+
else:
|
|
398
|
+
database_url = f'sqlite+aiosqlite:///{os.path.join(workspace_dir, "vibe_surf.db")}'
|
|
399
|
+
|
|
400
|
+
logger.info(f"๐ Final database_url: '{database_url}'")
|
|
401
|
+
|
|
402
|
+
db_manager = DatabaseManager(database_url)
|
|
403
|
+
|
|
404
|
+
# Initialize database tables
|
|
405
|
+
await db_manager.create_tables()
|
|
406
|
+
logger.info("โ
Database manager initialized successfully")
|
|
407
|
+
|
|
408
|
+
# Initialize LLM from default profile (if available) or fallback to environment variables
|
|
409
|
+
llm = await _initialize_default_llm()
|
|
410
|
+
|
|
411
|
+
# Initialize browser manager
|
|
412
|
+
if browser_manager:
|
|
413
|
+
main_browser_session = browser_manager.main_browser_session
|
|
414
|
+
else:
|
|
415
|
+
from screeninfo import get_monitors
|
|
416
|
+
primary_monitor = get_monitors()[0]
|
|
417
|
+
_update_extension_backend_url(envs["VIBESURF_EXTENSION"], backend_url)
|
|
418
|
+
|
|
419
|
+
browser_profile = AgentBrowserProfile(
|
|
420
|
+
executable_path=browser_execution_path,
|
|
421
|
+
user_data_dir=browser_user_data,
|
|
422
|
+
headless=False,
|
|
423
|
+
keep_alive=True,
|
|
424
|
+
custom_extensions=[envs["VIBESURF_EXTENSION"]],
|
|
425
|
+
window_size={"width": primary_monitor.width, "height": primary_monitor.height}
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Initialize components
|
|
429
|
+
main_browser_session = AgentBrowserSession(browser_profile=browser_profile)
|
|
430
|
+
await main_browser_session.start()
|
|
431
|
+
browser_manager = BrowserManager(
|
|
432
|
+
main_browser_session=main_browser_session
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# Load active MCP servers from database
|
|
436
|
+
mcp_server_config = await _load_active_mcp_servers()
|
|
437
|
+
|
|
438
|
+
# Initialize vibesurf controller with MCP server config
|
|
439
|
+
controller = VibeSurfController(mcp_server_config=mcp_server_config)
|
|
440
|
+
|
|
441
|
+
# Register MCP clients if there are any active MCP servers
|
|
442
|
+
if mcp_server_config and mcp_server_config.get("mcpServers"):
|
|
443
|
+
await controller.register_mcp_clients()
|
|
444
|
+
logger.info(f"โ
Registered {len(mcp_server_config['mcpServers'])} MCP servers")
|
|
445
|
+
|
|
446
|
+
# Initialize VibeSurfAgent
|
|
447
|
+
vibesurf_agent = VibeSurfAgent(
|
|
448
|
+
llm=llm,
|
|
449
|
+
browser_manager=browser_manager,
|
|
450
|
+
controller=controller,
|
|
451
|
+
workspace_dir=workspace_dir
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# Save environment configuration to envs.json
|
|
455
|
+
try:
|
|
456
|
+
with open(envs_file_path, 'w', encoding='utf-8') as f:
|
|
457
|
+
json.dump(envs, f, indent=2, ensure_ascii=False)
|
|
458
|
+
logger.info(f"โ
Saved environment configuration to {envs_file_path}")
|
|
459
|
+
except Exception as e:
|
|
460
|
+
logger.warning(f"Failed to save envs.json: {e}")
|
|
461
|
+
|
|
462
|
+
logger.info("โ
VibeSurf components initialized successfully")
|
|
463
|
+
|
|
464
|
+
except Exception as e:
|
|
465
|
+
logger.error(f"โ Failed to initialize VibeSurf components: {e}")
|
|
466
|
+
raise
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
async def _initialize_default_llm():
|
|
470
|
+
"""Initialize LLM from default profile or fallback to environment variables"""
|
|
471
|
+
global db_manager
|
|
472
|
+
|
|
473
|
+
try:
|
|
474
|
+
# Try to get default LLM profile from database
|
|
475
|
+
from .database.queries import LLMProfileQueries
|
|
476
|
+
from .utils.llm_factory import create_llm_from_profile
|
|
477
|
+
|
|
478
|
+
# Get database session from shared state db_manager
|
|
479
|
+
if db_manager:
|
|
480
|
+
async for db in db_manager.get_session():
|
|
481
|
+
try:
|
|
482
|
+
default_profile = await LLMProfileQueries.get_default_profile(db)
|
|
483
|
+
if default_profile:
|
|
484
|
+
# Get profile with decrypted API key
|
|
485
|
+
profile_with_key = await LLMProfileQueries.get_profile_with_decrypted_key(
|
|
486
|
+
db, default_profile.profile_name
|
|
487
|
+
)
|
|
488
|
+
if profile_with_key:
|
|
489
|
+
llm_instance = create_llm_from_profile(profile_with_key)
|
|
490
|
+
logger.info(f"โ
LLM initialized from default profile: {default_profile.profile_name}")
|
|
491
|
+
return llm_instance
|
|
492
|
+
break
|
|
493
|
+
except Exception as e:
|
|
494
|
+
logger.warning(f"Failed to load default LLM profile: {e}")
|
|
495
|
+
break
|
|
496
|
+
except Exception as e:
|
|
497
|
+
logger.warning(f"Database not available for LLM profile loading: {e}")
|
|
498
|
+
|
|
499
|
+
# Fallback to environment variables
|
|
500
|
+
logger.info("๐ Falling back to environment variable LLM configuration")
|
|
501
|
+
return ChatOpenAI(
|
|
502
|
+
model=os.getenv("LLM_MODEL", "gpt-4.1-mini"),
|
|
503
|
+
base_url=os.getenv("OPENAI_ENDPOINT", "https://api.openai.com/v1"),
|
|
504
|
+
api_key=os.getenv("OPENAI_API_KEY", "")
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
async def update_llm_from_profile(profile_name: str):
|
|
509
|
+
"""Update the global LLM instance from a specific profile"""
|
|
510
|
+
global vibesurf_agent, llm, db_manager
|
|
511
|
+
|
|
512
|
+
try:
|
|
513
|
+
from .database.queries import LLMProfileQueries
|
|
514
|
+
from .utils.llm_factory import create_llm_from_profile
|
|
515
|
+
|
|
516
|
+
# Get database session from shared state db_manager
|
|
517
|
+
if not db_manager:
|
|
518
|
+
raise ValueError("Database manager not initialized")
|
|
519
|
+
|
|
520
|
+
async for db in db_manager.get_session():
|
|
521
|
+
try:
|
|
522
|
+
# Get profile with decrypted API key
|
|
523
|
+
profile_with_key = await LLMProfileQueries.get_profile_with_decrypted_key(db, profile_name)
|
|
524
|
+
if not profile_with_key:
|
|
525
|
+
raise ValueError(f"LLM profile '{profile_name}' not found")
|
|
526
|
+
|
|
527
|
+
# Create new LLM instance
|
|
528
|
+
new_llm = create_llm_from_profile(profile_with_key)
|
|
529
|
+
|
|
530
|
+
# Update global state
|
|
531
|
+
llm = new_llm
|
|
532
|
+
if vibesurf_agent:
|
|
533
|
+
vibesurf_agent.llm = new_llm
|
|
534
|
+
|
|
535
|
+
logger.info(f"โ
LLM updated to profile: {profile_name}")
|
|
536
|
+
return True
|
|
537
|
+
|
|
538
|
+
except Exception as e:
|
|
539
|
+
logger.error(f"Failed to update LLM from profile {profile_name}: {e}")
|
|
540
|
+
raise
|
|
541
|
+
finally:
|
|
542
|
+
break
|
|
543
|
+
|
|
544
|
+
except Exception as e:
|
|
545
|
+
logger.error(f"Database error while updating LLM profile: {e}")
|
|
546
|
+
raise
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def get_envs() -> Dict[str, str]:
|
|
550
|
+
"""Get the current environment variables dictionary"""
|
|
551
|
+
global envs
|
|
552
|
+
return envs.copy()
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def update_envs(updates: Dict[str, str]) -> bool:
|
|
556
|
+
"""Update environment variables and save to envs.json"""
|
|
557
|
+
global envs, workspace_dir
|
|
558
|
+
|
|
559
|
+
try:
|
|
560
|
+
# Update the envs dictionary
|
|
561
|
+
envs.update(updates)
|
|
562
|
+
|
|
563
|
+
# Save to envs.json
|
|
564
|
+
envs_file_path = os.path.join(workspace_dir, "envs.json")
|
|
565
|
+
with open(envs_file_path, 'w', encoding='utf-8') as f:
|
|
566
|
+
json.dump(envs, f, indent=2, ensure_ascii=False)
|
|
567
|
+
|
|
568
|
+
logger.info(f"โ
Updated and saved environment variables to {envs_file_path}")
|
|
569
|
+
return True
|
|
570
|
+
|
|
571
|
+
except Exception as e:
|
|
572
|
+
logger.error(f"Failed to update environment variables: {e}")
|
|
573
|
+
return False
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _update_extension_backend_url(extension_path: str, backend_url: str):
|
|
577
|
+
try:
|
|
578
|
+
import re
|
|
579
|
+
|
|
580
|
+
config_js_path = os.path.join(extension_path, "config.js")
|
|
581
|
+
if not os.path.exists(config_js_path):
|
|
582
|
+
logger.warning(f"Extension config.js not found at: {config_js_path}")
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
with open(config_js_path, 'r', encoding='utf-8') as f:
|
|
586
|
+
content = f.read()
|
|
587
|
+
|
|
588
|
+
# ๅน้
BACKEND_URL: 'xyz' ๆ BACKEND_URL: "xyz"๏ผxyzๆฏไปปๆๅ
ๅฎน
|
|
589
|
+
pattern = r"BACKEND_URL:\s*(['\"]).*?\1"
|
|
590
|
+
replacement = f"BACKEND_URL: '{backend_url}'"
|
|
591
|
+
|
|
592
|
+
updated_content = re.sub(pattern, replacement, content)
|
|
593
|
+
|
|
594
|
+
with open(config_js_path, 'w', encoding='utf-8') as f:
|
|
595
|
+
f.write(updated_content)
|
|
596
|
+
|
|
597
|
+
logger.info(f"โ
Updated extension backend URL to: {backend_url}")
|
|
598
|
+
|
|
599
|
+
except Exception as e:
|
|
600
|
+
logger.error(f"โ Failed to update extension backend URL: {e}")
|
|
601
|
+
|