vibesurf 0.1.9a6__py3-none-any.whl → 0.1.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vibesurf might be problematic. Click here for more details.

Files changed (69) 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 +878 -814
  7. vibe_surf/agents/views.py +130 -0
  8. vibe_surf/backend/api/activity.py +3 -1
  9. vibe_surf/backend/api/browser.py +70 -0
  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 +47 -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 +80 -3
  18. vibe_surf/backend/shared_state.py +30 -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 +308 -62
  22. vibe_surf/browser/browser_manager.py +71 -100
  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 +88 -0
  26. vibe_surf/chrome_extension/manifest.json +3 -1
  27. vibe_surf/chrome_extension/scripts/api-client.js +13 -0
  28. vibe_surf/chrome_extension/scripts/file-manager.js +482 -0
  29. vibe_surf/chrome_extension/scripts/history-manager.js +658 -0
  30. vibe_surf/chrome_extension/scripts/modal-manager.js +487 -0
  31. vibe_surf/chrome_extension/scripts/session-manager.js +52 -11
  32. vibe_surf/chrome_extension/scripts/settings-manager.js +1214 -0
  33. vibe_surf/chrome_extension/scripts/ui-manager.js +1530 -3163
  34. vibe_surf/chrome_extension/sidepanel.html +47 -7
  35. vibe_surf/chrome_extension/styles/activity.css +934 -0
  36. vibe_surf/chrome_extension/styles/base.css +76 -0
  37. vibe_surf/chrome_extension/styles/history-modal.css +791 -0
  38. vibe_surf/chrome_extension/styles/input.css +568 -0
  39. vibe_surf/chrome_extension/styles/layout.css +186 -0
  40. vibe_surf/chrome_extension/styles/responsive.css +454 -0
  41. vibe_surf/chrome_extension/styles/settings-environment.css +165 -0
  42. vibe_surf/chrome_extension/styles/settings-forms.css +389 -0
  43. vibe_surf/chrome_extension/styles/settings-modal.css +141 -0
  44. vibe_surf/chrome_extension/styles/settings-profiles.css +244 -0
  45. vibe_surf/chrome_extension/styles/settings-responsive.css +144 -0
  46. vibe_surf/chrome_extension/styles/settings-utilities.css +25 -0
  47. vibe_surf/chrome_extension/styles/variables.css +54 -0
  48. vibe_surf/cli.py +5 -22
  49. vibe_surf/common.py +35 -0
  50. vibe_surf/llm/openai_compatible.py +148 -93
  51. vibe_surf/logger.py +99 -0
  52. vibe_surf/{controller/vibesurf_tools.py → tools/browser_use_tools.py} +233 -221
  53. vibe_surf/tools/file_system.py +415 -0
  54. vibe_surf/{controller → tools}/mcp_client.py +4 -3
  55. vibe_surf/tools/report_writer_tools.py +21 -0
  56. vibe_surf/tools/vibesurf_tools.py +657 -0
  57. vibe_surf/tools/views.py +120 -0
  58. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/METADATA +23 -3
  59. vibesurf-0.1.11.dist-info/RECORD +93 -0
  60. vibe_surf/chrome_extension/styles/main.css +0 -2338
  61. vibe_surf/chrome_extension/styles/settings.css +0 -1100
  62. vibe_surf/controller/file_system.py +0 -53
  63. vibe_surf/controller/views.py +0 -37
  64. vibesurf-0.1.9a6.dist-info/RECORD +0 -71
  65. /vibe_surf/{controller → tools}/__init__.py +0 -0
  66. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/WHEEL +0 -0
  67. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/entry_points.txt +0 -0
  68. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/licenses/LICENSE +0 -0
  69. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.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,174 +337,163 @@ 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)}"))
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
+ if browser_tabs:
367
+ browser_tabs_info = {}
368
+ for tab in browser_tabs:
369
+ browser_tabs_info[tab.target_id[-4:]] = {
370
+ "page_title": tab.title,
371
+ "page_url": tab.url,
372
+ }
373
+ context_info.append(
374
+ f"Current Available Browser Tabs:\n{json.dumps(browser_tabs_info, ensure_ascii=False, indent=2)}\n")
375
+ if active_browser_tab:
376
+ context_info.append(f"Current Active Browser Tab:{active_browser_tab.target_id[-4:]}\n")
377
+ if state.browser_results:
378
+ results_md = format_browser_results(state.browser_results)
379
+ context_info.append(f"Previous Browser Results:\n{results_md}\n")
380
+ if state.generated_report_result:
381
+ if state.generated_report_result.success:
382
+ context_info.append(f"Generated Report: ✅ Success - {state.generated_report_result.report_path}\n")
383
+ else:
384
+ context_info.append(f"Generated Report: ❌ Failed - {state.generated_report_result.msg}\nPath: {state.generated_report_result.report_path}\n")
471
385
 
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}"))
386
+ context_str = "\n".join(context_info) if context_info else "No additional context available."
387
+ vibesurf_agent.message_history.append(UserMessage(content=context_str))
477
388
 
478
- # Reset prev_browser_results
479
- state.prev_browser_results = []
480
389
  try:
481
- response = await state.llm.ainvoke(supervisor_message_history)
482
- # add result to message history
483
- supervisor_message_history.append(AssistantMessage(content=response.completion))
484
-
485
- supervisor_result = parse_supervisor_response(response.completion)
486
-
487
- action = supervisor_result["action"]
488
- reasoning = supervisor_result["reasoning"]
489
- # Log agent activity
490
- log_agent_activity(state, "supervisor_agent", "thinking", f"{reasoning}")
491
-
492
- state.supervisor_action = action
493
-
494
- logger.info(f"🎯 Supervisor decision: {action} - {reasoning}")
495
-
496
- # Handle different actions
497
- if action == "generate_todos":
498
- # Generate initial todos
499
- todo_items = supervisor_result.get("todo_items", [])
500
- if todo_items:
501
- state.todo_list = [TodoItem(task=task) for task in todo_items]
502
- todo_todo_list_md = format_todo_list_markdown(state.todo_list)
503
- supervisor_message_history.append(
504
- UserMessage(content=f"Successfully generated todo list:\n{todo_todo_list_md}"))
505
- log_agent_activity(state, "supervisor_agent", "result", f"Todo List:\n\n{todo_todo_list_md}")
506
- # Continue in supervisor to assign tasks
507
- state.current_step = "supervisor_agent"
508
-
509
- elif action == "update_todos":
510
- # Replace all remaining todos with the new list
511
- todo_items = supervisor_result.get("todo_items", [])
512
- if todo_items:
513
- # Clear current todo list and replace with new items
514
- state.todo_list = [TodoItem(task=task) for task in todo_items]
515
- todo_todo_list_md = format_todo_list_markdown(state.completed_todos + state.todo_list)
516
- supervisor_message_history.append(
517
- UserMessage(content=f"Successfully Updated todo list:\n{todo_todo_list_md}"))
518
- log_agent_activity(state, "supervisor_agent", "result", f"Todo List:\n\n{todo_todo_list_md}")
519
- else:
520
- # If no todo_items provided, clear the list
521
- state.todo_list = []
522
- todo_todo_list_md = format_todo_list_markdown(state.completed_todos + state.todo_list)
523
- supervisor_message_history.append(
524
- UserMessage(content=f"Cleared todo list - all tasks completed:\n{todo_todo_list_md}"))
525
- log_agent_activity(state, "supervisor_agent", "result",
526
- f"Cleared todo list - all tasks completed\n{todo_todo_list_md}")
527
-
528
- # Continue in supervisor to assign tasks
529
- state.current_step = "supervisor_agent"
530
-
531
- elif action == "assign_browser_task":
532
- # Assign browser tasks
533
- task_type = supervisor_result.get("task_type", "single")
534
- tasks_to_execute = supervisor_result.get("tasks_to_execute", [])
535
-
536
- if tasks_to_execute:
537
- tasks_to_execute_new = []
538
- todo_indices = [] # Track which todo items are being executed
539
-
540
- for task_item in tasks_to_execute:
541
- if isinstance(task_item, list):
542
- # Format: [page_index, todo_index]
543
- page_index, todo_index = task_item
544
- if todo_index < len(state.todo_list):
545
- task_description = state.todo_list[todo_index].task
546
- tasks_to_execute_new.append([browser_tabs[page_index].target_id, task_description])
547
- todo_indices.append(todo_index)
548
- else:
549
- # Format: todo_index
550
- todo_index = task_item
551
- if todo_index < len(state.todo_list):
552
- task_description = state.todo_list[todo_index].task
553
- tasks_to_execute_new.append(task_description)
554
- todo_indices.append(todo_index)
555
-
556
- state.execution_mode = ExecutionMode(
557
- mode=task_type,
558
- reason=reasoning
559
- )
560
- state.pending_tasks = tasks_to_execute_new
561
- state.pending_todo_indices = todo_indices # Store which todo indices are being executed
390
+ # Get LLM response with action output format
391
+ response = await vibesurf_agent.llm.ainvoke(vibesurf_agent.message_history, output_format=AgentOutput)
392
+ parsed = response.completion
393
+ actions = parsed.action
394
+ vibesurf_agent.message_history.append(
395
+ AssistantMessage(content=json.dumps(response.completion.model_dump(exclude_none=True, exclude_unset=True),
396
+ ensure_ascii=False)))
397
+
398
+ # Log thinking if present
399
+ if hasattr(parsed, 'thinking') and parsed.thinking:
400
+ await log_agent_activity(state, agent_name, "thinking", parsed.thinking)
401
+
402
+ for i, action in enumerate(actions):
403
+ action_data = action.model_dump(exclude_unset=True)
404
+ action_name = next(iter(action_data.keys())) if action_data else 'unknown'
405
+ logger.info(f"🛠️ Processing VibeSurf action {i + 1}/{len(actions)}: {action_name}")
406
+
407
+ # Check for special routing actions
408
+ if action_name == 'execute_browser_use_agent':
409
+ # Route to browser task execution node
410
+ params = action_data[action_name]
411
+ state.browser_tasks = params.get('tasks', [])
412
+ state.current_action = 'execute_browser_use_agent'
413
+ state.action_params = params
562
414
  state.current_step = "browser_task_execution"
