vibesurf 0.1.10__py3-none-any.whl → 0.1.12__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 +880 -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 +46 -13
  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 +5 -22
  34. vibe_surf/common.py +35 -0
  35. vibe_surf/llm/openai_compatible.py +217 -99
  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 +437 -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.12.dist-info}/METADATA +6 -2
  44. {vibesurf-0.1.10.dist-info → vibesurf-0.1.12.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.12.dist-info}/WHEEL +0 -0
  49. {vibesurf-0.1.10.dist-info → vibesurf-0.1.12.dist-info}/entry_points.txt +0 -0
  50. {vibesurf-0.1.10.dist-info → vibesurf-0.1.12.dist-info}/licenses/LICENSE +0 -0
  51. {vibesurf-0.1.10.dist-info → vibesurf-0.1.12.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,8 @@ from dataclasses import dataclass, field
10
10
  from datetime import datetime
11
11
  from pathlib import Path
12
12
  import pickle
13
+ import nanoid
14
+ import shutil
13
15
  from typing import Any, Dict, List, Literal, Optional
14
16
  from uuid_extensions import uuid7str
15
17
  from collections import defaultdict
@@ -22,50 +24,35 @@ from browser_use.browser.session import BrowserSession
22
24
  from browser_use.llm.base import BaseChatModel
23
25
  from browser_use.llm.messages import UserMessage, SystemMessage, BaseMessage, AssistantMessage
24
26
  from browser_use.browser.views import TabInfo
27
+ from browser_use.tokens.service import TokenCost
25
28
 
26
29
  from vibe_surf.agents.browser_use_agent import BrowserUseAgent
27
- from vibe_surf.agents.report_writer_agent import ReportWriterAgent
30
+ from vibe_surf.agents.report_writer_agent import ReportWriterAgent, ReportTaskResult
31
+ from vibe_surf.agents.views import CustomAgentOutput
28
32
 
29
33
  from vibe_surf.agents.prompts.vibe_surf_prompt import (
30
- SUPERVISOR_AGENT_SYSTEM_PROMPT,
34
+ VIBESURF_SYSTEM_PROMPT,
35
+ EXTEND_BU_SYSTEM_PROMPT
31
36
  )
32
37
  from vibe_surf.browser.browser_manager import BrowserManager
33
- from vibe_surf.controller.vibesurf_tools import VibeSurfController
38
+ from vibe_surf.tools.browser_use_tools import BrowserUseTools
39
+ from vibe_surf.tools.vibesurf_tools import VibeSurfTools
40
+ from vibe_surf.tools.file_system import CustomFileSystem
34
41
 
35
- logger = logging.getLogger(__name__)
42
+ from vibe_surf.logger import get_logger
36
43
 
37
-
38
- class TodoItem(BaseModel):
39
- """Individual todo item with simple string-based task description"""
40
- task: str = Field(description="Simple task description")
41
- status: Literal["pending", "in_progress", "completed"] = "pending"
42
- assigned_agent_id: Optional[str] = None
43
- result: Optional[str] = None
44
- error: Optional[str] = None
45
-
46
-
47
- class ExecutionMode(BaseModel):
48
- """Execution mode configuration"""
49
- mode: Literal["single", "parallel"] = "single"
50
- reason: str = Field(description="LLM reasoning for mode selection")
44
+ logger = get_logger(__name__)
51
45
 
52
46
 
53
47
  class BrowserTaskResult(BaseModel):
54
48
  """Result from browser task execution"""
55
49
  agent_id: str
56
- task: str
50
+ agent_workdir: str
57
51
  success: bool
52
+ task: Optional[str] = None
58
53
  result: Optional[str] = None
59
54
  error: Optional[str] = None
60
- screenshots: List[str] = Field(default_factory=list)
61
- extracted_data: Optional[str] = None
62
-
63
-
64
- class ReportRequirement(BaseModel):
65
- """Indicates if and what type of report is needed"""
66
- needs_report: bool = False
67
- report_type: Literal["summary", "detailed", "none"] = "none"
68
- reason: str = Field(description="LLM reasoning for report decision")
55
+ important_files: Optional[List[str]] = None
69
56
 
70
57
 
71
58
  class ControlResult(BaseModel):
@@ -97,50 +84,33 @@ class VibeSurfStatus(BaseModel):
97
84
 
98
85
  @dataclass
99
86
  class VibeSurfState:
100
- """Main LangGraph state for VibeSurfAgent workflow with simplified architecture"""
87
+ """LangGraph state for VibeSurfAgent workflow"""
101
88
 
102
89
  # Core task information
103
90
  original_task: str = ""
104
91
  upload_files: List[str] = field(default_factory=list)
105
92
  session_id: str = field(default_factory=lambda: uuid7str())
106
- task_id: str = field(default_factory=lambda: uuid7str())
93
+ current_workspace_dir: str = "./workspace"
107
94
 
108
95
  # Workflow state
109
- current_step: str = "task_analysis"
110
- is_simple_response: bool = False
111
-
112
- # Supervisor Agent - Core controller with message history
113
- supervisor_message_history: List[BaseMessage] = field(default_factory=list)
114
- supervisor_action: Optional[str] = None
96
+ current_step: str = "vibesurf_agent"
97
+ is_complete: bool = False
115
98
 
116
- # Todo list management
117
- todo_list: List[TodoItem] = field(default_factory=list)
118
- completed_todos: List[TodoItem] = field(default_factory=list)
119
- current_task_index: int = 0
99
+ # Current action and parameters from LLM
100
+ current_action: Optional[str] = None
101
+ action_params: Optional[Dict[str, Any]] = None
120
102
 
121
- # Task execution
122
- execution_mode: Optional[ExecutionMode] = None
123
- pending_tasks: List[str] = field(default_factory=list)
124
- pending_todo_indices: List[int] = field(default_factory=list) # Track which todo indices are being executed
125
- browser_results: List[BrowserTaskResult] = field(default_factory=list) # record all browser result
126
- prev_browser_results: List[BrowserTaskResult] = field(default_factory=list) # record previous browser result
103
+ # Browser task execution
104
+ browser_tasks: List[Dict[str, Any]] = field(default_factory=list)
105
+ browser_results: List[BrowserTaskResult] = field(default_factory=list)
127
106
 
107
+ generated_report_result: Optional[ReportTaskResult] = None
108
+
128
109
  # Response outputs
129
- simple_response: Optional[str] = None
130
- generated_report_path: Optional[str] = None
131
- final_summary: Optional[str] = None
132
- is_complete: bool = False
133
-
134
- # File organization
135
- workspace_dir: str = "./workspace"
136
- session_dir: Optional[str] = None
137
- task_dir: Optional[str] = None
110
+ final_response: Optional[str] = None
138
111
 
139
- # Integration components
140
- browser_manager: Optional[BrowserManager] = None
141
- vibesurf_controller: Optional[VibeSurfController] = None
142
- llm: Optional[BaseChatModel] = None
143
- vibesurf_agent: Optional[Any] = None
112
+ # vibesurf_agent
113
+ vibesurf_agent: Optional['VibeSurfAgent'] = None
144
114
 
145
115
  # Control state management
146
116
  paused: bool = False
@@ -148,139 +118,6 @@ class VibeSurfState:
148
118
  should_pause: bool = False
149
119
  should_stop: bool = False
150
120
 
151
- # Agent control tracking
152
- agent_control_states: Dict[str, Dict[str, Any]] = field(default_factory=dict)
153
- paused_agents: set = field(default_factory=set)
154
-
155
- # Control metadata
156
- control_timestamps: Dict[str, datetime] = field(default_factory=dict)
157
- control_reasons: Dict[str, str] = field(default_factory=dict)
158
- last_control_action: Optional[str] = None
159
-
160
- # Agent activity log
161
- agent_activity_logs: List[Dict[str, str]] = field(default_factory=list)
162
-
163
-
164
- # Utility functions for parsing LLM JSON responses
165
- def parse_json_response(response_text: str, fallback_data: Dict) -> Dict:
166
- """Parse JSON response with repair capability"""
167
- try:
168
- # Try to find JSON in the response
169
- json_start = response_text.find('{')
170
- json_end = response_text.rfind('}') + 1
171
-
172
- if json_start >= 0 and json_end > json_start:
173
- json_text = response_text[json_start:json_end]
174
- try:
175
- return json.loads(json_text)
176
- except json.JSONDecodeError:
177
- # Try to repair JSON
178
- repaired_json = repair_json(json_text)
179
- return json.loads(repaired_json)
180
-
181
- # If no JSON found, return fallback
182
- return fallback_data
183
- except Exception as e:
184
- logger.warning(f"JSON parsing failed: {e}, using fallback")
185
- return fallback_data
186
-
187
-
188
- def parse_task_analysis_response(response_text: str) -> Dict:
189
- """Parse task analysis JSON response for simple response detection"""
190
- fallback = {
191
- "is_simple_response": False,
192
- "reasoning": "Failed to parse response, defaulting to complex task",
193
- "simple_response_content": None
194
- }
195
- return parse_json_response(response_text, fallback)
196
-
197
-
198
- def parse_supervisor_response(response_text: str) -> Dict:
199
- """Parse supervisor agent JSON response"""
200
- fallback = {
201
- "action": "summary_generation",
202
- "reasoning": "Failed to parse response, defaulting to summary generation",
203
- "todo_items": [],
204
- "task_type": "single",
205
- "tasks_to_execute": [],
206
- "summary_content": None
207
- }
208
- result = parse_json_response(response_text, fallback)
209
-
210
- # Ensure todo_items is always a list for actions that modify todos
211
- if result.get("action") in ["generate_todos", "update_todos"]:
212
- if "todo_items" not in result or not isinstance(result["todo_items"], list):
213
- result["todo_items"] = []
214
-
215
- return result
216
-
217
-
218
- def parse_todo_generation_response(response_text: str) -> List[str]:
219
- """Parse todo generation JSON response"""
220
- fallback = {
221
- "todo_items": ["Complete the original task"]
222
- }
223
- result = parse_json_response(response_text, fallback)
224
- return result.get("todo_items", fallback["todo_items"])[:5] # Max 5 items
225
-
226
-
227
- def parse_execution_planning_response(response_text: str) -> ExecutionMode:
228
- """Parse execution planning JSON response"""
229
- fallback = {
230
- "execution_mode": "single",
231
- "reasoning": "Default single mode"
232
- }
233
- result = parse_json_response(response_text, fallback)
234
-
235
- return ExecutionMode(
236
- mode=result.get("execution_mode", "single"),
237
- reason=result.get("reasoning", "Default execution mode")
238
- )
239
-
240
-
241
- def parse_todo_update_response(response_text: str) -> Dict:
242
- """Parse todo update JSON response"""
243
- fallback = {
244
- "additional_tasks": [],
245
- "next_action": "summary_generation",
246
- "reasoning": "Default action"
247
- }
248
- return parse_json_response(response_text, fallback)
249
-
250
-
251
- def parse_report_decision_response(response_text: str) -> ReportRequirement:
252
- """Parse report decision JSON response"""
253
- fallback = {
254
- "needs_report": False,
255
- "report_type": "none",
256
- "reasoning": "Default no report"
257
- }
258
- result = parse_json_response(response_text, fallback)
259
-
260
- return ReportRequirement(
261
- needs_report=result.get("needs_report", False),
262
- report_type=result.get("report_type", "none"),
263
- reason=result.get("reasoning", "Default report decision")
264
- )
265
-
266
-
267
- def format_upload_files_list(upload_files: Optional[List[str]] = None) -> str:
268
- """Format uploaded file list for LLM prompt"""
269
- if upload_files is None:
270
- return ""
271
- return "\n".join(
272
- [f"{i + 1}. [{os.path.basename(file_path)}](file:///{file_path})" for i, file_path in enumerate(upload_files)])
273
-
274
-
275
- def format_todo_list(todo_list: List[TodoItem]) -> str:
276
- """Format todo list for LLM prompt"""
277
- return "\n".join([f"{i + 1}. {item.task}" for i, item in enumerate(todo_list)])
278
-
279
-
280
- def format_completed_todos(completed_todos: List[TodoItem]) -> str:
281
- """Format completed todos for LLM prompt"""
282
- return "\n".join([f"✅ {item.task} - {item.result or 'Completed'}" for item in completed_todos])
283
-
284
121
 
285
122
  def format_browser_results(browser_results: List[BrowserTaskResult]) -> str:
286
123
  """Format browser results for LLM prompt"""
@@ -295,97 +132,163 @@ def format_browser_results(browser_results: List[BrowserTaskResult]) -> str:
295
132
  return "\n".join(result_text)
296
133
 
297
134
 
298
- def format_todo_list_markdown(todo_list: List[TodoItem]) -> str:
299
- """Format todo list as markdown"""
300
- if not todo_list:
301
- return "No todo items"
302
-
303
- markdown_lines = []
304
- for i, item in enumerate(todo_list):
305
- if item.status == "completed":
306
- status_symbol = "- [x] "
135
+ def process_agent_msg_file_links(agent_msg: str, agent_name: str, base_dir: Path) -> str:
136
+ """
137
+ Process file links in agent_msg, converting relative paths to absolute paths
138
+
139
+ Args:
140
+ agent_msg: The agent message containing potential file links
141
+ agent_name: Name of the agent (used for special handling of browser_use_agent)
142
+ base_dir: Base directory path from file_system.get_dir()
143
+
144
+ Returns:
145
+ Processed agent_msg with absolute paths
146
+ """
147
+ # Pattern to match markdown links: [text](path)
148
+ link_pattern = r'\[([^\]]*)\]\(([^)]+)\)'
149
+
150
+ def replace_link(match):
151
+ text = match.group(1)
152
+ path = match.group(2)
153
+
154
+ # Skip if already an absolute path or URL
155
+ if path.startswith(('http://', 'https://', 'file:///', '/')):
156
+ return match.group(0)
157
+
158
+ # Build absolute path
159
+ if agent_name.startswith('browser_use_agent-'):
160
+ # Extract task_id and index from agent_name
161
+ # Format: browser_use_agent-{task_id}-{i + 1:03d}
162
+ parts = agent_name.split('-')
163
+ if len(parts) >= 3:
164
+ task_id = parts[1]
165
+ index = parts[2]
166
+ # Add the special sub-path for browser_use_agent
167
+ sub_path = f"bu_agents/{task_id}-{index}"
168
+ absolute_path = base_dir / sub_path / path
169
+ else:
170
+ absolute_path = base_dir / path
307
171
  else:
308
- status_symbol = "- [ ] "
309
- markdown_lines.append(f"{status_symbol}{item.task}")
310
-
311
- return "\n".join(markdown_lines)
172
+ absolute_path = base_dir / path
173
+
174
+ # Convert to string and normalize separators
175
+ abs_path_str = str(absolute_path).replace(os.path.sep, '/')
176
+
177
+ return f"[{text}](file:///{abs_path_str})"
178
+
179
+ # Replace all file links
180
+ processed_msg = re.sub(link_pattern, replace_link, agent_msg)
181
+ return processed_msg
312
182
 
313
183
 
314
- def log_agent_activity(state: VibeSurfState, agent_name: str, agent_status: str, agent_msg: str) -> None:
184
+ async def log_agent_activity(state: VibeSurfState, agent_name: str, agent_status: str, agent_msg: str) -> None:
315
185
  """Log agent activity to the activity log"""
186
+ token_summary = await state.vibesurf_agent.token_cost_service.get_usage_summary()
187
+ token_summary_md = token_summary.model_dump_json(indent=2, exclude_none=True, exclude_unset=True)
188
+ logger.debug(token_summary_md)
189
+
190
+ # Process file links in agent_msg to convert relative paths to absolute paths
191
+ base_dir = state.vibesurf_agent.file_system.get_dir()
192
+ processed_agent_msg = process_agent_msg_file_links(agent_msg, agent_name, base_dir)
193
+
316
194
  activity_entry = {
317
195
  "agent_name": agent_name,
318
196
  "agent_status": agent_status, # working, result, error
319
- "agent_msg": agent_msg
197
+ "agent_msg": processed_agent_msg,
198
+ "timestamp": datetime.now().isoformat(),
199
+ "total_tokens": token_summary.total_tokens,
200
+ "total_cost": token_summary.total_cost
320
201
  }
321
- state.agent_activity_logs.append(activity_entry)
322
- logger.info(f"📝 Logged activity: {agent_name} - {agent_status}:\n{agent_msg}")
202
+ state.vibesurf_agent.activity_logs.append(activity_entry)
203
+ logger.debug(f"📝 Logged activity: {agent_name} - {agent_status}:\n{processed_agent_msg}")
323
204
 
324
205
 
325
206
  def create_browser_agent_step_callback(state: VibeSurfState, agent_name: str):
326
207
  """Create a step callback function for browser-use agent to log each step"""
327
-
328
- def step_callback(browser_state_summary, agent_output, step_num: int) -> None:
208
+
209
+ async def step_callback(browser_state_summary, agent_output, step_num: int) -> None:
329
210
  """Callback function to log browser agent step information"""
330
211
  try:
331
212
  # Format step information as markdown
332
213
  step_msg = f"## Step {step_num}\n\n"
333
-
214
+
334
215
  # Add thinking if present
335
216
  if agent_output.thinking:
336
217
  step_msg += f"**💡 Thinking:**\n{agent_output.thinking}\n\n"
337
-
218
+
338
219
  # Add evaluation if present
339
220
  if agent_output.evaluation_previous_goal:
340
221
  step_msg += f"**👍 Evaluation:**\n{agent_output.evaluation_previous_goal}\n\n"
341
-
222
+
342
223
  # Add memory if present
343
224
  # if agent_output.memory:
344
225
  # step_msg += f"**🧠 Memory:** {agent_output.memory}\n\n"
345
-
226
+
346
227
  # Add next goal if present
347
228
  if agent_output.next_goal:
348
229
  step_msg += f"**🎯 Next Goal:**\n{agent_output.next_goal}\n\n"
349
-
230
+
350
231
  # Add action summary
351
232
  if agent_output.action and len(agent_output.action) > 0:
352
233
  action_count = len(agent_output.action)
353
234
  step_msg += f"**⚡ Actions:**\n"
354
-
235
+
236
+ all_action_data = []
355
237
  # Add brief action details
356
- for i, action in enumerate(agent_output.action): # Limit to first 3 actions to avoid too much detail
357
- action_data = action.model_dump(exclude_unset=True)
358
- action_name = next(iter(action_data.keys())) if action_data else 'unknown'
359
- action_params = json.dumps(action_data[action_name], ensure_ascii=False) if action_name in action_data else ""
360
- step_msg += f"- [x] {action_name}: {action_params}\n"
238
+ for i, action in enumerate(agent_output.action):
239
+ action_data = action.model_dump(exclude_unset=True, exclude_none=True)
240
+ all_action_data.append(action_data)
241
+ step_msg += f"```json\n{json.dumps(all_action_data, indent=2, ensure_ascii=False)}\n```"
361
242
  else:
362
243
  step_msg += f"**⚡ Actions:** No actions\n"
363
-
244
+
364
245
  # Log the step activity
365
- log_agent_activity(state, agent_name, "working", step_msg.strip())
366
-
246
+ await log_agent_activity(state, agent_name, "working", step_msg.strip())
247
+
367
248
  except Exception as e:
368
249
  logger.error(f"❌ Error in step callback for {agent_name}: {e}")
369
250
  # Log a simple fallback message
370
- log_agent_activity(state, agent_name, "step", f"Step {step_num} completed")
371
-
251
+ await log_agent_activity(state, agent_name, "step", f"Step {step_num} completed")
252
+
372
253
  return step_callback
373
254
 
374
255
 
375
- def ensure_directories(state: VibeSurfState) -> None:
376
- """Ensure proper directory structure"""
377
- # Create session directory: workspace_dir/session_id/
378
- state.session_dir = os.path.join(state.workspace_dir, state.session_id)
379
- os.makedirs(state.session_dir, exist_ok=True)
256
+ def create_report_writer_step_callback(state: VibeSurfState, agent_name: str):
257
+ """Create a step callback function for report writer agent to log each step"""
258
+
259
+ async def step_callback(parsed_output, step_num: int) -> None:
260
+ """Callback function to log report writer agent step information"""
261
+ try:
262
+ # Format step information as markdown
263
+ step_msg = f"## Step {step_num}\n\n"
264
+
265
+ # Add thinking if present
266
+ if hasattr(parsed_output, 'thinking') and parsed_output.thinking:
267
+ step_msg += f"**💡 Thinking:**\n{parsed_output.thinking}\n\n"
268
+
269
+ # Add action summary
270
+ if hasattr(parsed_output, 'action') and parsed_output.action and len(parsed_output.action) > 0:
271
+ action_count = len(parsed_output.action)
272
+ step_msg += f"**⚡ Actions:**\n"
273
+
274
+ # Add brief action details
275
+ all_action_data = []
276
+ for i, action in enumerate(parsed_output.action):
277
+ action_data = action.model_dump(exclude_unset=True, exclude_none=True)
278
+ all_action_data.append(action_data)
279
+ step_msg += f"```json\n{json.dumps(all_action_data, indent=2, ensure_ascii=False)}\n```"
280
+ else:
281
+ step_msg += f"**⚡ Actions:** No actions\n"
282
+
283
+ # Log the step activity
284
+ await log_agent_activity(state, agent_name, "working", step_msg.strip())
380
285
 
381
- # Create task directory: workspace_dir/session_id/task_id/
382
- state.task_dir = os.path.join(state.session_dir, state.task_id)
383
- os.makedirs(state.task_dir, exist_ok=True)
286
+ except Exception as e:
287
+ logger.error(f"❌ Error in step callback for {agent_name}: {e}")
288
+ # Log a simple fallback message
289
+ await log_agent_activity(state, agent_name, "step", f"Step {step_num} completed")
384
290
 
385
- # Create subdirectories for different output types
386
- os.makedirs(os.path.join(state.task_dir, "screenshots"), exist_ok=True)
387
- os.makedirs(os.path.join(state.task_dir, "reports"), exist_ok=True)
388
- os.makedirs(os.path.join(state.task_dir, "logs"), exist_ok=True)
291
+ return step_callback
389
292
 
390
293
 
391
294
  # Control-aware node wrapper
@@ -404,7 +307,6 @@ async def control_aware_node(node_func, state: VibeSurfState, node_name: str) ->
404
307
  logger.info(f"⏸️ Node {node_name} pausing workflow")
405
308
  state.paused = True
406
309
  state.should_pause = False
407
- state.control_timestamps["paused"] = datetime.now()
408
310
 
409
311
  logger.debug(f"⏸️ Node {node_name} waiting - workflow paused")
410
312
  await asyncio.sleep(0.5) # Check every 500ms
@@ -414,7 +316,6 @@ async def control_aware_node(node_func, state: VibeSurfState, node_name: str) ->
414
316
  logger.info(f"🛑 Node {node_name} stopped while paused")
415
317
  state.stopped = True
416
318
  state.should_stop = False
417
- state.control_timestamps["stopped"] = datetime.now()
418
319
  return state
419
320
 
420
321
  # Check for stop signal
@@ -422,12 +323,10 @@ async def control_aware_node(node_func, state: VibeSurfState, node_name: str) ->
422
323
  logger.info(f"🛑 Node {node_name} stopping workflow")
423
324
  state.stopped = True
424
325
  state.should_stop = False
425
- state.control_timestamps["stopped"] = datetime.now()
426
326
  return state
427
327
 
428
328
  # Execute the actual node
429
329
  logger.debug(f"▶️ Executing node: {node_name}")
430
- state.last_control_action = f"executing_{node_name}"
431
330
 
432
331
  try:
433
332
  return await node_func(state)
@@ -438,184 +337,164 @@ async def control_aware_node(node_func, state: VibeSurfState, node_name: str) ->
438
337
 
439
338
  # LangGraph Nodes
440
339
 
441
- async def supervisor_agent_node(state: VibeSurfState) -> VibeSurfState:
340
+ async def vibesurf_agent_node(state: VibeSurfState) -> VibeSurfState:
442
341
  """
443
- Core supervisor agent node - manages todos, assigns tasks, and coordinates workflow
342
+ Main VibeSurf agent node using thinking + action pattern like report_writer_agent
444
343
  """
445
- return await control_aware_node(_supervisor_agent_node_impl, state, "supervisor_agent")
446
-
344
+ return await control_aware_node(_vibesurf_agent_node_impl, state, "vibesurf_agent")
447
345
 
448
- def format_browser_tabs(tabs: Optional[List[TabInfo]] = None) -> str:
449
- if not tabs:
450
- return ""
451
- return "\n".join([f"[{i}] Page Title: {item.title}, Page Url: {item.url}, Page ID: {item.target_id}" for i, item in enumerate(tabs)])
452
346
 
347
+ async def _vibesurf_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
348
+ """Implementation using thinking + action pattern similar to report_writer_agent"""
453
349
 
454
- async def _supervisor_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
455
- """Implementation of supervisor agent node - core workflow controller"""
456
- logger.info("🎯 Supervisor Agent: Managing workflow and task coordination...")
350
+ agent_name = "vibesurf_agent"
457
351
 
458
- supervisor_message_history = state.supervisor_message_history
459
-
460
- # Build supervisor user prompt with current context
461
- if state.prev_browser_results:
462
- browser_results_md = format_browser_results(state.prev_browser_results)
463
- supervisor_message_history.append(AssistantMessage(
464
- content=f"Previous Browser Execution Results: \n{browser_results_md}"))
465
- elif state.generated_report_path:
466
- supervisor_message_history.append(AssistantMessage(content=f"Generated Report Path: {state.generated_report_path}"))
467
-
468
- if state.todo_list:
469
- supervisor_message_history.append(UserMessage(
470
- content=f"Completed Todos:\n{format_completed_todos(state.completed_todos)}\nCurrent Todos:\n{format_todo_list(state.todo_list)}"))
471
-
472
- browser_tabs = await state.browser_manager.main_browser_session.get_tabs()
473
- browser_tabs_md = format_browser_tabs(browser_tabs)
474
- if browser_tabs_md:
475
- supervisor_message_history.append(UserMessage(
476
- content=f"Available Browser Tabs:\n{browser_tabs_md}\n"))
477
- active_browser_tab = await state.browser_manager.get_activate_tab()
352
+ # Create action model and agent output using VibeSurfTools
353
+ vibesurf_agent = state.vibesurf_agent
354
+ ActionModel = vibesurf_agent.tools.registry.create_action_model()
355
+ if vibesurf_agent.thinking_mode:
356
+ AgentOutput = CustomAgentOutput.type_with_custom_actions(ActionModel)
357
+ else:
358
+ AgentOutput = CustomAgentOutput.type_with_custom_actions_no_thinking(ActionModel)
359
+
360
+ # Get current browser context
361
+ browser_tabs = await vibesurf_agent.browser_manager.main_browser_session.get_tabs()
362
+ active_browser_tab = await vibesurf_agent.browser_manager.get_activate_tab()
363
+
364
+ # Format context information
365
+ context_info = []
366
+ context_info.append(f"Current Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
367
+ if browser_tabs:
368
+ browser_tabs_info = {}
369
+ for tab in browser_tabs:
370
+ browser_tabs_info[tab.target_id[-4:]] = {
371
+ "page_title": tab.title,
372
+ "page_url": tab.url,
373
+ }
374
+ context_info.append(
375
+ f"Current Available Browser Tabs:\n{json.dumps(browser_tabs_info, ensure_ascii=False, indent=2)}\n")
478
376
  if active_browser_tab:
479
- active_tab_md = f"Page Title: {active_browser_tab.title}, Page Url: {active_browser_tab.url}, Page ID: {active_browser_tab.target_id}"
480
- supervisor_message_history.append(UserMessage(
481
- content=f"Current Active Browser Tab:\n{active_tab_md}\n"))
377
+ context_info.append(f"Current Active Browser Tab:{active_browser_tab.target_id[-4:]}\n")
378
+ if state.browser_results:
379
+ results_md = format_browser_results(state.browser_results)
380
+ context_info.append(f"Previous Browser Results:\n{results_md}\n")
381
+ if state.generated_report_result:
382
+ if state.generated_report_result.success:
383
+ context_info.append(f"Generated Report: ✅ Success - {state.generated_report_result.report_path}\n")
384
+ else:
385
+ context_info.append(f"Generated Report: ❌ Failed - {state.generated_report_result.msg}\nPath: {state.generated_report_result.report_path}\n")
386
+
387
+ context_str = "\n".join(context_info) if context_info else "No additional context available."
388
+ vibesurf_agent.message_history.append(UserMessage(content=context_str))
482
389
 
483
- # Reset prev_browser_results
484
- state.prev_browser_results = []
485
390
  try:
486
- response = await state.llm.ainvoke(supervisor_message_history)
487
- # add result to message history
488
- supervisor_message_history.append(AssistantMessage(content=response.completion))
489
-
490
- supervisor_result = parse_supervisor_response(response.completion)
491
-
492
- action = supervisor_result["action"]
493
- reasoning = supervisor_result["reasoning"]
494
- # Log agent activity
495
- log_agent_activity(state, "supervisor_agent", "thinking", f"{reasoning}")
496
-
497
- state.supervisor_action = action
498
-
499
- logger.info(f"🎯 Supervisor decision: {action} - {reasoning}")
500
-
501
- # Handle different actions
502
- if action == "generate_todos":
503
- # Generate initial todos
504
- todo_items = supervisor_result.get("todo_items", [])
505
- if todo_items:
506
- state.todo_list = [TodoItem(task=task) for task in todo_items]
507
- todo_todo_list_md = format_todo_list_markdown(state.todo_list)
508
- supervisor_message_history.append(
509
- UserMessage(content=f"Successfully generated todo list:\n{todo_todo_list_md}"))
510
- log_agent_activity(state, "supervisor_agent", "result", f"Todo List:\n\n{todo_todo_list_md}")
511
- # Continue in supervisor to assign tasks
512
- state.current_step = "supervisor_agent"
513
-
514
- elif action == "update_todos":
515
- # Replace all remaining todos with the new list
516
- todo_items = supervisor_result.get("todo_items", [])
517
- if todo_items:
518
- # Clear current todo list and replace with new items
519
- state.todo_list = [TodoItem(task=task) for task in todo_items]
520
- todo_todo_list_md = format_todo_list_markdown(state.completed_todos + state.todo_list)
521
- supervisor_message_history.append(
522
- UserMessage(content=f"Successfully Updated todo list:\n{todo_todo_list_md}"))
523
- log_agent_activity(state, "supervisor_agent", "result", f"Todo List:\n\n{todo_todo_list_md}")
524
- else:
525
- # If no todo_items provided, clear the list
526
- state.todo_list = []
527
- todo_todo_list_md = format_todo_list_markdown(state.completed_todos + state.todo_list)
528
- supervisor_message_history.append(
529
- UserMessage(content=f"Cleared todo list - all tasks completed:\n{todo_todo_list_md}"))
530
- log_agent_activity(state, "supervisor_agent", "result",
531
- f"Cleared todo list - all tasks completed\n{todo_todo_list_md}")
532
-
533
- # Continue in supervisor to assign tasks
534
- state.current_step = "supervisor_agent"
535
-
536
- elif action == "assign_browser_task":
537
- # Assign browser tasks
538
- task_type = supervisor_result.get("task_type", "single")
539
- tasks_to_execute = supervisor_result.get("tasks_to_execute", [])
540
-
541
- if tasks_to_execute:
542
- tasks_to_execute_new = []
543
- todo_indices = [] # Track which todo items are being executed
544
- for task_item in tasks_to_execute:
545
- if isinstance(task_item, list):
546
- # Format: [page_index, todo_index]
547
- page_index, todo_index = task_item
548
- if isinstance(todo_index, int):
549
- if todo_index < len(state.todo_list):
550
- task_description = state.todo_list[todo_index].task
551
- tasks_to_execute_new.append([browser_tabs[page_index].target_id, task_description])
552
- todo_indices.append(todo_index)
553
- elif isinstance(todo_index, str):
554
- tasks_to_execute_new.append([browser_tabs[page_index].target_id, todo_index])
555
- else:
556
- # Format: todo_index
557
- todo_index = task_item
558
- if isinstance(todo_index, int):
559
- if todo_index < len(state.todo_list):
560
- task_description = state.todo_list[todo_index].task
561
- tasks_to_execute_new.append(task_description)
562
- todo_indices.append(todo_index)
563
- elif isinstance(todo_index, str):
564
- tasks_to_execute_new.append(todo_index)
565
- logger.info(f"tasks_to_execute: {tasks_to_execute}")
566
- state.execution_mode = ExecutionMode(
567
- mode=task_type,
568
- reason=reasoning
569
- )
570
- state.pending_tasks = tasks_to_execute_new
571
- state.pending_todo_indices = todo_indices # Store which todo indices are being executed
391
+ # Get LLM response with action output format
392
+ response = await vibesurf_agent.llm.ainvoke(vibesurf_agent.message_history, output_format=AgentOutput)
393
+ parsed = response.completion
394
+ actions = parsed.action
395
+ vibesurf_agent.message_history.append(
396
+ AssistantMessage(content=json.dumps(response.completion.model_dump(exclude_none=True, exclude_unset=True),
397
+ ensure_ascii=False)))
398
+
399
+ # Log thinking if present
400
+ if hasattr(parsed, 'thinking') and parsed.thinking:
401
+ await log_agent_activity(state, agent_name, "thinking", parsed.thinking)
402
+
403
+ for i, action in enumerate(actions):
404
+ action_data = action.model_dump(exclude_unset=True)
405
+ action_name = next(iter(action_data.keys())) if action_data else 'unknown'
406
+ logger.info(f"🛠️ Processing VibeSurf action {i + 1}/{len(actions)}: {action_name}")
407
+
408
+ # Check for special routing actions
409
+ if action_name == 'execute_browser_use_agent':
410
+ # Route to browser task execution node
411
+ params = action_data[action_name]
412
+ state.browser_tasks = params.get('tasks', [])
413
+ state.current_action = 'execute_browser_use_agent'
414
+ state.action_params = params
572
415
  state.current_step = "browser_task_execution"
573
416
 
574
- log_agent_activity(state, "supervisor_agent", "result",
575
- f"Assigned {len(tasks_to_execute)} browser tasks ({task_type} mode)")
576
- else:
577
- # No tasks to execute, continue in supervisor
578
- state.current_step = "supervisor_agent"
579
- supervisor_message_history.append(
580
- UserMessage(content=f"No tasks to execute. Please provide browser tasks to execute."))
581
-
582
- elif action == "assign_report_task":
583
- # Assign report generation task
584
- state.current_step = "report_task_execution"
585
- log_agent_activity(state, "supervisor_agent", "result", "Assigned report generation task")
586
-
587
- elif action == "simple_response":
588
- # Use provided content or generate if not provided
589
- state.current_step = "simple_response"
590
- state.simple_response = supervisor_result["simple_response_content"]
591
- state.is_complete = True
592
- log_agent_activity(state, "supervisor_agent", "result", state.simple_response)
593
- elif action == "summary_generation":
594
- # Handle summary generation directly in supervisor
595
- summary_content = supervisor_result.get("summary_content")
596
-
597
- if summary_content:
598
- # Use LLM-provided summary content
599
- state.final_summary = summary_content
417
+ # Log agent activity
418
+ browser_tasks_md = []
419
+ for browser_task in state.browser_tasks:
420
+ bu_task = browser_task.get('task', "")
421
+ if bu_task:
422
+ browser_tasks_md.append(f"- [ ] {bu_task}")
423
+ browser_tasks_md = '\n'.join(browser_tasks_md)
424
+ agent_msg = f"Routing to browser task execution with {len(state.browser_tasks)} browser tasks:\n\n{browser_tasks_md}"
425
+ await log_agent_activity(state, agent_name, "working", agent_msg)
426
+ logger.debug(agent_msg)
427
+ return state
428
+
429
+ elif action_name == 'execute_report_writer_agent':
430
+ # Route to report task execution node
431
+ params = action_data[action_name]
432
+ state.current_action = 'execute_report_writer_agent'
433
+ state.action_params = params
434
+ state.current_step = "report_task_execution"
435
+ report_task = params.get('task', "")
436
+ agent_msg = f"Routing to report generation with task:\n{report_task}"
437
+ await log_agent_activity(state, agent_name, "working", agent_msg)
438
+ return state
439
+
440
+ elif action_name == 'task_done':
441
+ # Handle response/completion - direct to END
442
+ params = action_data[action_name]
443
+ response_content = params.get('response', 'Task completed!')
444
+ follow_tasks = params.get('suggestion_follow_tasks', [])
445
+ state.current_step = "END"
446
+
447
+ # Format final response
448
+ final_response = f"{response_content}"
449
+ await log_agent_activity(state, agent_name, "result", final_response)
450
+
451
+ if follow_tasks:
452
+ await log_agent_activity(state, agent_name, "suggestion_tasks",
453
+ '\n'.join(follow_tasks))
454
+ final_response += "\n\n## Suggested Follow-up Tasks:\n"
455
+ for j, task in enumerate(follow_tasks[:3], 1):
456
+ final_response += f"{j}. {task}\n"
457
+
458
+ state.final_response = final_response
459
+ logger.debug(final_response)
600
460
  state.is_complete = True
601
- state.current_step = "summary_generation"
602
- log_agent_activity(state, "supervisor_agent", "result", f"{summary_content}")
461
+ return state
462
+
603
463
  else:
604
- # Generate summary using the same logic as the old summary generation node
605
- state.current_step = "supervisor_agent"
606
- supervisor_message_history.append(
607
- UserMessage(content=f"The summary content is empty. Please provide summary content if you think all requirements have been accomplished."))
608
- else:
609
- # Unknown action, default to complete workflow
610
- state.current_step = "summary_generation"
611
- log_agent_activity(state, "supervisor_agent", "error", f"Unknown action: {action}")
464
+ if "todos" in action_name:
465
+ todo_content = await vibesurf_agent.file_system.read_file('todo.md')
466
+ action_msg = f"{action_name}:\n\n{todo_content}"
467
+ logger.debug(action_msg)
468
+ await log_agent_activity(state, agent_name, "working", action_msg)
469
+ else:
470
+ action_msg = f"**⚡ Actions:**\n"
471
+ action_msg += f"```json\n{json.dumps(action_data, indent=2, ensure_ascii=False)}\n```"
472
+ logger.debug(action_msg)
473
+ await log_agent_activity(state, agent_name, "working", action_msg)
474
+
475
+ result = await vibesurf_agent.tools.act(
476
+ action=action,
477
+ browser_manager=vibesurf_agent.browser_manager,
478
+ llm=vibesurf_agent.llm,
479
+ file_system=vibesurf_agent.file_system,
480
+ )
481
+ state.current_step = "vibesurf_agent"
482
+ if result.extracted_content:
483
+ vibesurf_agent.message_history.append(
484
+ UserMessage(content=f'Action result:\n{result.extracted_content}'))
485
+ await log_agent_activity(state, agent_name, "result", result.extracted_content)
486
+
487
+ if result.error:
488
+ vibesurf_agent.message_history.append(UserMessage(content=f'Action error:\n{result.error}'))
489
+ await log_agent_activity(state, agent_name, "error", result.error)
612
490
 
613
491
  return state
614
492
 
615
493
  except Exception as e:
616
- logger.error(f"❌ Supervisor agent failed: {e}")
617
- state.current_step = "summary_generation"
618
- log_agent_activity(state, "supervisor_agent", "error", f"Supervisor failed: {str(e)}")
494
+ logger.error(f"❌ VibeSurf agent failed: {e}")
495
+ state.final_response = f"Task execution failed: {str(e)}"
496
+ state.is_complete = True
497
+ await log_agent_activity(state, agent_name, "error", f"Agent failed: {str(e)}")
619
498
  return state
620
499
 
621
500
 
@@ -627,187 +506,132 @@ async def browser_task_execution_node(state: VibeSurfState) -> VibeSurfState:
627
506
 
628
507
 
629
508
  async def _browser_task_execution_node_impl(state: VibeSurfState) -> VibeSurfState:
630
- """Implementation of browser task execution node"""
631
- logger.info("🚀 Executing browser tasks assigned by supervisor...")
632
-
633
- # Log agent activity
634
- log_agent_activity(state, "browser_task_executor", "working",
635
- f"Executing {len(state.pending_tasks)} browser tasks in {state.execution_mode.mode if state.execution_mode else 'single'} mode")
636
-
637
- # Setup file organization
638
- ensure_directories(state)
639
-
509
+ """Implementation of browser task execution node - simplified tab-based approach"""
510
+ logger.info("🚀 Executing browser tasks assigned by vibesurf agent...")
640
511
  try:
641
- if state.execution_mode and state.execution_mode.mode == "parallel":
642
- # Execute tasks in parallel
643
- results = await execute_parallel_browser_tasks(state)
512
+ task_count = len(state.browser_tasks)
513
+ if task_count == 0:
514
+ raise ValueError("No browser tasks assigned. Please assign 1 task at least.")
515
+
516
+ if task_count <= 1:
517
+ # Single task execution
518
+ logger.info("📝 Using single execution for single task")
519
+ result = await execute_single_browser_tasks(state)
520
+ results = [result]
521
+ # Update browser results
522
+ state.browser_results.extend(results)
644
523
  else:
645
- # Execute tasks in single mode
646
- results = await execute_single_browser_tasks(state)
647
-
648
- # Update browser results
649
- state.prev_browser_results = copy.deepcopy(results)
650
- state.browser_results.extend(results)
651
-
652
- # Mark corresponding todos as completed using indices
653
- for i, todo_index in enumerate(state.pending_todo_indices):
654
- if todo_index < len(state.todo_list) and state.todo_list[todo_index].status != "completed":
655
- todo = state.todo_list[todo_index]
656
- todo.status = "completed"
657
- if i < len(results):
658
- todo.result = results[i].result if results[i].success else None
659
- todo.error = results[i].error if not results[i].success else None
660
- state.completed_todos.append(todo)
661
-
662
- # Remove completed todos from the todo list
663
- # Sort indices in reverse order to avoid index shifting issues
664
- for todo_index in sorted(state.pending_todo_indices, reverse=True):
665
- if todo_index < len(state.todo_list):
666
- state.todo_list.pop(todo_index)
667
-
668
- # Clear pending tasks and indices
669
- state.pending_tasks = []
670
- state.pending_todo_indices = []
671
-
672
- # Return to supervisor for next decision
673
- state.current_step = "supervisor_agent"
524
+ # Multiple tasks execution - parallel approach
525
+ logger.info(f"🚀 Using parallel execution for {task_count} tasks")
526
+ results = await execute_parallel_browser_tasks(state)
527
+ # Update browser results
528
+ state.browser_results.extend(results)
529
+
530
+ # Return to vibesurf agent for next decision
531
+ state.current_step = "vibesurf_agent"
674
532
 
675
533
  # Log result
676
534
  successful_tasks = sum(1 for result in results if result.success)
677
- log_agent_activity(state, "browser_task_executor", "result",
678
- f"Browser execution completed: {successful_tasks}/{len(results)} tasks successful")
535
+ await log_agent_activity(state, "browser_task_executor", "result",
536
+ f"Browser execution completed: {successful_tasks}/{len(results)} tasks successful")
679
537
 
680
538
  logger.info(f"✅ Browser task execution completed with {len(results)} results")
681
539
  return state
682
540
 
683
541
  except Exception as e:
684
542
  logger.error(f"❌ Browser task execution failed: {e}")
685
-
686
- # Create error results for pending tasks
687
- error_results = []
688
- for i, task in enumerate(state.pending_tasks):
689
- # Get the actual task description for the error result
690
- if isinstance(task, list):
691
- task_description = task[1] # [target_id, task_description]
692
- else:
693
- task_description = task
694
- error_results.append(BrowserTaskResult(
695
- agent_id="error",
696
- task=task_description,
697
- success=False,
698
- error=str(e)
699
- ))
700
-
701
- state.browser_results.extend(error_results)
702
- state.pending_tasks = []
703
- state.pending_todo_indices = []
704
- state.current_step = "supervisor_agent"
705
-
706
- log_agent_activity(state, "browser_task_executor", "error", f"Browser execution failed: {str(e)}")
707
- return state
708
-
709
-
710
- async def report_task_execution_node(state: VibeSurfState) -> VibeSurfState:
711
- """
712
- Execute HTML report generation task assigned by supervisor agent
713
- """
714
- return await control_aware_node(_report_task_execution_node_impl, state, "report_task_execution")
715
-
716
-
717
- async def _report_task_execution_node_impl(state: VibeSurfState) -> VibeSurfState:
718
- """Implementation of report task execution node"""
719
- logger.info("📄 Executing HTML report generation task...")
720
-
721
- # Log agent activity
722
- log_agent_activity(state, "report_task_executor", "working", "Generating HTML report")
723
-
724
- try:
725
- # Use ReportWriterAgent to generate HTML report
726
- report_writer = ReportWriterAgent(
727
- llm=state.llm,
728
- workspace_dir=state.task_dir
543
+ import traceback
544
+ traceback.print_exc()
545
+ state.browser_results.append(BrowserTaskResult(
546
+ agent_id="unknown",
547
+ agent_workdir="unknown",
548
+ task='unknown',
549
+ success=False,
550
+ error=str(e)
729
551
  )
552
+ )
553
+ state.current_step = "vibesurf_agent"
730
554
 
731
- report_data = {
732
- "original_task": state.original_task,
733
- "execution_results": state.browser_results,
734
- "report_type": "detailed", # Default to detailed report
735
- "upload_files": state.upload_files
736
- }
737
-
738
- report_path = await report_writer.generate_report(report_data)
739
-
740
- state.generated_report_path = report_path
741
-
742
- # Return to supervisor for next decision
743
- state.current_step = "supervisor_agent"
744
-
745
- log_agent_activity(state, "report_task_executor", "result",
746
- f"HTML report generated successfully at: `{report_path}`")
747
-
748
- logger.info(f"✅ Report generated: {report_path}")
749
- return state
750
-
751
- except Exception as e:
752
- logger.error(f"❌ Report generation failed: {e}")
753
- state.current_step = "supervisor_agent"
754
- log_agent_activity(state, "report_task_executor", "error", f"Report generation failed: {str(e)}")
555
+ await log_agent_activity(state, "browser_task_executor", "error", f"Browser execution failed: {str(e)}")
755
556
  return state
756
557
 
757
558
 
758
- async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTaskResult]:
559
+ async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTaskResult] | None:
759
560
  """Execute pending tasks in parallel using multiple browser agents"""
760
561
  logger.info("🔄 Executing pending tasks in parallel...")
761
562
 
762
563
  # Register agents with browser manager
763
564
  agents = []
764
- pending_tasks = state.pending_tasks
565
+ pending_tasks = state.browser_tasks
765
566
  bu_agent_ids = []
766
567
  register_sessions = []
767
- for i, task in enumerate(pending_tasks):
768
- agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
769
- if isinstance(task, list):
770
- target_id, task_description = task
771
- else:
772
- task_description = task
773
- target_id = None
568
+ task_id = nanoid.generate(size=5)
569
+ bu_agents_workdir = state.vibesurf_agent.file_system.get_dir() / "bu_agents"
570
+ bu_agents_workdir.mkdir(parents=True, exist_ok=True)
571
+
572
+ for i, task_info in enumerate(pending_tasks):
573
+ agent_id = f"bu_agent-{task_id}-{i + 1:03d}"
574
+ task_description = task_info.get('task', '')
575
+ if not task_description:
576
+ continue
577
+ target_id = task_info.get('target_id', None)
774
578
  register_sessions.append(
775
- state.browser_manager.register_agent(agent_id, target_id=target_id)
579
+ state.vibesurf_agent.browser_manager.register_agent(agent_id, target_id=target_id)
776
580
  )
777
581
  bu_agent_ids.append(agent_id)
778
582
  agent_browser_sessions = await asyncio.gather(*register_sessions)
779
583
 
780
- for i, task in enumerate(pending_tasks):
781
- agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
782
- if isinstance(task, list):
783
- target_id, task_description = task
784
- else:
785
- task_description = task
584
+ vibesurf_tools = state.vibesurf_agent.tools
585
+ bu_tools = BrowserUseTools()
586
+ for mcp_server_name, mcp_client in vibesurf_tools.mcp_clients.items():
587
+ await mcp_client.register_to_tools(
588
+ tools=bu_tools,
589
+ prefix=f"mcp.{mcp_server_name}."
590
+ )
591
+ bu_tasks = [None] * len(pending_tasks)
592
+ for i, task_info in enumerate(pending_tasks):
593
+ agent_id = f"bu_agent-{task_id}-{i + 1:03d}"
594
+ task_description = task_info.get('task', '')
595
+ if not task_description:
596
+ continue
597
+ task_files = task_info.get('task_files', [])
598
+ bu_agent_workdir = bu_agents_workdir / f"{task_id}-{i + 1:03d}"
599
+ bu_agent_workdir.mkdir(parents=True, exist_ok=True)
600
+ agent_name = f"browser_use_agent-{task_id}-{i + 1:03d}"
601
+ # Log agent creation
602
+ await log_agent_activity(state, agent_name, "working", f"{task_description}")
603
+
786
604
  try:
787
- # Log agent creation
788
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "working",
789
- f"{task_description}")
605
+ available_file_paths = []
606
+ if task_files:
607
+ for task_file in task_files:
608
+ upload_workdir = bu_agent_workdir / "upload_files"
609
+ upload_workdir.mkdir(parents=True, exist_ok=True)
610
+ task_file_path = state.vibesurf_agent.file_system.get_absolute_path(task_file)
611
+ if os.path.exists(task_file_path):
612
+ logger.info(f"Copy {task_file_path} to {upload_workdir}")
613
+ shutil.copy(task_file_path, str(upload_workdir))
614
+ available_file_paths.append(os.path.join("upload_files", os.path.basename(task_file_path)))
790
615
 