563
415
 
564
- log_agent_activity(state, "supervisor_agent", "result",
565
- f"Assigned {len(tasks_to_execute)} browser tasks ({task_type} mode)")
566
- else:
567
- # No tasks to execute, continue in supervisor
568
- state.current_step = "supervisor_agent"
569
- supervisor_message_history.append(
570
- UserMessage(content=f"No tasks to execute. Please provide browser tasks to execute."))
571
-
572
- elif action == "assign_report_task":
573
- # Assign report generation task
574
- state.current_step = "report_task_execution"
575
- log_agent_activity(state, "supervisor_agent", "result", "Assigned report generation task")
576
-
577
- elif action == "simple_response":
578
- # Use provided content or generate if not provided
579
- state.current_step = "simple_response"
580
- state.simple_response = supervisor_result["simple_response_content"]
581
- state.is_complete = True
582
- log_agent_activity(state, "supervisor_agent", "result", state.simple_response)
583
- elif action == "summary_generation":
584
- # Handle summary generation directly in supervisor
585
- summary_content = supervisor_result.get("summary_content")
586
-
587
- if summary_content:
588
- # Use LLM-provided summary content
589
- state.final_summary = summary_content
416
+ # Log agent activity
417
+ browser_tasks_md = []
418
+ for browser_task in state.browser_tasks:
419
+ bu_task = browser_task.get('task', "")
420
+ if bu_task:
421
+ browser_tasks_md.append(f"- [ ] {bu_task}")
422
+ browser_tasks_md = '\n'.join(browser_tasks_md)
423
+ agent_msg = f"Routing to browser task execution with {len(state.browser_tasks)} browser tasks:\n\n{browser_tasks_md}"
424
+ await log_agent_activity(state, agent_name, "working", agent_msg)
425
+ logger.debug(agent_msg)
426
+ return state
427
+
428
+ elif action_name == 'execute_report_writer_agent':
429
+ # Route to report task execution node
430
+ params = action_data[action_name]
431
+ state.current_action = 'execute_report_writer_agent'
432
+ state.action_params = params
433
+ state.current_step = "report_task_execution"
434
+ report_task = params.get('task', "")
435
+ agent_msg = f"Routing to report generation with task:\n{report_task}"
436
+ await log_agent_activity(state, agent_name, "working", agent_msg)
437
+ return state
438
+
439
+ elif action_name == 'task_done':
440
+ # Handle response/completion - direct to END
441
+ params = action_data[action_name]
442
+ response_content = params.get('response', 'Task completed!')
443
+ follow_tasks = params.get('suggestion_follow_tasks', [])
444
+ state.current_step = "END"
445
+
446
+ # Format final response
447
+ final_response = f"{response_content}"
448
+ await log_agent_activity(state, agent_name, "result", final_response)
449
+
450
+ if follow_tasks:
451
+ await log_agent_activity(state, agent_name, "suggestion_tasks",
452
+ '\n'.join(follow_tasks))
453
+ final_response += "\n\n## Suggested Follow-up Tasks:\n"
454
+ for j, task in enumerate(follow_tasks[:3], 1):
455
+ final_response += f"{j}. {task}\n"
456
+
457
+ state.final_response = final_response
458
+ logger.debug(final_response)
590
459
  state.is_complete = True
591
- state.current_step = "summary_generation"
592
- log_agent_activity(state, "supervisor_agent", "result", f"{summary_content}")
460
+ return state
461
+
593
462
  else:
594
- # Generate summary using the same logic as the old summary generation node
595
- state.current_step = "supervisor_agent"
596
- supervisor_message_history.append(
597
- UserMessage(content=f"The summary content is empty. Please provide summary content if you think all requirements have been accomplished."))
598
- else:
599
- # Unknown action, default to complete workflow
600
- state.current_step = "summary_generation"
601
- log_agent_activity(state, "supervisor_agent", "error", f"Unknown action: {action}")
463
+ if "todos" in action_name:
464
+ todo_content = await vibesurf_agent.file_system.read_file('todo.md')
465
+ action_msg = f"{action_name}:\n\n{todo_content}"
466
+ logger.debug(action_msg)
467
+ await log_agent_activity(state, agent_name, "working", action_msg)
468
+ else:
469
+ action_msg = f"**⚡ Actions:**\n"
470
+ action_msg += f"```json\n{json.dumps(action_data, indent=2, ensure_ascii=False)}\n```"
471
+ logger.debug(action_msg)
472
+ await log_agent_activity(state, agent_name, "working", action_msg)
473
+
474
+ result = await vibesurf_agent.tools.act(
475
+ action=action,
476
+ browser_manager=vibesurf_agent.browser_manager,
477
+ llm=vibesurf_agent.llm,
478
+ file_system=vibesurf_agent.file_system,
479
+ )
480
+ state.current_step = "vibesurf_agent"
481
+ if result.extracted_content:
482
+ vibesurf_agent.message_history.append(
483
+ UserMessage(content=f'Action result:\n{result.extracted_content}'))
484
+ await log_agent_activity(state, agent_name, "result", result.extracted_content)
485
+
486
+ if result.error:
487
+ vibesurf_agent.message_history.append(UserMessage(content=f'Action error:\n{result.error}'))
488
+ await log_agent_activity(state, agent_name, "error", result.error)
602
489
 
603
490
  return state
604
491
 
605
492
  except Exception as e:
606
- logger.error(f"❌ Supervisor agent failed: {e}")
607
- state.current_step = "summary_generation"
608
- log_agent_activity(state, "supervisor_agent", "error", f"Supervisor failed: {str(e)}")
493
+ logger.error(f"❌ VibeSurf agent failed: {e}")
494
+ state.final_response = f"Task execution failed: {str(e)}"
495
+ state.is_complete = True
496
+ await log_agent_activity(state, agent_name, "error", f"Agent failed: {str(e)}")
609
497
  return state
610
498
 
611
499
 
@@ -617,187 +505,132 @@ async def browser_task_execution_node(state: VibeSurfState) -> VibeSurfState:
617
505
 
618
506
 
619
507
  async def _browser_task_execution_node_impl(state: VibeSurfState) -> VibeSurfState:
620
- """Implementation of browser task execution node"""
621
- logger.info("🚀 Executing browser tasks assigned by supervisor...")
622
-
623
- # Log agent activity
624
- log_agent_activity(state, "browser_task_executor", "working",
625
- f"Executing {len(state.pending_tasks)} browser tasks in {state.execution_mode.mode if state.execution_mode else 'single'} mode")
626
-
627
- # Setup file organization
628
- ensure_directories(state)
629
-
508
+ """Implementation of browser task execution node - simplified tab-based approach"""
509
+ logger.info("🚀 Executing browser tasks assigned by vibesurf agent...")
630
510
  try:
631
- if state.execution_mode and state.execution_mode.mode == "parallel":
632
- # Execute tasks in parallel
633
- results = await execute_parallel_browser_tasks(state)
511
+ task_count = len(state.browser_tasks)
512
+ if task_count == 0:
513
+ raise ValueError("No browser tasks assigned. Please assign 1 task at least.")
514
+
515
+ if task_count <= 1:
516
+ # Single task execution
517
+ logger.info("📝 Using single execution for single task")
518
+ result = await execute_single_browser_tasks(state)
519
+ results = [result]
520
+ # Update browser results
521
+ state.browser_results.extend(results)
634
522
  else:
635
- # Execute tasks in single mode
636
- results = await execute_single_browser_tasks(state)
637
-
638
- # Update browser results
639
- state.prev_browser_results = copy.deepcopy(results)
640
- state.browser_results.extend(results)
641
-
642
- # Mark corresponding todos as completed using indices
643
- for i, todo_index in enumerate(state.pending_todo_indices):
644
- if todo_index < len(state.todo_list) and state.todo_list[todo_index].status != "completed":
645
- todo = state.todo_list[todo_index]
646
- todo.status = "completed"
647
- if i < len(results):
648
- todo.result = results[i].result if results[i].success else None
649
- todo.error = results[i].error if not results[i].success else None
650
- state.completed_todos.append(todo)
651
-
652
- # Remove completed todos from the todo list
653
- # Sort indices in reverse order to avoid index shifting issues
654
- for todo_index in sorted(state.pending_todo_indices, reverse=True):
655
- if todo_index < len(state.todo_list):
656
- state.todo_list.pop(todo_index)
657
-
658
- # Clear pending tasks and indices
659
- state.pending_tasks = []
660
- state.pending_todo_indices = []
661
-
662
- # Return to supervisor for next decision
663
- state.current_step = "supervisor_agent"
523
+ # Multiple tasks execution - parallel approach
524
+ logger.info(f"🚀 Using parallel execution for {task_count} tasks")
525
+ results = await execute_parallel_browser_tasks(state)
526
+ # Update browser results
527
+ state.browser_results.extend(results)
528
+
529
+ # Return to vibesurf agent for next decision
530
+ state.current_step = "vibesurf_agent"
664
531
 
665
532
  # Log result
666
533
  successful_tasks = sum(1 for result in results if result.success)
667
- log_agent_activity(state, "browser_task_executor", "result",
668
- f"Browser execution completed: {successful_tasks}/{len(results)} tasks successful")
534
+ await log_agent_activity(state, "browser_task_executor", "result",
535
+ f"Browser execution completed: {successful_tasks}/{len(results)} tasks successful")
669
536
 
670
537
  logger.info(f"✅ Browser task execution completed with {len(results)} results")
671
538
  return state
672
539
 
673
540
  except Exception as e:
674
541
  logger.error(f"❌ Browser task execution failed: {e}")