791
616
  # Create BrowserUseAgent for each task
792
- if state.upload_files:
793
- upload_files_md = format_upload_files_list(state.upload_files)
794
- bu_task = task_description + f"\nAvailable uploaded files:\n{upload_files_md}\n"
617
+ if available_file_paths:
618
+ upload_files_md = '\n'.join(available_file_paths)
619
+ bu_task = task_description + f"\nNecessary files for this task:\n{upload_files_md}\n"
795
620
  else:
796
621
  bu_task = task_description
797
-
622
+ bu_tasks[i] = bu_task
798
623
  # Create step callback for this agent
799
- agent_name = f"browser_use_agent-{i + 1}-{state.task_id[-4:]}"
800
624
  step_callback = create_browser_agent_step_callback(state, agent_name)
801
-
802
625
  agent = BrowserUseAgent(
803
626
  task=bu_task,
804
- llm=state.llm,
627
+ llm=state.vibesurf_agent.llm,
805
628
  browser_session=agent_browser_sessions[i],
806
- controller=state.vibesurf_controller,
807
- task_id=f"{state.task_id}-{i + 1}",
808
- file_system_path=os.path.join(state.task_dir, f"{state.task_id}-{i + 1}"),
629
+ tools=bu_tools,
630
+ task_id=f"{task_id}-{i + 1:03d}",
631
+ file_system_path=str(bu_agent_workdir),
809
632
  register_new_step_callback=step_callback,
810
- extend_system_message="Please make sure the language of your output in JSON value should remain the same as the user's request or task.",
633
+ extend_system_message=EXTEND_BU_SYSTEM_PROMPT,
634
+ token_cost_service=state.vibesurf_agent.token_cost_service
811
635
  )