675
-
676
- # Create error results for pending tasks
677
- error_results = []
678
- for i, task in enumerate(state.pending_tasks):
679
- # Get the actual task description for the error result
680
- if isinstance(task, list):
681
- task_description = task[1] # [target_id, task_description]
682
- else:
683
- task_description = task
684
- error_results.append(BrowserTaskResult(
685
- agent_id="error",
686
- task=task_description,
687
- success=False,
688
- error=str(e)
689
- ))
690
-
691
- state.browser_results.extend(error_results)
692
- state.pending_tasks = []
693
- state.pending_todo_indices = []
694
- state.current_step = "supervisor_agent"
695
-
696
- log_agent_activity(state, "browser_task_executor", "error", f"Browser execution failed: {str(e)}")
697
- return state
698
-
699
-
700
- async def report_task_execution_node(state: VibeSurfState) -> VibeSurfState:
701
- """
702
- Execute HTML report generation task assigned by supervisor agent
703
- """
704
- return await control_aware_node(_report_task_execution_node_impl, state, "report_task_execution")
705
-
706
-
707
- async def _report_task_execution_node_impl(state: VibeSurfState) -> VibeSurfState:
708
- """Implementation of report task execution node"""
709
- logger.info("📄 Executing HTML report generation task...")
710
-
711
- # Log agent activity
712
- log_agent_activity(state, "report_task_executor", "working", "Generating HTML report")
713
-
714
- try:
715
- # Use ReportWriterAgent to generate HTML report
716
- report_writer = ReportWriterAgent(
717
- llm=state.llm,
718
- workspace_dir=state.task_dir
542
+ import traceback
543
+ traceback.print_exc()
544
+ state.browser_results.append(BrowserTaskResult(
545
+ agent_id="unknown",
546
+ agent_workdir="unknown",
547
+ task='unknown',
548
+ success=False,
549
+ error=str(e)
719
550
  )
551
+ )
552
+ state.current_step = "vibesurf_agent"
720
553
 
721
- report_data = {
722
- "original_task": state.original_task,
723
- "execution_results": state.browser_results,
724
- "report_type": "detailed", # Default to detailed report
725
- "upload_files": state.upload_files
726
- }
727
-
728
- report_path = await report_writer.generate_report(report_data)
729
-
730
- state.generated_report_path = report_path
731
-
732
- # Return to supervisor for next decision
733
- state.current_step = "supervisor_agent"
734
-
735
- log_agent_activity(state, "report_task_executor", "result",
736
- f"HTML report generated successfully at: `{report_path}`")
737
-
738
- logger.info(f"✅ Report generated: {report_path}")
739
- return state
740
-
741
- except Exception as e:
742
- logger.error(f"❌ Report generation failed: {e}")
743
- state.current_step = "supervisor_agent"
744
- log_agent_activity(state, "report_task_executor", "error", f"Report generation failed: {str(e)}")
554
+ await log_agent_activity(state, "browser_task_executor", "error", f"Browser execution failed: {str(e)}")
745
555
  return state
746
556
 
747
557
 
748
- async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTaskResult]:
558
+ async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTaskResult] | None:
749
559
  """Execute pending tasks in parallel using multiple browser agents"""
750
560
  logger.info("🔄 Executing pending tasks in parallel...")
751
561
 
752
562
  # Register agents with browser manager
753
563
  agents = []
754
- pending_tasks = state.pending_tasks
564
+ pending_tasks = state.browser_tasks
755
565
  bu_agent_ids = []
756
566
  register_sessions = []
757
- for i, task in enumerate(pending_tasks):
758
- agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
759
- if isinstance(task, list):
760
- target_id, task_description = task
761
- else:
762
- task_description = task
763
- target_id = None
567
+ task_id = nanoid.generate(size=5)
568
+ bu_agents_workdir = state.vibesurf_agent.file_system.get_dir() / "bu_agents"
569
+ bu_agents_workdir.mkdir(parents=True, exist_ok=True)
570
+
571
+ for i, task_info in enumerate(pending_tasks):
572
+ agent_id = f"bu_agent-{task_id}-{i + 1:03d}"
573
+ task_description = task_info.get('task', '')
574
+ if not task_description:
575
+ continue
576
+ target_id = task_info.get('target_id', None)
764
577
  register_sessions.append(
765
- state.browser_manager.register_agent(agent_id, target_id=target_id)
578
+ state.vibesurf_agent.browser_manager.register_agent(agent_id, target_id=target_id)
766
579
  )
767
580
  bu_agent_ids.append(agent_id)
768
581
  agent_browser_sessions = await asyncio.gather(*register_sessions)
769
582
 
770
- for i, task in enumerate(pending_tasks):
771
- agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
772
- if isinstance(task, list):
773
- target_id, task_description = task
774
- else:
775
- task_description = task
583
+ vibesurf_tools = state.vibesurf_agent.tools
584
+ bu_tools = BrowserUseTools()
585
+ for mcp_server_name, mcp_client in vibesurf_tools.mcp_clients.items():
586
+ await mcp_client.register_to_tools(
587
+ tools=bu_tools,
588
+ prefix=f"mcp.{mcp_server_name}."
589
+ )
590
+ bu_tasks = [None] * len(pending_tasks)
591
+ for i, task_info in enumerate(pending_tasks):
592
+ agent_id = f"bu_agent-{task_id}-{i + 1:03d}"
593
+ task_description = task_info.get('task', '')
594
+ if not task_description:
595
+ continue
596
+ task_files = task_info.get('task_files', [])
597
+ bu_agent_workdir = bu_agents_workdir / f"{task_id}-{i + 1:03d}"
598
+ bu_agent_workdir.mkdir(parents=True, exist_ok=True)
599
+ agent_name = f"browser_use_agent-{task_id}-{i + 1:03d}"
600
+ # Log agent creation
601
+ await log_agent_activity(state, agent_name, "working", f"{task_description}")
602
+
776
603
  try:
777
- # Log agent creation
778
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "working",
779
- f"{task_description}")
604
+ available_file_paths = []
605
+ if task_files:
606
+ for task_file in task_files:
607
+ upload_workdir = bu_agent_workdir / "upload_files"
608
+ upload_workdir.mkdir(parents=True, exist_ok=True)
609
+ task_file_path = state.vibesurf_agent.file_system.get_absolute_path(task_file)
610
+ if os.path.exists(task_file_path):
611
+ logger.info(f"Copy {task_file_path} to {upload_workdir}")
612
+ shutil.copy(task_file_path, str(upload_workdir))
613
+ available_file_paths.append(os.path.join("upload_files", os.path.basename(task_file_path)))
780
614
 
781
615
  # Create BrowserUseAgent for each task
782
- if state.upload_files:
783
- upload_files_md = format_upload_files_list(state.upload_files)
784
- bu_task = task_description + f"\nAvailable uploaded files:\n{upload_files_md}\n"
616
+ if available_file_paths:
617
+ upload_files_md = '\n'.join(available_file_paths)
618
+ bu_task = task_description + f"\nNecessary files for this task:\n{upload_files_md}\n"
785
619
  else:
786
620
  bu_task = task_description
787
-
621
+ bu_tasks[i] = bu_task
788
622
  # Create step callback for this agent
789
- agent_name = f"browser_use_agent-{i + 1}-{state.task_id[-4:]}"
790
623
  step_callback = create_browser_agent_step_callback(state, agent_name)
791
-
792
624
  agent = BrowserUseAgent(
793
625
  task=bu_task,
794
- llm=state.llm,
626
+ llm=state.vibesurf_agent.llm,
795
627
  browser_session=agent_browser_sessions[i],
796
- controller=state.vibesurf_controller,
797
- task_id=f"{state.task_id}-{i + 1}",
798
- file_system_path=state.task_dir,
628
+ tools=bu_tools,
629
+ task_id=f"{task_id}-{i + 1:03d}",
630
+ file_system_path=str(bu_agent_workdir),
799
631
  register_new_step_callback=step_callback,
800
- 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.",
632
+ extend_system_message=EXTEND_BU_SYSTEM_PROMPT,
633
+ token_cost_service=state.vibesurf_agent.token_cost_service
801
634
  )
802
635
  agents.append(agent)
803
636
 
@@ -807,9 +640,11 @@ async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTa
807
640
  logger.debug(f"🔗 Registered parallel agent {agent_id} for control coordination")
808
641
 
809
642
  except Exception as e:
643
+ import traceback
644
+ traceback.print_exc()
810
645
  logger.error(f"❌ Failed to create agent {agent_id}: {e}")
811
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
812
- f"Failed to create agent: {str(e)}")
646
+ await log_agent_activity(state, agent_name, "error",
647
+ f"Failed to create agent: {str(e)}")
813
648
 
814
649
  # Execute all agents in parallel
815
650
  try:
@@ -818,155 +653,284 @@ async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTa
818
653
  # Process results
819
654
  results = []
820
655
  for i, (agent, history) in enumerate(zip(agents, histories)):
821
- agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
656
+ task = bu_tasks[i]
657
+ bu_agent_workdir = f"bu_agents/{task_id}-{i + 1:03d}"
658
+ agent_name = f"browser_use_agent-{task_id}-{i + 1:03d}"
659
+
660
+ important_files = []
661
+ if history and history.history and len(history.history[-1].result) > 0:
662
+ last_result = history.history[-1].result[-1]
663
+ important_files = last_result.attachments
664
+ if important_files:
665
+ important_files = [os.path.join(bu_agent_workdir, file_name) for file_name in important_files]
666
+
822
667
  if isinstance(history, Exception):
823
668
  results.append(BrowserTaskResult(
824
- agent_id=f"agent-{i + 1}",
825
- task=pending_tasks[i],
669
+ agent_id=f"{task_id}-{i + 1:03d}",
670
+ task=task,
826
671
  success=False,
827
- error=str(history)
672
+ error=str(history),
673
+ agent_workdir=bu_agent_workdir,
674
+ important_files=important_files,
828
675
  ))
829
676
  # Log error
830
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
831
- f"Task failed: {str(history)}")
677
+ await log_agent_activity(state, agent_name, "error", f"Task failed: {str(history)}")
832
678
  else:
833
679
  results.append(BrowserTaskResult(
834
- agent_id=f"agent-{i + 1}",
835
- task=pending_tasks[i],
680
+ agent_id=f"{task_id}-{i + 1:03d}",
681
+ task=task,
682
+ agent_workdir=bu_agent_workdir,
836
683
  success=history.is_successful(),
684
+ important_files=important_files,
837
685
  result=history.final_result() if hasattr(history, 'final_result') else "Task completed",
838
686
  error=str(history.errors()) if history.has_errors() and not history.is_successful() else ""
839
687
  ))
840
688
  # Log result
841
689
  if history.is_successful():