812
636
  agents.append(agent)
813
637
 
@@ -817,9 +641,11 @@ async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTa
817
641
  logger.debug(f"🔗 Registered parallel agent {agent_id} for control coordination")
818
642
 
819
643
  except Exception as e:
644
+ import traceback
645
+ traceback.print_exc()
820
646
  logger.error(f"❌ Failed to create agent {agent_id}: {e}")
821
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
822
- f"Failed to create agent: {str(e)}")
647
+ await log_agent_activity(state, agent_name, "error",
648
+ f"Failed to create agent: {str(e)}")
823
649
 
824
650
  # Execute all agents in parallel
825
651
  try:
@@ -828,155 +654,284 @@ async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTa
828
654
  # Process results
829
655
  results = []
830
656
  for i, (agent, history) in enumerate(zip(agents, histories)):
831
- agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
657
+ task = bu_tasks[i]
658
+ bu_agent_workdir = f"bu_agents/{task_id}-{i + 1:03d}"
659
+ agent_name = f"browser_use_agent-{task_id}-{i + 1:03d}"
660
+
661
+ important_files = []
662
+ if history and history.history and len(history.history[-1].result) > 0:
663
+ last_result = history.history[-1].result[-1]
664
+ important_files = last_result.attachments
665
+ if important_files:
666
+ important_files = [os.path.join(bu_agent_workdir, file_name) for file_name in important_files]
667
+
832
668
  if isinstance(history, Exception):
833
669
  results.append(BrowserTaskResult(
834
- agent_id=f"agent-{i + 1}",
835
- task=pending_tasks[i],
670
+ agent_id=f"{task_id}-{i + 1:03d}",
671
+ task=task,
836
672
  success=False,
837
- error=str(history)
673
+ error=str(history),
674
+ agent_workdir=bu_agent_workdir,
675
+ important_files=important_files,
838
676
  ))
839
677
  # Log error
840
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
841
- f"Task failed: {str(history)}")
678
+ await log_agent_activity(state, agent_name, "error", f"Task failed: {str(history)}")
842
679
  else:
843
680
  results.append(BrowserTaskResult(
844
- agent_id=f"agent-{i + 1}",
845
- task=pending_tasks[i],
681
+ agent_id=f"{task_id}-{i + 1:03d}",
682
+ task=task,
683
+ agent_workdir=bu_agent_workdir,
846
684
  success=history.is_successful(),
685
+ important_files=important_files,
847
686
  result=history.final_result() if hasattr(history, 'final_result') else "Task completed",
848
687
  error=str(history.errors()) if history.has_errors() and not history.is_successful() else ""
849
688
  ))
850
689
  # Log result
851
690
  if history.is_successful():
852
- result_text = history.final_result() if hasattr(history, 'final_result') else "Task completed"
853
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "result",
854
- f"Task completed successfully: \n{result_text}")
691
+ result_text = history.final_result()
692
+ await log_agent_activity(state, agent_name, "result",
693
+ f"Task completed successfully: \n{result_text}")
855
694
  else:
856
- error_text = str(history.errors()) if history.has_errors() else "Unknown error"
857
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
858
- f"Task failed: {error_text}")
695
+ error_text = str(history.errors())
696
+ await log_agent_activity(state, agent_name, "error", f"Task failed: {error_text}")
859
697
 
860
698
  return results
861
699
 
700
+ except Exception as e:
701
+ import traceback
702
+ traceback.print_exc()
703
+
862
704
  finally:
863
705
  # Remove agents from control tracking and cleanup browser sessions
864
706
  for i, agent_id in enumerate(bu_agent_ids):
865
707
  if not isinstance(pending_tasks[i], list):
866
- await state.browser_manager.unregister_agent(agent_id, close_tabs=True)
708
+ await state.vibesurf_agent.browser_manager.unregister_agent(agent_id, close_tabs=True)
867
709
  if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
868
710
  state.vibesurf_agent._running_agents.pop(agent_id, None)
869
711
  logger.debug(f"🔗 Unregistered parallel agent {agent_id} from control coordination")
870
712
 
871
713
 
872
- async def execute_single_browser_tasks(state: VibeSurfState) -> List[BrowserTaskResult]:
714
+ async def execute_single_browser_tasks(state: VibeSurfState) -> BrowserTaskResult | None:
873
715
  """Execute pending tasks in single mode one by one"""
874
716
  logger.info("🔄 Executing pending tasks in single mode...")
717
+ task_info = state.browser_tasks[0]
718
+ task_id = nanoid.generate(size=5)
719
+ bu_agents_workdir = state.vibesurf_agent.file_system.get_dir() / "bu_agents"
720
+ bu_agents_workdir.mkdir(parents=True, exist_ok=True)
721
+ task_description = task_info.get('task', '')
722
+ if not task_description:
723
+ return BrowserTaskResult(
724
+ agent_id=f"{task_id}-{1:03d}",
725
+ task='',
726
+ agent_workdir=f"bu_agents/{task_id}-{1:03d}",
727
+ success=False,
728
+ error="Task description is empty. Please provide a valid task description for browser use agent.",
729
+ )
730
+
731
+ task_files = task_info.get('task_files', [])
732
+ bu_agent_workdir = bu_agents_workdir / f"{task_id}-{1:03d}"
733
+ bu_agent_workdir.mkdir(parents=True, exist_ok=True)
734
+ agent_name = f"browser_use_agent-{task_id}-{1:03d}"
735
+ agent_id = f"bu_agent-{task_id}-{1:03d}"
736
+ # Log agent creation
737
+ await log_agent_activity(state, agent_name, "working", f"{task_description}")
875
738
 
876
- results = []
877
- for i, task in enumerate(state.pending_tasks):
878
- if isinstance(task, list):
879
- target_id, task_description = task
880
- await state.browser_manager.main_browser_session.get_or_create_cdp_session(target_id, focus=True)
739
+ try:
740
+ vibesurf_tools = state.vibesurf_agent.tools
741
+ bu_tools = BrowserUseTools()
742
+ for mcp_server_name, mcp_client in vibesurf_tools.mcp_clients.items():
743
+ await mcp_client.register_to_tools(
744
+ tools=bu_tools,
745
+ prefix=f"mcp.{mcp_server_name}."
746
+ )
747
+ available_file_paths = []
748
+ if task_files:
749
+ for task_file in task_files:
750
+ upload_workdir = bu_agent_workdir / "upload_files"
751
+ upload_workdir.mkdir(parents=True, exist_ok=True)
752
+ task_file_path = state.vibesurf_agent.file_system.get_absolute_path(task_file)
753
+ if os.path.exists(task_file_path):
754
+ logger.info(f"Copy {task_file_path} to {upload_workdir}")
755
+ shutil.copy(task_file_path, str(upload_workdir))
756
+ available_file_paths.append(os.path.join("upload_files", os.path.basename(task_file_path)))
757
+
758
+ # Create BrowserUseAgent for each task
759
+ if available_file_paths:
760
+ upload_files_md = '\n'.join(available_file_paths)
761
+ bu_task = task_description + f"\nNecessary files for this task:\n{upload_files_md}\n"
762
+ else:
763
+ bu_task = task_description
764
+
765
+ step_callback = create_browser_agent_step_callback(state, agent_name)
766
+ main_browser_session = state.vibesurf_agent.browser_manager.main_browser_session
767
+ if task_info.get("tab_id", None):
768
+ tab_id = task_info.get("tab_id")
769
+ target_id = await main_browser_session.get_target_id_from_tab_id(tab_id)
770
+ await main_browser_session.get_or_create_cdp_session(target_id=target_id)
881
771
  else:
882
- task_description = task
883
- await state.browser_manager.get_activate_tab()
884
- logger.info(f"🔄 Executing task ({i + 1}/{len(state.pending_tasks)}): {task_description}")
772
+ new_target = await main_browser_session.cdp_client.send.Target.createTarget(
773
+ params={'url': 'about:blank'})
774
+ target_id = new_target["targetId"]
775
+ await main_browser_session.get_or_create_cdp_session(target_id=target_id)
776
+ agent = BrowserUseAgent(
777
+ task=bu_task,
778
+ llm=state.vibesurf_agent.llm,
779
+ browser_session=main_browser_session,
780
+ tools=bu_tools,
781
+ task_id=f"{task_id}-{1:03d}",
782
+ file_system_path=str(bu_agent_workdir),
783
+ register_new_step_callback=step_callback,
784
+ extend_system_message=EXTEND_BU_SYSTEM_PROMPT,
785
+ token_cost_service=state.vibesurf_agent.token_cost_service
786
+ )
787
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
788
+ state.vibesurf_agent._running_agents[agent_id] = agent
789
+ logger.debug(f"🔗 Registered single agent {agent_id} for control coordination")
790
+
791
+ history = await agent.run()
792
+ bu_agent_workdir = f"bu_agents/{task_id}-{1:03d}"
793
+ important_files = []
794
+ if history and history.history and len(history.history[-1].result) > 0:
795
+ last_result = history.history[-1].result[-1]
796
+ important_files = last_result.attachments
797
+ if important_files:
798
+ important_files = [os.path.join(bu_agent_workdir, file_name) for file_name in important_files]
799
+
800
+ result = BrowserTaskResult(
801
+ agent_id=agent_id,
802
+ agent_workdir=bu_agent_workdir,
803
+ task=bu_task,
804
+ important_files=important_files,
805
+ success=history.is_successful(),
806
+ result=history.final_result() if hasattr(history, 'final_result') else "Task completed",
807
+ error=str(history.errors()) if history.has_errors() and not history.is_successful() else ""
808
+ )
885
809
 
886
- agent_id = f"agent-single-{state.task_id[-4:]}-{i}"
810
+ # Log result
811
+ if result.success:
812
+ await log_agent_activity(state, agent_name, "result",
813
+ f"Task completed successfully: \n{result.result}")
814
+ else:
815
+ await log_agent_activity(state, agent_name, "error",
816
+ f"Task failed: {result.error}")
817
+ return result
887
818
 
888
- # Log agent activity
889
- log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "working", f"{task_description}")
890
- try:
891
- if state.upload_files:
892
- upload_files_md = format_upload_files_list(state.upload_files)
893
- bu_task = task_description + f"\nAvailable user uploaded files:\n{upload_files_md}\n"
894
- else:
895
- bu_task = task_description
896
- # Create step callback for this agent
897
- agent_name = f"browser_use_agent-{state.task_id[-4:]}"
898
- step_callback = create_browser_agent_step_callback(state, agent_name)
899
-
900
- agent = BrowserUseAgent(
901
- task=bu_task,
902
- llm=state.llm,
903
- browser_session=state.browser_manager.main_browser_session,
904
- controller=state.vibesurf_controller,
905
- task_id=f"{state.task_id}-{i}",
906
- file_system_path=os.path.join(state.task_dir, f"{state.task_id}-{i}"),
907
- register_new_step_callback=step_callback,
908
- extend_system_message="Please make sure the language of your output in JSON values should remain the same as the user's request or task."
909
- )
819
+ except Exception as e:
820
+ import traceback
821
+ traceback.print_exc()
822
+
823
+ bu_agent_workdir = f"bu_agents/{task_id}-{1:03d}"
824
+ return BrowserTaskResult(
825
+ agent_id=agent_id,
826
+ agent_workdir=bu_agent_workdir,
827
+ task=task_description,
828
+ success=False,
829
+ error=str(e)
830
+ )
831
+ finally:
832
+ # Remove agent from control tracking
833
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
834
+ state.vibesurf_agent._running_agents.pop(agent_id, None)
835
+ logger.debug(f"🔗 Unregistered single agent {agent_id} from control coordination")
910
836
 
911
- # Track agent in VibeSurfAgent for control coordination
912
- if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
913
- state.vibesurf_agent._running_agents[agent_id] = agent
914
- logger.debug(f"🔗 Registered single agent {agent_id} for control coordination")
915
837
 
916
- try:
917
- history = await agent.run()
838
+ async def report_task_execution_node(state: VibeSurfState) -> VibeSurfState:
839
+ """
840
+ Execute HTML report generation task assigned by supervisor agent
841
+ """
842
+ return await control_aware_node(_report_task_execution_node_impl, state, "report_task_execution")
918
843
 
919
- result = BrowserTaskResult(
920
- agent_id=agent_id,
921
- task=task,
922
- success=history.is_successful(),
923
- result=history.final_result() if hasattr(history, 'final_result') else "Task completed",
924
- error=str(history.errors()) if history.has_errors() and not history.is_successful() else ""
925
- )
926
844
 
927
- # Log result
928
- if result.success:
929
- log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "result",
930
- f"Task completed successfully: \n{result.result}")
931
- else:
932
- log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "error",
933
- f"Task failed: {result.error}")
845
+ async def _report_task_execution_node_impl(state: VibeSurfState) -> VibeSurfState:
846
+ """Implementation of report task execution node"""
847
+ logger.info("📄 Executing HTML report generation task...")
934
848
 
935
- results.append(result)
936
- finally:
937
- # Remove agent from control tracking
938
- if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
939
- state.vibesurf_agent._running_agents.pop(agent_id, None)
940
- logger.debug(f"🔗 Unregistered single agent {agent_id} from control coordination")
849
+ agent_name = "report_writer_agent"
941
850
 