842
- result_text = history.final_result() if hasattr(history, 'final_result') else "Task completed"
843
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "result",
844
- f"Task completed successfully: \n{result_text}")
690
+ result_text = history.final_result()
691
+ await log_agent_activity(state, agent_name, "result",
692
+ f"Task completed successfully: \n{result_text}")
845
693
  else:
846
- error_text = str(history.errors()) if history.has_errors() else "Unknown error"
847
- log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
848
- f"Task failed: {error_text}")
694
+ error_text = str(history.errors())
695
+ await log_agent_activity(state, agent_name, "error", f"Task failed: {error_text}")
849
696
 
850
697
  return results
851
698
 
699
+ except Exception as e:
700
+ import traceback
701
+ traceback.print_exc()
702
+
852
703
  finally:
853
704
  # Remove agents from control tracking and cleanup browser sessions
854
705
  for i, agent_id in enumerate(bu_agent_ids):
855
706
  if not isinstance(pending_tasks[i], list):
856
- await state.browser_manager.unregister_agent(agent_id, close_tabs=True)
707
+ await state.vibesurf_agent.browser_manager.unregister_agent(agent_id, close_tabs=True)
857
708
  if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
858
709
  state.vibesurf_agent._running_agents.pop(agent_id, None)
859
710
  logger.debug(f"🔗 Unregistered parallel agent {agent_id} from control coordination")
860
711
 
861
712
 
862
- async def execute_single_browser_tasks(state: VibeSurfState) -> List[BrowserTaskResult]:
713
+ async def execute_single_browser_tasks(state: VibeSurfState) -> BrowserTaskResult | None:
863
714
  """Execute pending tasks in single mode one by one"""
864
715
  logger.info("🔄 Executing pending tasks in single mode...")
716
+ task_info = state.browser_tasks[0]
717
+ task_id = nanoid.generate(size=5)
718
+ bu_agents_workdir = state.vibesurf_agent.file_system.get_dir() / "bu_agents"
719
+ bu_agents_workdir.mkdir(parents=True, exist_ok=True)
720
+ task_description = task_info.get('task', '')
721
+ if not task_description:
722
+ return BrowserTaskResult(
723
+ agent_id=f"{task_id}-{1:03d}",
724
+ task='',
725
+ agent_workdir=f"bu_agents/{task_id}-{1:03d}",
726
+ success=False,
727
+ error="Task description is empty. Please provide a valid task description for browser use agent.",
728
+ )
729
+
730
+ task_files = task_info.get('task_files', [])
731
+ bu_agent_workdir = bu_agents_workdir / f"{task_id}-{1:03d}"
732
+ bu_agent_workdir.mkdir(parents=True, exist_ok=True)
733
+ agent_name = f"browser_use_agent-{task_id}-{1:03d}"
734
+ agent_id = f"bu_agent-{task_id}-{1:03d}"
735
+ # Log agent creation
736
+ await log_agent_activity(state, agent_name, "working", f"{task_description}")
865
737
 
866
- results = []
867
- for i, task in enumerate(state.pending_tasks):
868
- if isinstance(task, list):
869
- target_id, task_description = task
738
+ try:
739
+ vibesurf_tools = state.vibesurf_agent.tools
740
+ bu_tools = BrowserUseTools()
741
+ for mcp_server_name, mcp_client in vibesurf_tools.mcp_clients.items():
742
+ await mcp_client.register_to_tools(
743
+ tools=bu_tools,
744
+ prefix=f"mcp.{mcp_server_name}."
745
+ )
746
+ available_file_paths = []
747
+ if task_files:
748
+ for task_file in task_files:
749
+ upload_workdir = bu_agent_workdir / "upload_files"
750
+ upload_workdir.mkdir(parents=True, exist_ok=True)
751
+ task_file_path = state.vibesurf_agent.file_system.get_absolute_path(task_file)
752
+ if os.path.exists(task_file_path):
753
+ logger.info(f"Copy {task_file_path} to {upload_workdir}")
754
+ shutil.copy(task_file_path, str(upload_workdir))
755
+ available_file_paths.append(os.path.join("upload_files", os.path.basename(task_file_path)))
756
+
757
+ # Create BrowserUseAgent for each task
758
+ if available_file_paths:
759
+ upload_files_md = '\n'.join(available_file_paths)
760
+ bu_task = task_description + f"\nNecessary files for this task:\n{upload_files_md}\n"
761
+ else:
762
+ bu_task = task_description
763
+
764
+ step_callback = create_browser_agent_step_callback(state, agent_name)
765
+ main_browser_session = state.vibesurf_agent.browser_manager.main_browser_session
766
+ if task_info.get("tab_id", None):
767
+ tab_id = task_info.get("tab_id")
768
+ target_id = await main_browser_session.get_target_id_from_tab_id(tab_id)
769
+ await main_browser_session.get_or_create_cdp_session(target_id=target_id)
870
770
  else:
871
- task_description = task
872
- logger.info(f"🔄 Executing task ({i + 1}/{len(state.pending_tasks)}): {task_description}")
771
+ new_target = await main_browser_session.cdp_client.send.Target.createTarget(
772
+ params={'url': 'about:blank'})
773
+ target_id = new_target["targetId"]
774
+ await main_browser_session.get_or_create_cdp_session(target_id=target_id)
775
+ agent = BrowserUseAgent(
776
+ task=bu_task,
777
+ llm=state.vibesurf_agent.llm,
778
+ browser_session=main_browser_session,
779
+ tools=bu_tools,
780
+ task_id=f"{task_id}-{1:03d}",
781
+ file_system_path=str(bu_agent_workdir),
782
+ register_new_step_callback=step_callback,
783
+ extend_system_message=EXTEND_BU_SYSTEM_PROMPT,
784
+ token_cost_service=state.vibesurf_agent.token_cost_service
785
+ )
786
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
787
+ state.vibesurf_agent._running_agents[agent_id] = agent
788
+ logger.debug(f"🔗 Registered single agent {agent_id} for control coordination")
789
+
790
+ history = await agent.run()
791
+ bu_agent_workdir = f"bu_agents/{task_id}-{1:03d}"
792
+ important_files = []
793
+ if history and history.history and len(history.history[-1].result) > 0:
794
+ last_result = history.history[-1].result[-1]
795
+ important_files = last_result.attachments
796
+ if important_files:
797
+ important_files = [os.path.join(bu_agent_workdir, file_name) for file_name in important_files]
798
+
799
+ result = BrowserTaskResult(
800
+ agent_id=agent_id,
801
+ agent_workdir=bu_agent_workdir,
802
+ task=bu_task,
803
+ important_files=important_files,
804
+ success=history.is_successful(),
805
+ result=history.final_result() if hasattr(history, 'final_result') else "Task completed",
806
+ error=str(history.errors()) if history.has_errors() and not history.is_successful() else ""
807
+ )
873
808
 
874
- agent_id = f"agent-single-{state.task_id[-4:]}-{i}"
809
+ # Log result
810
+ if result.success:
811
+ await log_agent_activity(state, agent_name, "result",
812
+ f"Task completed successfully: \n{result.result}")
813
+ else:
814
+ await log_agent_activity(state, agent_name, "error",
815
+ f"Task failed: {result.error}")
816
+ return result
875
817
 
876
- # Log agent activity
877
- log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "working", f"{task_description}")
818
+ except Exception as e:
819
+ import traceback
820
+ traceback.print_exc()
821
+
822
+ bu_agent_workdir = f"bu_agents/{task_id}-{1:03d}"
823
+ return BrowserTaskResult(
824
+ agent_id=agent_id,
825
+ agent_workdir=bu_agent_workdir,
826
+ task=task_description,
827
+ success=False,
828
+ error=str(e)
829
+ )
830
+ finally:
831
+ # Remove agent from control tracking
832
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
833
+ state.vibesurf_agent._running_agents.pop(agent_id, None)
834
+ logger.debug(f"🔗 Unregistered single agent {agent_id} from control coordination")
878
835
 
879
- try:
880
- await state.browser_manager._get_active_target()
881
- if state.upload_files:
882
- upload_files_md = format_upload_files_list(state.upload_files)
883
- bu_task = task_description + f"\nAvailable user uploaded files:\n{upload_files_md}\n"
884
- else:
885
- bu_task = task_description
886
- # Create step callback for this agent
887
- agent_name = f"browser_use_agent-{state.task_id[-4:]}"
888
- step_callback = create_browser_agent_step_callback(state, agent_name)
889
-
890
- agent = BrowserUseAgent(
891
- task=bu_task,
892
- llm=state.llm,
893
- browser_session=state.browser_manager.main_browser_session,
894
- controller=state.vibesurf_controller,
895
- task_id=f"{state.task_id}-{i}",
896
- file_system_path=state.task_dir,
897
- register_new_step_callback=step_callback,
898
- 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."
899
- )
900
836
 
901
- # Track agent in VibeSurfAgent for control coordination
902
- if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
903
- state.vibesurf_agent._running_agents[agent_id] = agent
904
- logger.debug(f"🔗 Registered single agent {agent_id} for control coordination")
837
+ async def report_task_execution_node(state: VibeSurfState) -> VibeSurfState:
838
+ """
839
+ Execute HTML report generation task assigned by supervisor agent
840
+ """
841
+ return await control_aware_node(_report_task_execution_node_impl, state, "report_task_execution")
905
842
 
906
- try:
907
- history = await agent.run()
908
843
 
909
- result = BrowserTaskResult(
910
- agent_id=agent_id,
911
- task=task,
912
- success=history.is_successful(),
913
- result=history.final_result() if hasattr(history, 'final_result') else "Task completed",
914
- error=str(history.errors()) if history.has_errors() and not history.is_successful() else ""
915
- )
844
+ async def _report_task_execution_node_impl(state: VibeSurfState) -> VibeSurfState:
845
+ """Implementation of report task execution node"""
846
+ logger.info("📄 Executing HTML report generation task...")
916
847
 
917
- # Log result
918
- if result.success:
919
- log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "result",
920
- f"Task completed successfully: \n{result.result}")
921
- else:
922
- log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "error",
923
- f"Task failed: {result.error}")
848
+ agent_name = "report_writer_agent"
924
849
 