942
- except Exception as e:
943
- logger.error(f"❌ Single task execution failed: {e}")
944
- log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "error",
945
- f"Task execution failed: {str(e)}")
946
- results.append(BrowserTaskResult(
947
- agent_id=agent_id,
948
- task=task,
949
- success=False,
950
- error=str(e)
951
- ))
851
+ # Log agent activity
852
+ await log_agent_activity(state, agent_name, "working", "Generating HTML report")
853
+
854
+ try:
855
+ # Create step callback for report writer agent
856
+ step_callback = create_report_writer_step_callback(state, agent_name)
857
+
858
+ # Use ReportWriterAgent to generate HTML report
859
+ report_writer = ReportWriterAgent(
860
+ llm=state.vibesurf_agent.llm,
861
+ workspace_dir=str(state.vibesurf_agent.file_system.get_dir()),
862
+ step_callback=step_callback,
863
+ thinking_mode=state.vibesurf_agent.thinking_mode,
864
+ )
865
+
866
+ # Register report writer agent for control coordination
867
+ agent_id = "report_writer_agent"
868
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
869
+ state.vibesurf_agent._running_agents[agent_id] = report_writer
870
+ logger.debug(f"🔗 Registered report writer agent for control coordination")
871
+
872
+ try:
873
+ action_params = state.action_params
874
+ report_task = action_params.get('task', [])
875
+ report_information = {
876
+ "browser_results": [bu_result.model_dump() for bu_result in state.browser_results if bu_result]
877
+ }
878
+ report_data = {
879
+ "report_task": report_task,
880
+ "report_information": report_information
881
+ }
882
+
883
+ report_result = await report_writer.generate_report(report_data)
884
+ state.generated_report_result = report_result
885
+
886
+ # Return to vibesurf agent for next decision
887
+ state.current_step = "vibesurf_agent"
888
+
889
+ if report_result.success:
890
+ await log_agent_activity(state, agent_name, "result",
891
+ f"✅ HTML report generated successfully: {report_result.msg}\nPath: `{report_result.report_path}`")
892
+ logger.info(f"✅ Report generated successfully: {report_result.report_path}")
893
+ else:
894
+ await log_agent_activity(state, agent_name, "error",
895
+ f"❌ Report generation failed: {report_result.msg}\nPath: `{report_result.report_path}`")
896
+ logger.warning(f"⚠️ Report generation failed: {report_result.msg}")
897
+
898
+ finally:
899
+ # Remove report writer agent from control tracking
900
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
901
+ state.vibesurf_agent._running_agents.pop(agent_id, None)
902
+ logger.debug(f"🔗 Unregistered report writer agent from control coordination")
903
+
904
+ return state
952
905
 
953
- return results
906
+ except Exception as e:
907
+ logger.error(f"❌ Report generation failed: {e}")
908
+ state.current_step = "vibesurf_agent"
909
+ await log_agent_activity(state, agent_name, "error", f"Report generation failed: {str(e)}")
910
+ return state
954
911
 
955
912
 
956
- def route_after_supervisor_agent(state: VibeSurfState) -> str:
957
- """Route based on supervisor agent decisions"""
913
+ def route_after_vibesurf_agent(state: VibeSurfState) -> str:
914
+ """Route based on vibesurf agent decisions"""
958
915
  if state.current_step == "browser_task_execution":
959
916
  return "browser_task_execution"
960
917
  elif state.current_step == "report_task_execution":
961
918
  return "report_task_execution"
962
- elif state.current_step == "summary_generation":
963
- return "summary_generation" # Summary generated, go to END
964
- elif state.current_step == "simple_response":
965
- return "simple_response"
966
- elif state.current_step == "supervisor_agent":
967
- return "supervisor_agent" # Continue in supervisor loop
919
+ elif state.current_step == "vibesurf_agent":
920
+ return "vibesurf_agent" # Continue in vibesurf agent loop
921
+ elif state.is_complete:
922
+ return "END" # task_done sets is_complete=True, go directly to END
968
923
  else:
969
924
  return "END" # Default fallback - complete workflow
970
925
 
971
926
 
972
927
  def route_after_browser_task_execution(state: VibeSurfState) -> str:
973
- """Route back to supervisor after browser task completion"""
974
- return "supervisor_agent"
928
+ """Route back to vibesurf agent after browser task completion"""
929
+ return "vibesurf_agent"
975
930
 
976
931
 
977
932
  def route_after_report_task_execution(state: VibeSurfState) -> str:
978
- """Route back to supervisor after report task completion"""
979
- return "supervisor_agent"
933
+ """Route back to vibesurf agent after report task completion"""
934
+ return "vibesurf_agent"
980
935
 
981
936
 
982
937
  def should_continue(state: VibeSurfState) -> str:
@@ -988,38 +943,36 @@ def should_continue(state: VibeSurfState) -> str:
988
943
 
989
944
 
990
945
  def create_vibe_surf_workflow() -> StateGraph:
991
- """Create the simplified LangGraph workflow with supervisor agent as core controller"""
946
+ """Create the simplified LangGraph workflow with supervisor agent as core tools"""
992
947
 
993
948
  workflow = StateGraph(VibeSurfState)
994
949
 
995
950
  # Add nodes for simplified architecture
996
- workflow.add_node("supervisor_agent", supervisor_agent_node)
951
+ workflow.add_node("vibesurf_agent", vibesurf_agent_node)
997
952
  workflow.add_node("browser_task_execution", browser_task_execution_node)
998
953
  workflow.add_node("report_task_execution", report_task_execution_node)
999
954
 
1000
955
  # Set entry point
1001
- workflow.set_entry_point("supervisor_agent")
956
+ workflow.set_entry_point("vibesurf_agent")
1002
957
 
1003
- # Supervisor agent routes to different execution nodes or END
958
+ # VibeSurf agent routes to different execution nodes or END
1004
959
  workflow.add_conditional_edges(
1005
- "supervisor_agent",
1006
- route_after_supervisor_agent,
960
+ "vibesurf_agent",
961
+ route_after_vibesurf_agent,
1007
962
  {
1008
963
  "browser_task_execution": "browser_task_execution",
1009
964
  "report_task_execution": "report_task_execution",
1010
- "summary_generation": END,
1011
- "supervisor_agent": "supervisor_agent",
1012
- "simple_response": END,
965
+ "vibesurf_agent": "vibesurf_agent",
1013
966
  "END": END
1014
967
  }
1015
968
  )
1016
969
 
1017
- # Execution nodes return to supervisor
970
+ # Execution nodes return to vibesurf agent
1018
971
  workflow.add_conditional_edges(
1019
972
  "browser_task_execution",
1020
973
  route_after_browser_task_execution,
1021
974
  {
1022
- "supervisor_agent": "supervisor_agent"
975
+ "vibesurf_agent": "vibesurf_agent"
1023
976
  }
1024
977
  )
1025
978
 
@@ -1027,7 +980,7 @@ def create_vibe_surf_workflow() -> StateGraph:
1027
980
  "report_task_execution",
1028
981
  route_after_report_task_execution,
1029
982
  {
1030
- "supervisor_agent": "supervisor_agent"
983
+ "vibesurf_agent": "vibesurf_agent"
1031
984
  }
1032
985
  )
1033
986
 
@@ -1035,24 +988,32 @@ def create_vibe_surf_workflow() -> StateGraph:
1035
988
 
1036
989
 
1037
990
  class VibeSurfAgent:
1038
- """Main LangGraph-based VibeSurfAgent with comprehensive control capabilities"""
991
+ """Main LangGraph-based VibeSurf Agent"""
1039
992
 
1040
993
  def __init__(
1041
994
  self,
1042
995
  llm: BaseChatModel,
1043
996
  browser_manager: BrowserManager,
1044
- controller: VibeSurfController,
997
+ tools: VibeSurfTools,
1045
998
  workspace_dir: str = "./workspace",
999
+ thinking_mode: bool = True,
1000
+ calculate_token_cost: bool = True,
1046
1001
  ):
1047
1002
  """Initialize VibeSurfAgent with required components"""
1048
- self.llm = llm
1049
- self.browser_manager = browser_manager
1050
- self.controller = controller
1003
+ self.llm: BaseChatModel = llm
1004
+ self.calculate_token_cost = calculate_token_cost
1005
+ self.token_cost_service = TokenCost(include_cost=calculate_token_cost)
1006
+ self.token_cost_service.register_llm(llm)
1007
+ self.browser_manager: BrowserManager = browser_manager
1008
+ self.tools: VibeSurfTools = tools
1051
1009
  self.workspace_dir = workspace_dir
1052
1010
  os.makedirs(self.workspace_dir, exist_ok=True)
1011
+ self.thinking_mode = thinking_mode
1012
+
1053
1013
  self.cur_session_id = None
1054
- self.message_history = self.load_message_history()
1055
- self.activity_logs = self.load_activity_logs()
1014
+ self.file_system: Optional[CustomFileSystem] = None
1015
+ self.message_history = []
1016
+ self.activity_logs = []
1056
1017
 
1057
1018
  # Create LangGraph workflow
1058
1019
  self.workflow = create_vibe_surf_workflow()
@@ -1064,47 +1025,96 @@ class VibeSurfAgent:
1064
1025
  self._running_agents: Dict[str, Any] = {} # Track running BrowserUseAgent instances
1065
1026
  self._execution_task: Optional[asyncio.Task] = None
1066
1027
 
1067
- logger.info("🌊 VibeSurfAgent initialized with LangGraph workflow and control capabilities")
1068
-
1069
- def load_message_history(self, message_history_path: Optional[str] = None):
1070
- if message_history_path is None:
1071
- message_history_path = os.path.join(self.workspace_dir, "message_history.pkl")
1072
- if not os.path.exists(message_history_path):
1073
- return defaultdict(list)
1074
- with open(message_history_path, "rb") as f:
1075
- message_history = pickle.load(f)
1076
- logger.info(f"Loading message history from {message_history_path}")
1077
- for session_id in message_history:
1078
- logger.info(f"{session_id} has {len(message_history[session_id])} messages.")
1079
- return message_history
1080
-
1081
- def save_message_history(self, message_history_path: Optional[str] = None):
1082
- if message_history_path is None:
1083
- message_history_path = os.path.join(self.workspace_dir, "message_history.pkl")
1084
-
1085
- with open(message_history_path, "wb") as f:
1086
- logger.info(f"Saving message history with {len(self.message_history)} sessions to {message_history_path}")
1087
- pickle.dump(self.message_history, f)
1088
-
1089
- def load_activity_logs(self, activity_logs_path: Optional[str] = None):
1090
- if activity_logs_path is None:
1091
- activity_logs_path = os.path.join(self.workspace_dir, "activity_logs.pkl")
1092
- if not os.path.exists(activity_logs_path):
1093
- return defaultdict(list)
1094
- with open(activity_logs_path, "rb") as f:
1095
- activity_logs = pickle.load(f)
1096
- logger.info(f"Loading activity logs from {activity_logs_path}")
1097
- for session_id in activity_logs:
1098
- logger.info(f"{session_id} has {len(activity_logs[session_id])} activity logs.")
1099
- return activity_logs
1100
-
1101
- def save_activity_logs(self, activity_logs_path: Optional[str] = None):
1102
- if activity_logs_path is None:
1103
- activity_logs_path = os.path.join(self.workspace_dir, "activity_logs.pkl")
1104
-
1105
- with open(activity_logs_path, "wb") as f:
1106
- logger.info(f"Saving activity logs with {len(self.activity_logs)} sessions to {activity_logs_path}")
1107
- pickle.dump(self.activity_logs, f)
1028
+ logger.info("🌊 VibeSurf Agent initialized with LangGraph workflow")
1029
+
1030
+ def load_message_history(self, session_id: Optional[str] = None) -> list:
1031
+ """Load message history for a specific session, or return [] for new sessions"""
1032
+ if session_id is None:
1033
+ return []
1034
+
1035
+ session_message_history_path = os.path.join(self.workspace_dir, "sessions", session_id, "message_history.pkl")
1036
+
1037
+ if not os.path.exists(session_message_history_path):
1038
+ all_message_history_path = os.path.join(self.workspace_dir, "message_history.pkl")
1039
+ if os.path.exists(all_message_history_path):
1040
+ with open(all_message_history_path, "rb") as f:
1041
+ message_history_dict = pickle.load(f)
1042
+ if session_id in message_history_dict:
1043
+ return message_history_dict[session_id]
1044
+ logger.info(f"No message history found for session {session_id}, creating new")
1045
+ return []
1046
+
1047
+ try:
1048
+ with open(session_message_history_path, "rb") as f:
1049
+ message_history = pickle.load(f)
1050
+ logger.info(f"Loading message history for session {session_id} from {session_message_history_path}")
1051
+ return message_history
1052
+ except Exception as e:
1053
+ logger.error(f"Failed to load message history for session {session_id}: {e}")
1054
+ return []
1055
+
1056
+ def save_message_history(self, session_id: Optional[str] = None):
1057
+ """Save message history for a specific session"""
1058
+ if session_id is None:
1059
+ return
1060
+
1061
+ # Create session directory if it doesn't exist
1062
+ session_dir = os.path.join(self.workspace_dir, "sessions", session_id)
1063
+ os.makedirs(session_dir, exist_ok=True)
1064
+
1065
+ session_message_history_path = os.path.join(session_dir, "message_history.pkl")
1066
+
1067
+ try:
1068
+ with open(session_message_history_path, "wb") as f:
1069
+ logger.info(f"Saving message history for session {session_id} to {session_message_history_path}")
1070
+ pickle.dump(self.message_history, f)
1071
+ except Exception as e:
1072
+ logger.error(f"Failed to save message history for session {session_id}: {e}")
1073
+
1074
+ def load_activity_logs(self, session_id: Optional[str] = None) -> list:
1075
+ """Load activity logs for a specific session, or return [] for new sessions"""
1076
+ if session_id is None:
1077
+ return []
1078
+
1079
+ session_activity_logs_path = os.path.join(self.workspace_dir, "sessions", session_id, "activity_logs.pkl")
1080
+
1081
+ if not os.path.exists(session_activity_logs_path):
1082
+ # Adaptive to the older version
1083
+ all_activity_logs_path = os.path.join(self.workspace_dir, "activity_logs.pkl")
1084
+ if os.path.exists(all_activity_logs_path):
1085
+ with open(all_activity_logs_path, "rb") as f:
1086
+ activity_logs_dict = pickle.load(f)
1087
+ if session_id in activity_logs_dict:
1088
+ return activity_logs_dict[session_id]
1089
+ logger.info(f"No activity logs found for session {session_id}, creating new")
1090
+ return []
1091
+
1092
+ try:
1093
+ with open(session_activity_logs_path, "rb") as f:
1094
+ activity_logs = pickle.load(f)
1095
+ logger.info(f"Loading activity logs for session {session_id} from {session_activity_logs_path}")
1096
+ return activity_logs
1097
+ except Exception as e:
1098
+ logger.error(f"Failed to load activity logs for session {session_id}: {e}")
1099
+ return []
1100
+
1101
+ def save_activity_logs(self, session_id: Optional[str] = None):
1102
+ """Save activity logs for a specific session"""
1103
+ if session_id is None:
1104
+ return
1105
+
1106
+ # Create session directory if it doesn't exist
1107
+ session_dir = os.path.join(self.workspace_dir, "sessions", session_id)
1108
+ os.makedirs(session_dir, exist_ok=True)
1109
+
1110
+ session_activity_logs_path = os.path.join(session_dir, "activity_logs.pkl")
1111
+
1112
+ try:
1113
+ with open(session_activity_logs_path, "wb") as f:
1114
+ logger.info(f"Saving activity logs for session {session_id} to {session_activity_logs_path}")
1115
+ pickle.dump(self.activity_logs, f)
1116
+ except Exception as e:
1117
+ logger.error(f"Failed to save activity logs for session {session_id}: {e}")
1108
1118
 