925
- results.append(result)
926
- finally:
927
- # Remove agent from control tracking
928
- if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
929
- state.vibesurf_agent._running_agents.pop(agent_id, None)
930
- logger.debug(f"🔗 Unregistered single agent {agent_id} from control coordination")
850
+ # Log agent activity
851
+ await log_agent_activity(state, agent_name, "working", "Generating HTML report")
931
852
 
932
- except Exception as e:
933
- logger.error(f"❌ Single task execution failed: {e}")
934
- log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "error",
935
- f"Task execution failed: {str(e)}")
936
- results.append(BrowserTaskResult(
937
- agent_id=agent_id,
938
- task=task,
939
- success=False,
940
- error=str(e)
941
- ))
853
+ try:
854
+ # Create step callback for report writer agent
855
+ step_callback = create_report_writer_step_callback(state, agent_name)
856
+
857
+ # Use ReportWriterAgent to generate HTML report
858
+ report_writer = ReportWriterAgent(
859
+ llm=state.vibesurf_agent.llm,
860
+ workspace_dir=str(state.vibesurf_agent.file_system.get_dir()),
861
+ step_callback=step_callback,
862
+ thinking_mode=state.vibesurf_agent.thinking_mode,
863
+ )
864
+
865
+ # Register report writer agent for control coordination
866
+ agent_id = "report_writer_agent"
867
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
868
+ state.vibesurf_agent._running_agents[agent_id] = report_writer
869
+ logger.debug(f"🔗 Registered report writer agent for control coordination")
870
+
871
+ try:
872
+ action_params = state.action_params
873
+ report_task = action_params.get('task', [])
874
+ report_information = {
875
+ "browser_results": [bu_result.model_dump() for bu_result in state.browser_results if bu_result]
876
+ }
877
+ report_data = {
878
+ "report_task": report_task,
879
+ "report_information": report_information
880
+ }
942
881
 
943
- return results
882
+ report_result = await report_writer.generate_report(report_data)
883
+ state.generated_report_result = report_result
944
884
 
885
+ # Return to vibesurf agent for next decision
886
+ state.current_step = "vibesurf_agent"
945
887
 
946
- def route_after_supervisor_agent(state: VibeSurfState) -> str:
947
- """Route based on supervisor agent decisions"""
888
+ if report_result.success:
889
+ await log_agent_activity(state, agent_name, "result",
890
+ f"✅ HTML report generated successfully: {report_result.msg}\nPath: `{report_result.report_path}`")
891
+ logger.info(f"✅ Report generated successfully: {report_result.report_path}")
892
+ else:
893
+ await log_agent_activity(state, agent_name, "error",
894
+ f"❌ Report generation failed: {report_result.msg}\nPath: `{report_result.report_path}`")
895
+ logger.warning(f"⚠️ Report generation failed: {report_result.msg}")
896
+
897
+ finally:
898
+ # Remove report writer agent from control tracking
899
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
900
+ state.vibesurf_agent._running_agents.pop(agent_id, None)
901
+ logger.debug(f"🔗 Unregistered report writer agent from control coordination")
902
+
903
+ return state
904
+
905
+ except Exception as e:
906
+ logger.error(f"❌ Report generation failed: {e}")
907
+ state.current_step = "vibesurf_agent"
908
+ await log_agent_activity(state, agent_name, "error", f"Report generation failed: {str(e)}")
909
+ return state
910
+
911
+
912
+ def route_after_vibesurf_agent(state: VibeSurfState) -> str:
913
+ """Route based on vibesurf agent decisions"""
948
914
  if state.current_step == "browser_task_execution":
949
915
  return "browser_task_execution"
950
916
  elif state.current_step == "report_task_execution":
951
917
  return "report_task_execution"
952
- elif state.current_step == "summary_generation":
953
- return "summary_generation" # Summary generated, go to END
954
- elif state.current_step == "simple_response":
955
- return "simple_response"
956
- elif state.current_step == "supervisor_agent":
957
- return "supervisor_agent" # Continue in supervisor loop
918
+ elif state.current_step == "vibesurf_agent":
919
+ return "vibesurf_agent" # Continue in vibesurf agent loop
920
+ elif state.is_complete:
921
+ return "END" # task_done sets is_complete=True, go directly to END
958
922
  else:
959
923
  return "END" # Default fallback - complete workflow
960
924
 
961
925
 
962
926
  def route_after_browser_task_execution(state: VibeSurfState) -> str:
963
- """Route back to supervisor after browser task completion"""
964
- return "supervisor_agent"
927
+ """Route back to vibesurf agent after browser task completion"""
928
+ return "vibesurf_agent"
965
929
 
966
930
 
967
931
  def route_after_report_task_execution(state: VibeSurfState) -> str:
968
- """Route back to supervisor after report task completion"""
969
- return "supervisor_agent"
932
+ """Route back to vibesurf agent after report task completion"""
933
+ return "vibesurf_agent"
970
934
 
971
935
 
972
936
  def should_continue(state: VibeSurfState) -> str:
@@ -978,38 +942,36 @@ def should_continue(state: VibeSurfState) -> str:
978
942
 
979
943
 
980
944
  def create_vibe_surf_workflow() -> StateGraph:
981
- """Create the simplified LangGraph workflow with supervisor agent as core controller"""
945
+ """Create the simplified LangGraph workflow with supervisor agent as core tools"""
982
946
 
983
947
  workflow = StateGraph(VibeSurfState)
984
948
 
985
949
  # Add nodes for simplified architecture
986
- workflow.add_node("supervisor_agent", supervisor_agent_node)
950
+ workflow.add_node("vibesurf_agent", vibesurf_agent_node)
987
951
  workflow.add_node("browser_task_execution", browser_task_execution_node)
988
952
  workflow.add_node("report_task_execution", report_task_execution_node)
989
953
 
990
954
  # Set entry point
991
- workflow.set_entry_point("supervisor_agent")
955
+ workflow.set_entry_point("vibesurf_agent")
992
956
 
993
- # Supervisor agent routes to different execution nodes or END
957
+ # VibeSurf agent routes to different execution nodes or END
994
958
  workflow.add_conditional_edges(
995
- "supervisor_agent",
996
- route_after_supervisor_agent,
959
+ "vibesurf_agent",
960
+ route_after_vibesurf_agent,
997
961
  {
998
962
  "browser_task_execution": "browser_task_execution",
999
963
  "report_task_execution": "report_task_execution",
1000
- "summary_generation": END,
1001
- "supervisor_agent": "supervisor_agent",
1002
- "simple_response": END,
964
+ "vibesurf_agent": "vibesurf_agent",
1003
965
  "END": END
1004
966
  }
1005
967
  )
1006
968
 
1007
- # Execution nodes return to supervisor
969
+ # Execution nodes return to vibesurf agent
1008
970
  workflow.add_conditional_edges(
1009
971
  "browser_task_execution",
1010
972
  route_after_browser_task_execution,
1011
973
  {
1012
- "supervisor_agent": "supervisor_agent"
974
+ "vibesurf_agent": "vibesurf_agent"
1013
975
  }
1014
976
  )
1015
977
 
@@ -1017,7 +979,7 @@ def create_vibe_surf_workflow() -> StateGraph:
1017
979
  "report_task_execution",
1018
980
  route_after_report_task_execution,
1019
981
  {
1020
- "supervisor_agent": "supervisor_agent"
982
+ "vibesurf_agent": "vibesurf_agent"
1021
983
  }
1022
984
  )
1023
985
 
@@ -1025,24 +987,32 @@ def create_vibe_surf_workflow() -> StateGraph:
1025
987
 
1026
988
 
1027
989
  class VibeSurfAgent:
1028
- """Main LangGraph-based VibeSurfAgent with comprehensive control capabilities"""
990
+ """Main LangGraph-based VibeSurf Agent"""
1029
991
 
1030
992
  def __init__(
1031
993
  self,
1032
994
  llm: BaseChatModel,
1033
995
  browser_manager: BrowserManager,
1034
- controller: VibeSurfController,
996
+ tools: VibeSurfTools,
1035
997
  workspace_dir: str = "./workspace",
998
+ thinking_mode: bool = True,
999
+ calculate_token_cost: bool = True,
1036
1000
  ):
1037
1001
  """Initialize VibeSurfAgent with required components"""
1038
- self.llm = llm
1039
- self.browser_manager = browser_manager
1040
- self.controller = controller
1002
+ self.llm: BaseChatModel = llm
1003
+ self.calculate_token_cost = calculate_token_cost
1004
+ self.token_cost_service = TokenCost(include_cost=calculate_token_cost)
1005
+ self.token_cost_service.register_llm(llm)
1006
+ self.browser_manager: BrowserManager = browser_manager
1007
+ self.tools: VibeSurfTools = tools
1041
1008
  self.workspace_dir = workspace_dir
1042
1009
  os.makedirs(self.workspace_dir, exist_ok=True)
1010
+ self.thinking_mode = thinking_mode
1011
+
1043
1012
  self.cur_session_id = None
1044
- self.message_history = self.load_message_history()
1045
- self.activity_logs = self.load_activity_logs()
1013
+ self.file_system: Optional[CustomFileSystem] = None
1014
+ self.message_history = []
1015
+ self.activity_logs = []
1046
1016
 
1047
1017
  # Create LangGraph workflow
1048
1018
  self.workflow = create_vibe_surf_workflow()
@@ -1054,47 +1024,96 @@ class VibeSurfAgent:
1054
1024
  self._running_agents: Dict[str, Any] = {} # Track running BrowserUseAgent instances
1055
1025
  self._execution_task: Optional[asyncio.Task] = None
1056
1026
 
1057
- logger.info("🌊 VibeSurfAgent initialized with LangGraph workflow and control capabilities")
1058
-
1059
- def load_message_history(self, message_history_path: Optional[str] = None):
1060
- if message_history_path is None:
1061
- message_history_path = os.path.join(self.workspace_dir, "message_history.pkl")
1062
- if not os.path.exists(message_history_path):
1063
- return defaultdict(list)
1064
- with open(message_history_path, "rb") as f:
1065
- message_history = pickle.load(f)
1066
- logger.info(f"Loading message history from {message_history_path}")
1067
- for session_id in message_history:
1068
- logger.info(f"{session_id} has {len(message_history[session_id])} messages.")
1069
- return message_history
1070
-
1071
- def save_message_history(self, message_history_path: Optional[str] = None):
1072
- if message_history_path is None:
1073
- message_history_path = os.path.join(self.workspace_dir, "message_history.pkl")
1074
-
1075
- with open(message_history_path, "wb") as f:
1076
- logger.info(f"Saving message history with {len(self.message_history)} sessions to {message_history_path}")
1077
- pickle.dump(self.message_history, f)
1078
-
1079
- def load_activity_logs(self, activity_logs_path: Optional[str] = None):
1080
- if activity_logs_path is None:
1081
- activity_logs_path = os.path.join(self.workspace_dir, "activity_logs.pkl")
1082
- if not os.path.exists(activity_logs_path):
1083
- return defaultdict(list)
1084
- with open(activity_logs_path, "rb") as f:
1085
- activity_logs = pickle.load(f)
1086
- logger.info(f"Loading activity logs from {activity_logs_path}")
1087
- for session_id in activity_logs:
1088
- logger.info(f"{session_id} has {len(activity_logs[session_id])} activity logs.")
1089
- return activity_logs
1090
-
1091
- def save_activity_logs(self, activity_logs_path: Optional[str] = None):
1092
- if activity_logs_path is None:
1093
- activity_logs_path = os.path.join(self.workspace_dir, "activity_logs.pkl")
1094
-
1095
- with open(activity_logs_path, "wb") as f:
1096
- logger.info(f"Saving activity logs with {len(self.activity_logs)} sessions to {activity_logs_path}")
1097
- pickle.dump(self.activity_logs, f)
1027
+ logger.info("🌊 VibeSurf Agent initialized with LangGraph workflow")
1028
+
1029
+ def load_message_history(self, session_id: Optional[str] = None) -> list:
1030
+ """Load message history for a specific session, or return [] for new sessions"""
1031
+ if session_id is None:
1032
+ return []
1033
+
1034
+ session_message_history_path = os.path.join(self.workspace_dir, "sessions", session_id, "message_history.pkl")
1035
+
1036
+ if not os.path.exists(session_message_history_path):
1037
+ all_message_history_path = os.path.join(self.workspace_dir, "message_history.pkl")
1038
+ if os.path.exists(all_message_history_path):
1039
+ with open(all_message_history_path, "rb") as f:
1040
+ message_history_dict = pickle.load(f)
1041
+ if session_id in message_history_dict:
1042
+ return message_history_dict[session_id]
1043
+ logger.info(f"No message history found for session {session_id}, creating new")
1044
+ return []
1045
+
1046
+ try:
1047
+ with open(session_message_history_path, "rb") as f:
1048
+ message_history = pickle.load(f)
1049
+ logger.info(f"Loading message history for session {session_id} from {session_message_history_path}")
1050
+ return message_history
1051
+ except Exception as e:
1052
+ logger.error(f"Failed to load message history for session {session_id}: {e}")
1053
+ return []
1054
+
1055
+ def save_message_history(self, session_id: Optional[str] = None):
1056
+ """Save message history for a specific session"""
1057
+ if session_id is None:
1058
+ return
1059
+
1060
+ # Create session directory if it doesn't exist
1061
+ session_dir = os.path.join(self.workspace_dir, "sessions", session_id)
1062
+ os.makedirs(session_dir, exist_ok=True)
1063
+
1064
+ session_message_history_path = os.path.join(session_dir, "message_history.pkl")
1065
+
1066
+ try:
1067
+ with open(session_message_history_path, "wb") as f:
1068
+ logger.info(f"Saving message history for session {session_id} to {session_message_history_path}")
1069
+ pickle.dump(self.message_history, f)
1070
+ except Exception as e:
1071
+ logger.error(f"Failed to save message history for session {session_id}: {e}")
1072
+
1073
+ def load_activity_logs(self, session_id: Optional[str] = None) -> list:
1074
+ """Load activity logs for a specific session, or return [] for new sessions"""
1075
+ if session_id is None:
1076
+ return []
1077
+
1078
+ session_activity_logs_path = os.path.join(self.workspace_dir, "sessions", session_id, "activity_logs.pkl")
1079
+
1080
+ if not os.path.exists(session_activity_logs_path):
1081
+ # Adaptive to the older version
1082
+ all_activity_logs_path = os.path.join(self.workspace_dir, "activity_logs.pkl")
1083
+ if os.path.exists(all_activity_logs_path):
1084
+ with open(all_activity_logs_path, "rb") as f:
1085
+ activity_logs_dict = pickle.load(f)
1086
+ if session_id in activity_logs_dict:
1087
+ return activity_logs_dict[session_id]
1088
+ logger.info(f"No activity logs found for session {session_id}, creating new")
1089
+ return []
1090
+
1091
+ try:
1092
+ with open(session_activity_logs_path, "rb") as f:
1093
+ activity_logs = pickle.load(f)
1094
+ logger.info(f"Loading activity logs for session {session_id} from {session_activity_logs_path}")
1095
+ return activity_logs
1096
+ except Exception as e:
1097
+ logger.error(f"Failed to load activity logs for session {session_id}: {e}")
1098
+ return []
1099
+
1100
+ def save_activity_logs(self, session_id: Optional[str] = None):
1101
+ """Save activity logs for a specific session"""
1102
+ if session_id is None:
1103
+ return
1104
+
1105
+ # Create session directory if it doesn't exist
1106
+ session_dir = os.path.join(self.workspace_dir, "sessions", session_id)
1107
+ os.makedirs(session_dir, exist_ok=True)
1108
+
1109
+ session_activity_logs_path = os.path.join(session_dir, "activity_logs.pkl")
1110
+
1111
+ try:
1112
+ with open(session_activity_logs_path, "wb") as f:
1113
+ logger.info(f"Saving activity logs for session {session_id} to {session_activity_logs_path}")
1114
+ pickle.dump(self.activity_logs, f)
1115
+ except Exception as e:
1116
+ logger.error(f"Failed to save activity logs for session {session_id}: {e}")
1098
1117
 
1099
1118
  async def stop(self, reason: str = None) -> ControlResult:
1100
1119
  """
@@ -1111,21 +1130,16 @@ class VibeSurfAgent:
1111
1130
  reason = reason or "Manual stop requested"
1112
1131
  logger.info(f"🛑 Stopping agent execution: {reason}")
1113
1132
 
1114
- if self.cur_session_id in self.message_history:
1115
- supervisor_message_history = self.message_history[self.cur_session_id]
1116
- supervisor_message_history.append(UserMessage(
1117
- content=f"🛑 Stopping agent execution: {reason}"))
1133
+ self.message_history.append(UserMessage(
1134
+ content=f"🛑 Stopping agent execution: {reason}"))
1118
1135
 
1119
1136
  if self._current_state:
1120
1137
  self._current_state.should_stop = True
1121
1138
  self._current_state.stopped = True
1122
- self._current_state.control_timestamps["stopped"] = datetime.now()
1123
- self._current_state.control_reasons["stopped"] = reason
1124
- self._current_state.last_control_action = "stop"
1125
1139
 
1126
1140
  # Stop all running agents with timeout
1127
1141
  try:
1128
- await asyncio.wait_for(self._stop_all_agents(reason), timeout=3.0)
1142
+ await asyncio.wait_for(self._stop_all_agents(), timeout=3.0)
1129
1143
  except asyncio.TimeoutError:
1130
1144
  logger.warning("⚠️ Agent stopping timed out, continuing with task cancellation")
1131
1145
 
@@ -1176,19 +1190,14 @@ class VibeSurfAgent:
1176
1190
  reason = reason or "Manual pause requested"
1177
1191
  logger.info(f"⏸️ Pausing agent execution: {reason}")
1178
1192
 
1179
- if self.cur_session_id in self.message_history:
1180
- supervisor_message_history = self.message_history[self.cur_session_id]
1181
- supervisor_message_history.append(UserMessage(
1182
- content=f"⏸️ Pausing agent execution: {reason}"))
1193
+ self.message_history.append(UserMessage(
1194
+ content=f"⏸️ Pausing agent execution: {reason}"))
1183
1195
 
1184
1196
  if self._current_state:
1185
1197
  self._current_state.should_pause = True
1186
- self._current_state.control_timestamps["pause_requested"] = datetime.now()
1187
- self._current_state.control_reasons["paused"] = reason
1188
- self._current_state.last_control_action = "pause"
1189
1198
 
1190
1199
  # Pause all running agents
1191
- await self._pause_all_agents(reason)
1200
+ await self._pause_all_agents()
1192
1201
 
1193
1202
  logger.info(f"✅ VibeSurf execution paused: {reason}")
1194
1203
  return ControlResult(
@@ -1221,20 +1230,15 @@ class VibeSurfAgent:
1221
1230
  reason = reason or "Manual resume requested"
1222
1231
  logger.info(f"▶️ Resuming agent execution: {reason}")
1223
1232
 
1224
- if self.cur_session_id in self.message_history:
1225
- supervisor_message_history = self.message_history[self.cur_session_id]
1226
- supervisor_message_history.append(UserMessage(
1227
- content=f"▶️ Resuming agent execution: {reason}"))
1233
+ self.message_history.append(UserMessage(
1234
+ content=f"▶️ Resuming agent execution: {reason}"))
1228
1235
 
1229
1236
  if self._current_state:
1230
1237
  self._current_state.paused = False
1231
1238
  self._current_state.should_pause = False
1232
- self._current_state.control_timestamps["resumed"] = datetime.now()
1233
- self._current_state.control_reasons["resumed"] = reason
1234
- self._current_state.last_control_action = "resume"
1235
1239
 
1236
1240
  # Resume all paused agents
1237
- await self._resume_all_agents(reason)
1241
+ await self._resume_all_agents()
1238
1242
 
1239
1243
  logger.info(f"✅ VibeSurf execution resumed: {reason}")
1240
1244
  return ControlResult(
@@ -1268,15 +1272,6 @@ class VibeSurfAgent:
1268
1272
  reason = reason or f"Manual pause requested for agent {agent_id}"
1269
1273
  logger.info(f"⏸️ Pausing agent {agent_id}: {reason}")
1270
1274
 
1271
- # Update state tracking
1272
- if self._current_state:
1273
- self._current_state.paused_agents.add(agent_id)
1274
- if agent_id not in self._current_state.agent_control_states:
1275
- self._current_state.agent_control_states[agent_id] = {}
1276
- self._current_state.agent_control_states[agent_id]["paused"] = True
1277
- self._current_state.agent_control_states[agent_id]["pause_reason"] = reason
1278
- self._current_state.agent_control_states[agent_id]["pause_timestamp"] = datetime.now()
1279
-
1280
1275
  # Pause the specific agent if it's running
1281
1276
  agent = self._running_agents.get(agent_id)
1282
1277
  if agent:
@@ -1317,15 +1312,6 @@ class VibeSurfAgent:
1317
1312
  reason = reason or f"Manual resume requested for agent {agent_id}"
1318
1313
  logger.info(f"▶️ Resuming agent {agent_id}: {reason}")
1319
1314
 
1320
- # Update state tracking
1321
- if self._current_state:
1322
- self._current_state.paused_agents.discard(agent_id)
1323
- if agent_id not in self._current_state.agent_control_states:
1324
- self._current_state.agent_control_states[agent_id] = {}
1325
- self._current_state.agent_control_states[agent_id]["paused"] = False
1326
- self._current_state.agent_control_states[agent_id]["resume_reason"] = reason
1327
- self._current_state.agent_control_states[agent_id]["resume_timestamp"] = datetime.now()
1328
-
1329
1315
  # Resume the specific agent if it's running
1330
1316
  agent = self._running_agents.get(agent_id)
1331
1317
  if agent:
@@ -1366,7 +1352,7 @@ class VibeSurfAgent:
1366
1352
  elif self._current_state.paused or self._current_state.should_pause:
1367
1353
  overall_status = "paused"
1368
1354
  elif self._current_state.is_complete:
1369
- overall_status = "idle"
1355
+ overall_status = "completed"
1370
1356
  else:
1371
1357
  overall_status = "running"
1372
1358
 
@@ -1380,13 +1366,11 @@ class VibeSurfAgent:
1380
1366
  error_message = None
1381
1367
  pause_reason = None
1382
1368
 
1383
- # Check if agent is paused
1384
- if self._current_state and agent_id in self._current_state.paused_agents:
1385
- status = "paused"
1386
- agent_state = self._current_state.agent_control_states.get(agent_id, {})
1387
- pause_reason = agent_state.get("pause_reason")
1388
- elif self._current_state and self._current_state.stopped:
1369
+ # Simplified status checking since paused_agents removed
1370
+ if self._current_state and self._current_state.stopped:
1389
1371
  status = "stopped"
1372
+ elif self._current_state and self._current_state.paused:
1373
+ status = "paused"
1390
1374
 
1391
1375
  # Get current action if available
1392
1376
  if agent and hasattr(agent, 'state'):
@@ -1411,11 +1395,9 @@ class VibeSurfAgent:
1411
1395
  if self._current_state:
1412
1396
  progress = {
1413
1397
  "current_step": self._current_state.current_step,
1414
- "completed_todos": len(self._current_state.completed_todos),
1415
- "total_todos": len(self._current_state.todo_list),
1416
- "current_task_index": self._current_state.current_task_index,
1417
1398
  "is_complete": self._current_state.is_complete,
1418
- "last_control_action": self._current_state.last_control_action
1399
+ "browser_tasks_count": len(self._current_state.browser_tasks),
1400
+ "browser_results_count": len(self._current_state.browser_results)
1419
1401
  }
1420
1402
 
1421
1403
  return VibeSurfStatus(
@@ -1433,7 +1415,7 @@ class VibeSurfAgent:
1433
1415
  progress={"error": str(e)}
1434
1416
  )
1435
1417
 
1436
- async def _stop_all_agents(self, reason: str) -> None:
1418
+ async def _stop_all_agents(self) -> None:
1437
1419
  """Stop all running agents"""
1438
1420
  for agent_id, agent in self._running_agents.items():
1439
1421
  try:
@@ -1444,36 +1426,120 @@ class VibeSurfAgent:
1444
1426
  except Exception as e:
1445
1427
  logger.warning(f"⚠️ Failed to stop agent {agent_id}: {e}")
1446
1428
 
1447
- async def _pause_all_agents(self, reason: str) -> None:
1429
+ async def _pause_all_agents(self) -> None:
1448
1430
  """Pause all running agents"""
1449
1431
  for agent_id, agent in self._running_agents.items():
1450
1432
  try:
1451
1433
  if hasattr(agent, 'pause'):
1452
1434
  await agent.pause()
1453
1435
  logger.info(f"⏸️ Paused agent {agent_id}")
1454
- if self._current_state:
1455
- self._current_state.paused_agents.add(agent_id)
1436
+ # Note: paused_agents removed in simplified state
1456
1437
  except Exception as e:
1457
1438
  logger.warning(f"⚠️ Failed to pause agent {agent_id}: {e}")
1458
1439
 
1459
- async def _resume_all_agents(self, reason: str) -> None:
1440
+ async def _resume_all_agents(self) -> None:
1460
1441
  """Resume all paused agents"""
1461
1442
  for agent_id, agent in self._running_agents.items():
1462
1443
  try:
1463
1444
  if hasattr(agent, 'resume'):
1464
1445
  await agent.resume()
1465
1446
  logger.info(f"▶️ Resumed agent {agent_id}")
1466
- if self._current_state:
1467
- self._current_state.paused_agents.discard(agent_id)
1447
+ # Note: paused_agents removed in simplified state
1468
1448
  except Exception as e:
1469
1449
  logger.warning(f"⚠️ Failed to resume agent {agent_id}: {e}")
1470
1450
 
1451
+ def _create_sub_agent_prompt(self, new_task: str, agent_id: str) -> str:
1452
+ """
1453
+ Create a generic prompt for sub-agents when receiving new tasks.
1454
+ This prompt is designed to work with any type of sub-agent.
1455
+ """
1456
+ return f"""🔄 **New Task/Guidance from User:**
1457
+
1458
+ {new_task}
1459
+
1460
+ **Note:** As a sub-agent, you should evaluate whether this new task is relevant to your current work. You may:
1461
+ - **Use it** if it provides helpful guidance, tips, or corrections for your current task
1462
+ - **Use it** if it's a follow-up task that enhances your current work
1463
+ - **Ignore it** if it's unrelated to your specific responsibilities or doesn't apply to your current task
1464
+
1465
+ Please continue with your assigned work, incorporating this guidance only if it's relevant and helpful to your specific role and current task."""
1466
+
1467
+ async def add_new_task(self, new_task: str) -> None:
1468
+ """
1469
+ Add a new task or follow-up instruction during execution.
1470
+ This can be user feedback, guidance, or additional requirements.
1471
+
1472
+ Args:
1473
+ new_task: The new task, guidance, or instruction from the user
1474
+ """
1475
+ activity_entry = {
1476
+ "agent_name": 'user',
1477
+ "agent_status": 'request', # working, result, error
1478
+ "agent_msg": f"{new_task}"
1479
+ }
1480
+ self.activity_logs.append(activity_entry)
1481
+
1482
+ # Create an English prompt for the main agent
1483
+ prompt = f"""🔄 **New Task/Follow-up from User:**
1484
+
1485
+ {new_task}
1486
+
1487
+ **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."""
1488
+
1489
+ # Add to VibeSurf agent's message history
1490
+ self.message_history.append(UserMessage(content=prompt))
1491
+ logger.info(f"🌊 VibeSurf agent received new task: {new_task}")
1492
+
1493
+ # Propagate to all running sub-agents with generic sub-agent prompt
1494
+ if self._running_agents:
1495
+ logger.info(f"📡 Propagating new task to {len(self._running_agents)} running agents")
1496
+ for agent_id, agent in self._running_agents.items():
1497
+ try:
1498
+ if hasattr(agent, 'add_new_task'):
1499
+ # Use the generic sub-agent prompt
1500
+ sub_agent_prompt = self._create_sub_agent_prompt(new_task, agent_id)
1501
+ agent.add_new_task(sub_agent_prompt)
1502
+ logger.debug(f"✅ Sent new task to agent {agent_id}")
1503
+ else:
1504
+ logger.debug(f"⚠️ Agent {agent_id} doesn't support add_new_task")
1505
+ except Exception as e:
1506
+ logger.warning(f"⚠️ Failed to send new task to agent {agent_id}: {e}")
1507
+ else:
1508
+ logger.debug("📭 No running agents to propagate new task to")
1509
+
1510
+ async def process_upload_files(self, upload_files: Optional[List[str]] = None):
1511
+ if not upload_files:
1512
+ return []
1513
+ new_upload_files = []
1514
+ for ufile_path in upload_files:
1515
+ # timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
1516
+ dst_filename = f"upload_files/{os.path.basename(ufile_path)}"
1517
+ new_upload_files.append(dst_filename)
1518
+ return new_upload_files
1519
+
1520
+ def format_upload_files(self, upload_files: Optional[List[str]] = None, use_abspath: bool = False) -> str:
1521
+ """Format uploaded file for LLM prompt"""
1522
+ if upload_files is None:
1523
+ return ""
1524
+ if use_abspath:
1525
+ file_urls = []
1526
+ for i, file_path in enumerate(upload_files):
1527
+ abs_file_path = self.file_system.get_absolute_path(file_path)
1528
+ normalized_path = abs_file_path.replace(os.path.sep, '/')
1529
+ file_url = f"{i + 1}. [{os.path.basename(file_path)}](file:///{normalized_path})"
1530
+ file_urls.append(file_url)
1531
+ return "\n".join(file_urls)
1532
+ else:
1533
+ return "\n".join(
1534
+ [f"{i + 1}. {file_path}" for i, file_path in enumerate(upload_files)])
1535
+
1471
1536
  async def run(
1472
1537
  self,
1473
1538
  task: str,
1474
1539
  upload_files: Optional[List[str]] = None,
1475
1540
  session_id: Optional[str] = None,
1476
- ) -> str:
1541
+ thinking_mode: bool = True
1542
+ ) -> str | None:
1477
1543
  """
1478
1544
  Main execution method that returns markdown summary with control capabilities
1479
1545
 
@@ -1484,53 +1550,52 @@ class VibeSurfAgent:
1484
1550
  Returns:
1485
1551
  str: Markdown summary of execution results
1486
1552
  """