1109
1119
  async def stop(self, reason: str = None) -> ControlResult:
1110
1120
  """
@@ -1121,21 +1131,16 @@ class VibeSurfAgent:
1121
1131
  reason = reason or "Manual stop requested"
1122
1132
  logger.info(f"🛑 Stopping agent execution: {reason}")
1123
1133
 
1124
- if self.cur_session_id in self.message_history:
1125
- supervisor_message_history = self.message_history[self.cur_session_id]
1126
- supervisor_message_history.append(UserMessage(
1127
- content=f"🛑 Stopping agent execution: {reason}"))
1134
+ self.message_history.append(UserMessage(
1135
+ content=f"🛑 Stopping agent execution: {reason}"))
1128
1136
 
1129
1137
  if self._current_state:
1130
1138
  self._current_state.should_stop = True
1131
1139
  self._current_state.stopped = True
1132
- self._current_state.control_timestamps["stopped"] = datetime.now()
1133
- self._current_state.control_reasons["stopped"] = reason
1134
- self._current_state.last_control_action = "stop"
1135
1140
 
1136
1141
  # Stop all running agents with timeout
1137
1142
  try:
1138
- await asyncio.wait_for(self._stop_all_agents(reason), timeout=3.0)
1143
+ await asyncio.wait_for(self._stop_all_agents(), timeout=3.0)
1139
1144
  except asyncio.TimeoutError:
1140
1145
  logger.warning("⚠️ Agent stopping timed out, continuing with task cancellation")
1141
1146
 
@@ -1186,19 +1191,14 @@ class VibeSurfAgent:
1186
1191
  reason = reason or "Manual pause requested"
1187
1192
  logger.info(f"⏸️ Pausing agent execution: {reason}")
1188
1193
 
1189
- if self.cur_session_id in self.message_history:
1190
- supervisor_message_history = self.message_history[self.cur_session_id]
1191
- supervisor_message_history.append(UserMessage(
1192
- content=f"⏸️ Pausing agent execution: {reason}"))
1194
+ self.message_history.append(UserMessage(
1195
+ content=f"⏸️ Pausing agent execution: {reason}"))
1193
1196
 
1194
1197
  if self._current_state:
1195
1198
  self._current_state.should_pause = True
1196
- self._current_state.control_timestamps["pause_requested"] = datetime.now()
1197
- self._current_state.control_reasons["paused"] = reason
1198
- self._current_state.last_control_action = "pause"
1199
1199
 
1200
1200
  # Pause all running agents
1201
- await self._pause_all_agents(reason)
1201
+ await self._pause_all_agents()
1202
1202
 
1203
1203
  logger.info(f"✅ VibeSurf execution paused: {reason}")
1204
1204
  return ControlResult(
@@ -1231,20 +1231,15 @@ class VibeSurfAgent:
1231
1231
  reason = reason or "Manual resume requested"
1232
1232
  logger.info(f"▶️ Resuming agent execution: {reason}")
1233
1233
 
1234
- if self.cur_session_id in self.message_history:
1235
- supervisor_message_history = self.message_history[self.cur_session_id]
1236
- supervisor_message_history.append(UserMessage(
1237
- content=f"▶️ Resuming agent execution: {reason}"))
1234
+ self.message_history.append(UserMessage(
1235
+ content=f"▶️ Resuming agent execution: {reason}"))
1238
1236
 
1239
1237
  if self._current_state:
1240
1238
  self._current_state.paused = False
1241
1239
  self._current_state.should_pause = False
1242
- self._current_state.control_timestamps["resumed"] = datetime.now()
1243
- self._current_state.control_reasons["resumed"] = reason
1244
- self._current_state.last_control_action = "resume"
1245
1240
 
1246
1241
  # Resume all paused agents
1247
- await self._resume_all_agents(reason)
1242
+ await self._resume_all_agents()
1248
1243
 
1249
1244
  logger.info(f"✅ VibeSurf execution resumed: {reason}")
1250
1245
  return ControlResult(
@@ -1278,15 +1273,6 @@ class VibeSurfAgent:
1278
1273
  reason = reason or f"Manual pause requested for agent {agent_id}"
1279
1274
  logger.info(f"⏸️ Pausing agent {agent_id}: {reason}")
1280
1275
 
1281
- # Update state tracking
1282
- if self._current_state:
1283
- self._current_state.paused_agents.add(agent_id)
1284
- if agent_id not in self._current_state.agent_control_states:
1285
- self._current_state.agent_control_states[agent_id] = {}
1286
- self._current_state.agent_control_states[agent_id]["paused"] = True
1287
- self._current_state.agent_control_states[agent_id]["pause_reason"] = reason
1288
- self._current_state.agent_control_states[agent_id]["pause_timestamp"] = datetime.now()
1289
-
1290
1276
  # Pause the specific agent if it's running
1291
1277
  agent = self._running_agents.get(agent_id)
1292
1278
  if agent:
@@ -1327,15 +1313,6 @@ class VibeSurfAgent:
1327
1313
  reason = reason or f"Manual resume requested for agent {agent_id}"
1328
1314
  logger.info(f"▶️ Resuming agent {agent_id}: {reason}")
1329
1315
 
1330
- # Update state tracking
1331
- if self._current_state:
1332
- self._current_state.paused_agents.discard(agent_id)
1333
- if agent_id not in self._current_state.agent_control_states:
1334
- self._current_state.agent_control_states[agent_id] = {}
1335
- self._current_state.agent_control_states[agent_id]["paused"] = False
1336
- self._current_state.agent_control_states[agent_id]["resume_reason"] = reason
1337
- self._current_state.agent_control_states[agent_id]["resume_timestamp"] = datetime.now()
1338
-
1339
1316
  # Resume the specific agent if it's running
1340
1317
  agent = self._running_agents.get(agent_id)
1341
1318
  if agent:
@@ -1376,7 +1353,7 @@ class VibeSurfAgent:
1376
1353
  elif self._current_state.paused or self._current_state.should_pause:
1377
1354
  overall_status = "paused"
1378
1355
  elif self._current_state.is_complete:
1379
- overall_status = "idle"
1356
+ overall_status = "completed"
1380
1357
  else:
1381
1358
  overall_status = "running"
1382
1359
 
@@ -1390,13 +1367,11 @@ class VibeSurfAgent:
1390
1367
  error_message = None
1391
1368
  pause_reason = None
1392
1369
 
1393
- # Check if agent is paused
1394
- if self._current_state and agent_id in self._current_state.paused_agents:
1395
- status = "paused"
1396
- agent_state = self._current_state.agent_control_states.get(agent_id, {})
1397
- pause_reason = agent_state.get("pause_reason")
1398
- elif self._current_state and self._current_state.stopped:
1370
+ # Simplified status checking since paused_agents removed
1371
+ if self._current_state and self._current_state.stopped:
1399
1372
  status = "stopped"
1373
+ elif self._current_state and self._current_state.paused:
1374
+ status = "paused"
1400
1375
 
1401
1376
  # Get current action if available
1402
1377
  if agent and hasattr(agent, 'state'):
@@ -1421,11 +1396,9 @@ class VibeSurfAgent:
1421
1396
  if self._current_state:
1422
1397
  progress = {
1423
1398
  "current_step": self._current_state.current_step,
1424
- "completed_todos": len(self._current_state.completed_todos),
1425
- "total_todos": len(self._current_state.todo_list),
1426
- "current_task_index": self._current_state.current_task_index,
1427
1399
  "is_complete": self._current_state.is_complete,
1428
- "last_control_action": self._current_state.last_control_action
1400
+ "browser_tasks_count": len(self._current_state.browser_tasks),
1401
+ "browser_results_count": len(self._current_state.browser_results)
1429
1402
  }
1430
1403
 
1431
1404
  return VibeSurfStatus(
@@ -1443,7 +1416,7 @@ class VibeSurfAgent:
1443
1416
  progress={"error": str(e)}
1444
1417
  )
1445
1418
 
1446
- async def _stop_all_agents(self, reason: str) -> None:
1419
+ async def _stop_all_agents(self) -> None:
1447
1420
  """Stop all running agents"""
1448
1421
  for agent_id, agent in self._running_agents.items():
1449
1422
  try:
@@ -1454,36 +1427,120 @@ class VibeSurfAgent:
1454
1427
  except Exception as e:
1455
1428
  logger.warning(f"⚠️ Failed to stop agent {agent_id}: {e}")
1456
1429
 
1457
- async def _pause_all_agents(self, reason: str) -> None:
1430
+ async def _pause_all_agents(self) -> None:
1458
1431
  """Pause all running agents"""
1459
1432
  for agent_id, agent in self._running_agents.items():
1460
1433
  try:
1461
1434
  if hasattr(agent, 'pause'):
1462
1435
  await agent.pause()
1463
1436
  logger.info(f"⏸️ Paused agent {agent_id}")
1464
- if self._current_state:
1465
- self._current_state.paused_agents.add(agent_id)
1437
+ # Note: paused_agents removed in simplified state
1466
1438
  except Exception as e:
1467
1439
  logger.warning(f"⚠️ Failed to pause agent {agent_id}: {e}")
1468
1440
 
1469
- async def _resume_all_agents(self, reason: str) -> None:
1441
+ async def _resume_all_agents(self) -> None:
1470
1442
  """Resume all paused agents"""
1471
1443
  for agent_id, agent in self._running_agents.items():
1472
1444
  try:
1473
1445
  if hasattr(agent, 'resume'):
1474
1446
  await agent.resume()
1475
1447
  logger.info(f"▶️ Resumed agent {agent_id}")
1476
- if self._current_state:
1477
- self._current_state.paused_agents.discard(agent_id)
1448
+ # Note: paused_agents removed in simplified state
1478
1449
  except Exception as e:
1479
1450
  logger.warning(f"⚠️ Failed to resume agent {agent_id}: {e}")
1480
1451
 
1452
+ def _create_sub_agent_prompt(self, new_task: str, agent_id: str) -> str:
1453
+ """
1454
+ Create a generic prompt for sub-agents when receiving new tasks.
1455
+ This prompt is designed to work with any type of sub-agent.
1456
+ """
1457
+ return f"""🔄 **New Task/Guidance from User:**
1458
+
1459
+ {new_task}
1460
+
1461
+ **Note:** As a sub-agent, you should evaluate whether this new task is relevant to your current work. You may:
1462
+ - **Use it** if it provides helpful guidance, tips, or corrections for your current task
1463
+ - **Use it** if it's a follow-up task that enhances your current work
1464
+ - **Ignore it** if it's unrelated to your specific responsibilities or doesn't apply to your current task
1465
+
1466
+ Please continue with your assigned work, incorporating this guidance only if it's relevant and helpful to your specific role and current task."""
1467
+
1468
+ async def add_new_task(self, new_task: str) -> None:
1469
+ """
1470
+ Add a new task or follow-up instruction during execution.
1471
+ This can be user feedback, guidance, or additional requirements.
1472
+
1473
+ Args:
1474
+ new_task: The new task, guidance, or instruction from the user
1475
+ """
1476
+ activity_entry = {
1477
+ "agent_name": 'user',
1478
+ "agent_status": 'request', # working, result, error
1479
+ "agent_msg": f"{new_task}"
1480
+ }
1481
+ self.activity_logs.append(activity_entry)
1482
+
1483
+ # Create an English prompt for the main agent
1484
+ prompt = f"""🔄 **New Task/Follow-up from User:**
1485
+
1486
+ {new_task}
1487
+
1488
+ **Instructions:** This is additional guidance, a follow-up task, or user feedback to help with the current task execution. Please analyze how this relates to the current task and proceed accordingly."""
1489
+
1490
+ # Add to VibeSurf agent's message history
1491
+ self.message_history.append(UserMessage(content=prompt))
1492
+ logger.info(f"🌊 VibeSurf agent received new task: {new_task}")
1493
+
1494
+ # Propagate to all running sub-agents with generic sub-agent prompt
1495
+ if self._running_agents:
1496
+ logger.info(f"📡 Propagating new task to {len(self._running_agents)} running agents")
1497
+ for agent_id, agent in self._running_agents.items():
1498
+ try:
1499
+ if hasattr(agent, 'add_new_task'):
1500
+ # Use the generic sub-agent prompt
1501
+ sub_agent_prompt = self._create_sub_agent_prompt(new_task, agent_id)
1502
+ agent.add_new_task(sub_agent_prompt)
1503
+ logger.debug(f"✅ Sent new task to agent {agent_id}")
1504
+ else:
1505
+ logger.debug(f"⚠️ Agent {agent_id} doesn't support add_new_task")
1506
+ except Exception as e:
1507
+ logger.warning(f"⚠️ Failed to send new task to agent {agent_id}: {e}")
1508
+ else:
1509
+ logger.debug("📭 No running agents to propagate new task to")
1510
+
1511
+ async def process_upload_files(self, upload_files: Optional[List[str]] = None):
1512
+ if not upload_files:
1513
+ return []
1514
+ new_upload_files = []
1515
+ for ufile_path in upload_files:
1516
+ # timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
1517
+ dst_filename = f"upload_files/{os.path.basename(ufile_path)}"
1518
+ new_upload_files.append(dst_filename)
1519
+ return new_upload_files
1520
+
1521
+ def format_upload_files(self, upload_files: Optional[List[str]] = None, use_abspath: bool = False) -> str:
1522
+ """Format uploaded file for LLM prompt"""
1523
+ if upload_files is None:
1524
+ return ""
1525
+ if use_abspath:
1526
+ file_urls = []
1527
+ for i, file_path in enumerate(upload_files):
1528
+ abs_file_path = self.file_system.get_absolute_path(file_path)
1529
+ normalized_path = abs_file_path.replace(os.path.sep, '/')
1530
+ file_url = f"{i + 1}. [{os.path.basename(file_path)}](file:///{normalized_path})"
1531
+ file_urls.append(file_url)
1532
+ return "\n".join(file_urls)
1533
+ else:
1534
+ return "\n".join(
1535
+ [f"{i + 1}. {file_path}" for i, file_path in enumerate(upload_files)])
1536
+
1481
1537
  async def run(
1482
1538
  self,
1483
1539
  task: str,
1484
1540
  upload_files: Optional[List[str]] = None,
1485
1541
  session_id: Optional[str] = None,
1486
- ) -> str:
1542
+ thinking_mode: bool = True
1543
+ ) -> str | None:
1487
1544
  """
1488
1545
  Main execution method that returns markdown summary with control capabilities
1489
1546
 
@@ -1494,53 +1551,52 @@ class VibeSurfAgent:
1494
1551
  Returns:
1495
1552
  str: Markdown summary of execution results
1496
1553
  """