1487
- logger.info(f"🚀 Starting VibeSurfAgent execution for task: {task[:100]}...")
1488
- agent_activity_logs = None
1553
+ logger.info(f"🚀 Starting VibeSurfAgent execution for task: {task}. Powered by LLM model: {self.llm.model_name}")
1489
1554
  try:
1555
+ self.thinking_mode = thinking_mode
1490
1556
  session_id = session_id or self.cur_session_id or uuid7str()
1491
1557
  if session_id != self.cur_session_id:
1558
+ # Load session-specific data when switching sessions
1492
1559
  self.cur_session_id = session_id
1560
+ self.message_history = self.load_message_history(session_id)
1561
+ self.activity_logs = self.load_activity_logs(session_id)
1562
+ session_dir = os.path.join(self.workspace_dir, "sessions", self.cur_session_id)
1563
+ os.makedirs(session_dir, exist_ok=True)
1564
+ self.file_system = CustomFileSystem(session_dir)
1565
+ self.token_cost_service.clear_history()
1493
1566
 
1494
- if self.cur_session_id not in self.message_history:
1495
- logger.info(f"{self.cur_session_id} not found in message_history, create a new one.")
1496
- self.message_history[self.cur_session_id] = []
1497
- supervisor_message_history = self.message_history[self.cur_session_id]
1498
- if not supervisor_message_history:
1499
- supervisor_message_history.append(SystemMessage(content=SUPERVISOR_AGENT_SYSTEM_PROMPT))
1500
1567
  if upload_files and not isinstance(upload_files, list):
1501
1568
  upload_files = [upload_files]
1502
- upload_files_md = format_upload_files_list(upload_files)
1569
+ upload_files = await self.process_upload_files(upload_files)
1570
+
1571
+ if not self.message_history:
1572
+ self.message_history.append(SystemMessage(content=VIBESURF_SYSTEM_PROMPT))
1573
+
1574
+ # Format processed upload files for prompt
1503
1575
  user_request = f"* User's New Request:\n{task}\n"
1504
1576
  if upload_files:
1577
+ upload_files_md = self.format_upload_files(upload_files)
1505
1578
  user_request += f"* User Uploaded Files:\n{upload_files_md}\n"
1506
- supervisor_message_history.append(
1507
- UserMessage(
1508
- content=user_request)
1579
+ self.message_history.append(
1580
+ UserMessage(content=user_request)
1509
1581
  )
1510
1582
  logger.info(user_request)
1511
1583
 
1512
- if self.cur_session_id not in self.activity_logs:
1513
- self.activity_logs[self.cur_session_id] = []
1514
- agent_activity_logs = self.activity_logs[self.cur_session_id]
1584
+ abs_upload_files_md = self.format_upload_files(upload_files, use_abspath=True)
1515
1585
  activity_entry = {
1516
1586
  "agent_name": 'user',
1517
1587
  "agent_status": 'request', # working, result, error
1518
- "agent_msg": f"{task}\nUpload Files:\n{upload_files_md}\n" if upload_files else f"{task}"
1588
+ "agent_msg": f"{task}\nUpload Files:\n{abs_upload_files_md}\n" if upload_files else f"{task}"
1519
1589
  }
1520
- agent_activity_logs.append(activity_entry)
1590
+ self.activity_logs.append(activity_entry)
1521
1591
 
1522
- # Initialize state
1592
+ # Initialize state first (needed for file processing)
1523
1593
  initial_state = VibeSurfState(
1524
1594
  original_task=task,
1525
1595
  upload_files=upload_files or [],
1526
- workspace_dir=self.workspace_dir,
1527
- browser_manager=self.browser_manager,
1528
- vibesurf_controller=self.controller,
1529
- agent_activity_logs=agent_activity_logs,
1530
- supervisor_message_history=supervisor_message_history,
1531
- llm=self.llm,
1532
1596
  session_id=session_id,
1533
- vibesurf_agent=self # Reference to VibeSurfAgent for control coordination
1597
+ current_workspace_dir=os.path.join(self.workspace_dir, "sessions", self.cur_session_id),
1598
+ vibesurf_agent=self,
1534
1599
  )
1535
1600
 
1536
1601
  # Set current state for control operations
@@ -1565,36 +1630,39 @@ class VibeSurfAgent:
1565
1630
  except asyncio.CancelledError:
1566
1631
  logger.info("🛑 VibeSurfAgent execution was cancelled")
1567
1632
  # Add cancellation activity log
1568
- if agent_activity_logs:
1633
+ if self.activity_logs:
1569
1634
  activity_entry = {
1570
1635
  "agent_name": "VibeSurfAgent",
1571
1636
  "agent_status": "cancelled",
1572
1637
  "agent_msg": "Task execution was cancelled by user request."
1573
1638
  }
1574
- agent_activity_logs.append(activity_entry)
1639
+ self.activity_logs.append(activity_entry)
1575
1640
  return f"# Task Execution Cancelled\n\n**Task:** {task}\n\nExecution was stopped by user request."
1576
1641
  except Exception as e:
1642
+ import traceback
1643
+ traceback.print_exc()
1577
1644
  logger.error(f"❌ VibeSurfAgent execution failed: {e}")
1578
1645
  # Add error activity log
1579
- if agent_activity_logs:
1646
+ if self.activity_logs:
1580
1647
  activity_entry = {
1581
1648
  "agent_name": "VibeSurfAgent",
1582
1649
  "agent_status": "error",
1583
1650
  "agent_msg": f"Task execution failed: {str(e)}"
1584
1651
  }
1585
- agent_activity_logs.append(activity_entry)
1652
+ self.activity_logs.append(activity_entry)
1586
1653
  return f"# Task Execution Failed\n\n**Task:** {task}\n\n**Error:** {str(e)}\n\nPlease try again or contact support."
1587
1654
  finally:
1588
- if agent_activity_logs:
1589
- activity_entry = {
1590
- "agent_name": "VibeSurfAgent",
1591
- "agent_status": "done", # working, result, error
1592
- "agent_msg": "Finish Task."
1593
- }
1594
- agent_activity_logs.append(activity_entry)
1595
- # Reset state
1596
- self.save_message_history()
1597
- self.save_activity_logs()
1655
+
1656
+ activity_entry = {
1657
+ "agent_name": "VibeSurfAgent",
1658
+ "agent_status": "done", # working, result, error
1659
+ "agent_msg": "Finish Task."
1660
+ }
1661
+ self.activity_logs.append(activity_entry)
1662
+ # Save session-specific data
1663
+ if self.cur_session_id:
1664
+ self.save_message_history(self.cur_session_id)
1665
+ self.save_activity_logs(self.cur_session_id)
1598
1666
  async with self._control_lock:
1599
1667
  self._current_state = None
1600
1668
  self._execution_task = None
@@ -1604,44 +1672,40 @@ class VibeSurfAgent:
1604
1672
  List[Dict]]:
1605
1673
  if session_id is None:
1606
1674
  session_id = self.cur_session_id
1607
-
1608
- logger.debug(f"📊 GET_ACTIVITY_LOGS DEBUG - Session: {session_id}, Message Index: {message_index}, Current Session: {self.cur_session_id}")
1609
-
1675
+
1610
1676
  # Ensure session_id exists in activity_logs
1611
- if session_id not in self.activity_logs:
1612
- logger.warning(f"⚠️ Session {session_id} not found in activity_logs. Available sessions: {list(self.activity_logs.keys())}")
1613
- return None
1614
-
1615
- session_logs = self.activity_logs[session_id]
1677
+ if session_id != self.cur_session_id:
1678
+ session_logs = self.load_activity_logs(session_id)
1679
+ else:
1680
+ session_logs = self.activity_logs
1681
+
1616
1682
  logger.debug(f"📋 Session {session_id} has {len(session_logs)} activity logs")
1617
-
1683
+
1618
1684
  if message_index is None:
1619
1685
  logger.debug(f"📤 Returning all {len(session_logs)} activity logs for session {session_id}")
1620
1686
  return session_logs
1621
1687
  else:
1622
1688
  if message_index >= len(session_logs):
1623
- logger.debug(f"⚠️ Message index {message_index} out of range for session {session_id} (max index: {len(session_logs) - 1})")
1689
+ logger.debug(
1690
+ f"⚠️ Message index {message_index} out of range for session {session_id} (max index: {len(session_logs) - 1})")
1624
1691
  return None
1625
1692
  else:
1626
1693
  activity_log = session_logs[message_index]
1627
- logger.debug(f"📤 Returning activity log at index {message_index}: {activity_log.get('agent_name', 'unknown')} - {activity_log.get('agent_status', 'unknown')}")
1694
+ logger.debug(
1695
+ f"📤 Returning activity log at index {message_index}: {activity_log.get('agent_name', 'unknown')} - {activity_log.get('agent_status', 'unknown')}")
1628
1696
  return activity_log
1629
1697
 
1630
1698
  async def _get_result(self, state) -> str:
1631
1699
  """Get the final result from execution with simplified workflow support"""
1632
1700
  # Handle both dict and dataclass state types due to LangGraph serialization
1633
- simple_response = state.get('simple_response') if isinstance(state, dict) else getattr(state, 'simple_response',
1634
- None)
1635
- final_summary = state.get('final_summary') if isinstance(state, dict) else getattr(state, 'final_summary', None)
1701
+ final_response = state.get('final_response') if isinstance(state, dict) else getattr(state, 'final_response',
1702
+ None)
1636
1703
  original_task = state.get('original_task') if isinstance(state, dict) else getattr(state, 'original_task',
1637
1704
  'Unknown task')
1638
-
1639
- # Fallback for cases where state doesn't support logging
1640
- if simple_response:
1641
- return f"# Response\n\n{simple_response}"
1642
- elif final_summary:
1643
- return final_summary
1705
+ if final_response:
1706
+ return final_response
1644
1707
  else:
1645
1708
  return f"# Task Execution Completed\n\n**Task:** {original_task}\n\nTask execution completed but no detailed result available."
1646
1709
 
1710
+
1647
1711
  workflow = create_vibe_surf_workflow()