1497
- logger.info(f"🚀 Starting VibeSurfAgent execution for task: {task[:100]}...")
1498
- agent_activity_logs = None
1554
+ logger.info(f"🚀 Starting VibeSurfAgent execution for task: {task}. Powered by LLM model: {self.llm.model_name}")
1499
1555
  try:
1556
+ self.thinking_mode = thinking_mode
1500
1557
  session_id = session_id or self.cur_session_id or uuid7str()
1501
1558
  if session_id != self.cur_session_id:
1559
+ # Load session-specific data when switching sessions
1502
1560
  self.cur_session_id = session_id
1561
+ self.message_history = self.load_message_history(session_id)
1562
+ self.activity_logs = self.load_activity_logs(session_id)
1563
+ session_dir = os.path.join(self.workspace_dir, "sessions", self.cur_session_id)
1564
+ os.makedirs(session_dir, exist_ok=True)
1565
+ self.file_system = CustomFileSystem(session_dir)
1566
+ self.token_cost_service.clear_history()
1503
1567
 
1504
- if self.cur_session_id not in self.message_history:
1505
- logger.info(f"{self.cur_session_id} not found in message_history, create a new one.")
1506
- self.message_history[self.cur_session_id] = []
1507
- supervisor_message_history = self.message_history[self.cur_session_id]
1508
- if not supervisor_message_history:
1509
- supervisor_message_history.append(SystemMessage(content=SUPERVISOR_AGENT_SYSTEM_PROMPT))
1510
1568
  if upload_files and not isinstance(upload_files, list):
1511
1569
  upload_files = [upload_files]
1512
- upload_files_md = format_upload_files_list(upload_files)
1570
+ upload_files = await self.process_upload_files(upload_files)
1571
+
1572
+ if not self.message_history:
1573
+ self.message_history.append(SystemMessage(content=VIBESURF_SYSTEM_PROMPT))
1574
+
1575
+ # Format processed upload files for prompt
1513
1576
  user_request = f"* User's New Request:\n{task}\n"
1514
1577
  if upload_files:
1578
+ upload_files_md = self.format_upload_files(upload_files)
1515
1579
  user_request += f"* User Uploaded Files:\n{upload_files_md}\n"
1516
- supervisor_message_history.append(
1517
- UserMessage(
1518
- content=user_request)
1580
+ self.message_history.append(
1581
+ UserMessage(content=user_request)
1519
1582
  )
1520
1583
  logger.info(user_request)
1521
1584
 
1522
- if self.cur_session_id not in self.activity_logs:
1523
- self.activity_logs[self.cur_session_id] = []
1524
- agent_activity_logs = self.activity_logs[self.cur_session_id]
1585
+ abs_upload_files_md = self.format_upload_files(upload_files, use_abspath=True)
1525
1586
  activity_entry = {
1526
1587
  "agent_name": 'user',
1527
1588
  "agent_status": 'request', # working, result, error
1528
- "agent_msg": f"{task}\nUpload Files:\n{upload_files_md}\n" if upload_files else f"{task}"
1589
+ "agent_msg": f"{task}\nUpload Files:\n{abs_upload_files_md}\n" if upload_files else f"{task}"
1529
1590
  }
1530
- agent_activity_logs.append(activity_entry)
1591
+ self.activity_logs.append(activity_entry)
1531
1592
 
1532
- # Initialize state
1593
+ # Initialize state first (needed for file processing)
1533
1594
  initial_state = VibeSurfState(
1534
1595
  original_task=task,
1535
1596
  upload_files=upload_files or [],
1536
- workspace_dir=self.workspace_dir,
1537
- browser_manager=self.browser_manager,
1538
- vibesurf_controller=self.controller,
1539
- agent_activity_logs=agent_activity_logs,
1540
- supervisor_message_history=supervisor_message_history,
1541
- llm=self.llm,
1542
1597
  session_id=session_id,
1543
- vibesurf_agent=self # Reference to VibeSurfAgent for control coordination
1598
+ current_workspace_dir=os.path.join(self.workspace_dir, "sessions", self.cur_session_id),
1599
+ vibesurf_agent=self,
1544
1600
  )
1545
1601
 
1546
1602
  # Set current state for control operations
@@ -1575,36 +1631,39 @@ class VibeSurfAgent:
1575
1631
  except asyncio.CancelledError:
1576
1632
  logger.info("🛑 VibeSurfAgent execution was cancelled")
1577
1633
  # Add cancellation activity log
1578
- if agent_activity_logs:
1634
+ if self.activity_logs:
1579
1635
  activity_entry = {
1580
1636
  "agent_name": "VibeSurfAgent",
1581
1637
  "agent_status": "cancelled",
1582
1638
  "agent_msg": "Task execution was cancelled by user request."
1583
1639
  }
1584
- agent_activity_logs.append(activity_entry)
1640
+ self.activity_logs.append(activity_entry)
1585
1641
  return f"# Task Execution Cancelled\n\n**Task:** {task}\n\nExecution was stopped by user request."
1586
1642
  except Exception as e:
1643
+ import traceback
1644
+ traceback.print_exc()
1587
1645
  logger.error(f"❌ VibeSurfAgent execution failed: {e}")
1588
1646
  # Add error activity log
1589
- if agent_activity_logs:
1647
+ if self.activity_logs:
1590
1648
  activity_entry = {
1591
1649
  "agent_name": "VibeSurfAgent",
1592
1650
  "agent_status": "error",
1593
1651
  "agent_msg": f"Task execution failed: {str(e)}"
1594
1652
  }
1595
- agent_activity_logs.append(activity_entry)
1653
+ self.activity_logs.append(activity_entry)
1596
1654
  return f"# Task Execution Failed\n\n**Task:** {task}\n\n**Error:** {str(e)}\n\nPlease try again or contact support."
1597
1655
  finally:
1598
- if agent_activity_logs:
1599
- activity_entry = {
1600
- "agent_name": "VibeSurfAgent",
1601
- "agent_status": "done", # working, result, error
1602
- "agent_msg": "Finish Task."
1603
- }
1604
- agent_activity_logs.append(activity_entry)
1605
- # Reset state
1606
- self.save_message_history()
1607
- self.save_activity_logs()
1656
+
1657
+ activity_entry = {
1658
+ "agent_name": "VibeSurfAgent",
1659
+ "agent_status": "done", # working, result, error
1660
+ "agent_msg": "Finish Task."
1661
+ }
1662
+ self.activity_logs.append(activity_entry)
1663
+ # Save session-specific data
1664
+ if self.cur_session_id:
1665
+ self.save_message_history(self.cur_session_id)
1666
+ self.save_activity_logs(self.cur_session_id)
1608
1667
  async with self._control_lock:
1609
1668
  self._current_state = None
1610
1669
  self._execution_task = None
@@ -1614,44 +1673,40 @@ class VibeSurfAgent:
1614
1673
  List[Dict]]:
1615
1674
  if session_id is None:
1616
1675
  session_id = self.cur_session_id
1617
-
1618
- logger.debug(f"📊 GET_ACTIVITY_LOGS DEBUG - Session: {session_id}, Message Index: {message_index}, Current Session: {self.cur_session_id}")
1619
-
1676
+
1620
1677
  # Ensure session_id exists in activity_logs
1621
- if session_id not in self.activity_logs:
1622
- logger.warning(f"⚠️ Session {session_id} not found in activity_logs. Available sessions: {list(self.activity_logs.keys())}")
1623
- return None
1624
-
1625
- session_logs = self.activity_logs[session_id]
1678
+ if session_id != self.cur_session_id:
1679
+ session_logs = self.load_activity_logs(session_id)
1680
+ else:
1681
+ session_logs = self.activity_logs
1682
+
1626
1683
  logger.debug(f"📋 Session {session_id} has {len(session_logs)} activity logs")
1627
-
1684
+
1628
1685
  if message_index is None:
1629
1686
  logger.debug(f"📤 Returning all {len(session_logs)} activity logs for session {session_id}")
1630
1687
  return session_logs
1631
1688
  else:
1632
1689
  if message_index >= len(session_logs):
1633
- logger.debug(f"⚠️ Message index {message_index} out of range for session {session_id} (max index: {len(session_logs) - 1})")
1690
+ logger.debug(
1691
+ f"⚠️ Message index {message_index} out of range for session {session_id} (max index: {len(session_logs) - 1})")
1634
1692
  return None
1635
1693
  else:
1636
1694
  activity_log = session_logs[message_index]
1637
- logger.debug(f"📤 Returning activity log at index {message_index}: {activity_log.get('agent_name', 'unknown')} - {activity_log.get('agent_status', 'unknown')}")
1695
+ logger.debug(
1696
+ f"📤 Returning activity log at index {message_index}: {activity_log.get('agent_name', 'unknown')} - {activity_log.get('agent_status', 'unknown')}")
1638
1697
  return activity_log
1639
1698
 
1640
1699
  async def _get_result(self, state) -> str:
1641
1700
  """Get the final result from execution with simplified workflow support"""
1642
1701
  # Handle both dict and dataclass state types due to LangGraph serialization
1643
- simple_response = state.get('simple_response') if isinstance(state, dict) else getattr(state, 'simple_response',
1644
- None)
1645
- final_summary = state.get('final_summary') if isinstance(state, dict) else getattr(state, 'final_summary', None)
1702
+ final_response = state.get('final_response') if isinstance(state, dict) else getattr(state, 'final_response',
1703
+ None)
1646
1704
  original_task = state.get('original_task') if isinstance(state, dict) else getattr(state, 'original_task',
1647
1705
  'Unknown task')
1648
-
1649
- # Fallback for cases where state doesn't support logging
1650
- if simple_response:
1651
- return f"# Response\n\n{simple_response}"
1652
- elif final_summary:
1653
- return final_summary
1706
+ if final_response:
1707
+ return final_response
1654
1708
  else:
1655
1709
  return f"# Task Execution Completed\n\n**Task:** {original_task}\n\nTask execution completed but no detailed result available."
1656
1710
 
1711
+
1657
1712
  workflow = create_vibe_surf_workflow()