vibesurf 0.1.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (70) hide show
  1. vibe_surf/__init__.py +12 -0
  2. vibe_surf/_version.py +34 -0
  3. vibe_surf/agents/__init__.py +0 -0
  4. vibe_surf/agents/browser_use_agent.py +1106 -0
  5. vibe_surf/agents/prompts/__init__.py +1 -0
  6. vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
  7. vibe_surf/agents/report_writer_agent.py +360 -0
  8. vibe_surf/agents/vibe_surf_agent.py +1632 -0
  9. vibe_surf/backend/__init__.py +0 -0
  10. vibe_surf/backend/api/__init__.py +3 -0
  11. vibe_surf/backend/api/activity.py +243 -0
  12. vibe_surf/backend/api/config.py +740 -0
  13. vibe_surf/backend/api/files.py +322 -0
  14. vibe_surf/backend/api/models.py +257 -0
  15. vibe_surf/backend/api/task.py +300 -0
  16. vibe_surf/backend/database/__init__.py +13 -0
  17. vibe_surf/backend/database/manager.py +129 -0
  18. vibe_surf/backend/database/models.py +164 -0
  19. vibe_surf/backend/database/queries.py +922 -0
  20. vibe_surf/backend/database/schemas.py +100 -0
  21. vibe_surf/backend/llm_config.py +182 -0
  22. vibe_surf/backend/main.py +137 -0
  23. vibe_surf/backend/migrations/__init__.py +16 -0
  24. vibe_surf/backend/migrations/init_db.py +303 -0
  25. vibe_surf/backend/migrations/seed_data.py +236 -0
  26. vibe_surf/backend/shared_state.py +601 -0
  27. vibe_surf/backend/utils/__init__.py +7 -0
  28. vibe_surf/backend/utils/encryption.py +164 -0
  29. vibe_surf/backend/utils/llm_factory.py +225 -0
  30. vibe_surf/browser/__init__.py +8 -0
  31. vibe_surf/browser/agen_browser_profile.py +130 -0
  32. vibe_surf/browser/agent_browser_session.py +416 -0
  33. vibe_surf/browser/browser_manager.py +296 -0
  34. vibe_surf/browser/utils.py +790 -0
  35. vibe_surf/browser/watchdogs/__init__.py +0 -0
  36. vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
  37. vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
  38. vibe_surf/chrome_extension/background.js +558 -0
  39. vibe_surf/chrome_extension/config.js +48 -0
  40. vibe_surf/chrome_extension/content.js +284 -0
  41. vibe_surf/chrome_extension/dev-reload.js +47 -0
  42. vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
  43. vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
  44. vibe_surf/chrome_extension/icons/logo.png +0 -0
  45. vibe_surf/chrome_extension/manifest.json +53 -0
  46. vibe_surf/chrome_extension/popup.html +134 -0
  47. vibe_surf/chrome_extension/scripts/api-client.js +473 -0
  48. vibe_surf/chrome_extension/scripts/main.js +491 -0
  49. vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
  50. vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
  51. vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
  52. vibe_surf/chrome_extension/sidepanel.html +347 -0
  53. vibe_surf/chrome_extension/styles/animations.css +471 -0
  54. vibe_surf/chrome_extension/styles/components.css +670 -0
  55. vibe_surf/chrome_extension/styles/main.css +2307 -0
  56. vibe_surf/chrome_extension/styles/settings.css +1100 -0
  57. vibe_surf/cli.py +357 -0
  58. vibe_surf/controller/__init__.py +0 -0
  59. vibe_surf/controller/file_system.py +53 -0
  60. vibe_surf/controller/mcp_client.py +68 -0
  61. vibe_surf/controller/vibesurf_controller.py +616 -0
  62. vibe_surf/controller/views.py +37 -0
  63. vibe_surf/llm/__init__.py +21 -0
  64. vibe_surf/llm/openai_compatible.py +237 -0
  65. vibesurf-0.1.0.dist-info/METADATA +97 -0
  66. vibesurf-0.1.0.dist-info/RECORD +70 -0
  67. vibesurf-0.1.0.dist-info/WHEEL +5 -0
  68. vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
  69. vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
  70. vibesurf-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1632 @@
1
+ import asyncio
2
+ import copy
3
+ import json
4
+ import logging
5
+ import os
6
+ import pdb
7
+ import re
8
+ import time
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ import pickle
13
+ from typing import Any, Dict, List, Literal, Optional
14
+ from uuid_extensions import uuid7str
15
+ from collections import defaultdict
16
+ from langgraph.graph import StateGraph, END
17
+ from langgraph.checkpoint.memory import MemorySaver
18
+ from pydantic import BaseModel, Field
19
+ from json_repair import repair_json
20
+
21
+ from browser_use.browser.session import BrowserSession
22
+ from browser_use.llm.base import BaseChatModel
23
+ from browser_use.llm.messages import UserMessage, SystemMessage, BaseMessage, AssistantMessage
24
+ from browser_use.browser.views import TabInfo
25
+
26
+ from vibe_surf.agents.browser_use_agent import BrowserUseAgent
27
+ from vibe_surf.agents.report_writer_agent import ReportWriterAgent
28
+
29
+ from vibe_surf.agents.prompts.vibe_surf_prompt import (
30
+ SUPERVISOR_AGENT_SYSTEM_PROMPT,
31
+ )
32
+ from vibe_surf.browser.browser_manager import BrowserManager
33
+ from vibe_surf.controller.vibesurf_controller import VibeSurfController
34
+
35
+ logger = logging.getLogger(__name__)
36
+
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
+ max_parallel_agents: int = 5
51
+ reason: str = Field(description="LLM reasoning for mode selection")
52
+
53
+
54
+ class BrowserTaskResult(BaseModel):
55
+ """Result from browser task execution"""
56
+ agent_id: str
57
+ task: str
58
+ success: bool
59
+ result: Optional[str] = None
60
+ error: Optional[str] = None
61
+ screenshots: List[str] = Field(default_factory=list)
62
+ extracted_data: Optional[str] = None
63
+
64
+
65
+ class ReportRequirement(BaseModel):
66
+ """Indicates if and what type of report is needed"""
67
+ needs_report: bool = False
68
+ report_type: Literal["summary", "detailed", "none"] = "none"
69
+ reason: str = Field(description="LLM reasoning for report decision")
70
+
71
+
72
+ class ControlResult(BaseModel):
73
+ """Result of a control operation"""
74
+ success: bool
75
+ message: str
76
+ timestamp: datetime = Field(default_factory=datetime.now)
77
+ details: Optional[Dict[str, Any]] = None
78
+
79
+
80
+ class AgentStatus(BaseModel):
81
+ """Status of an individual agent"""
82
+ agent_id: str
83
+ status: Literal["running", "paused", "stopped", "idle", "error"] = "idle"
84
+ current_action: Optional[str] = None
85
+ last_update: datetime = Field(default_factory=datetime.now)
86
+ error_message: Optional[str] = None
87
+ pause_reason: Optional[str] = None
88
+
89
+
90
+ class VibeSurfStatus(BaseModel):
91
+ """Overall status of the vibesurf execution"""
92
+ overall_status: Literal["running", "paused", "stopped", "idle", "error"] = "idle"
93
+ agent_statuses: Dict[str, AgentStatus] = Field(default_factory=dict)
94
+ progress: Dict[str, Any] = Field(default_factory=dict)
95
+ last_update: datetime = Field(default_factory=datetime.now)
96
+ active_step: Optional[str] = None
97
+
98
+
99
+ @dataclass
100
+ class VibeSurfState:
101
+ """Main LangGraph state for VibeSurfAgent workflow with simplified architecture"""
102
+
103
+ # Core task information
104
+ original_task: str = ""
105
+ upload_files: List[str] = field(default_factory=list)
106
+ session_id: str = field(default_factory=lambda: uuid7str())
107
+ task_id: str = field(default_factory=lambda: uuid7str())
108
+
109
+ # Workflow state
110
+ current_step: str = "task_analysis"
111
+ is_simple_response: bool = False
112
+
113
+ # Supervisor Agent - Core controller with message history
114
+ supervisor_message_history: List[BaseMessage] = field(default_factory=list)
115
+ supervisor_action: Optional[str] = None
116
+
117
+ # Todo list management
118
+ todo_list: List[TodoItem] = field(default_factory=list)
119
+ completed_todos: List[TodoItem] = field(default_factory=list)
120
+ current_task_index: int = 0
121
+
122
+ # Task execution
123
+ execution_mode: Optional[ExecutionMode] = None
124
+ pending_tasks: List[str] = field(default_factory=list)
125
+ pending_todo_indices: List[int] = field(default_factory=list) # Track which todo indices are being executed
126
+ browser_results: List[BrowserTaskResult] = field(default_factory=list) # record all browser result
127
+ prev_browser_results: List[BrowserTaskResult] = field(default_factory=list) # record previous browser result
128
+
129
+ # Response outputs
130
+ simple_response: Optional[str] = None
131
+ generated_report_path: Optional[str] = None
132
+ final_summary: Optional[str] = None
133
+ is_complete: bool = False
134
+
135
+ # File organization
136
+ workspace_dir: str = "./workspace"
137
+ session_dir: Optional[str] = None
138
+ task_dir: Optional[str] = None
139
+
140
+ # Integration components
141
+ browser_manager: Optional[BrowserManager] = None
142
+ vibesurf_controller: Optional[VibeSurfController] = None
143
+ llm: Optional[BaseChatModel] = None
144
+ vibesurf_agent: Optional[Any] = None
145
+
146
+ # Control state management
147
+ paused: bool = False
148
+ stopped: bool = False
149
+ should_pause: bool = False
150
+ should_stop: bool = False
151
+
152
+ # Agent control tracking
153
+ agent_control_states: Dict[str, Dict[str, Any]] = field(default_factory=dict)
154
+ paused_agents: set = field(default_factory=set)
155
+
156
+ # Control metadata
157
+ control_timestamps: Dict[str, datetime] = field(default_factory=dict)
158
+ control_reasons: Dict[str, str] = field(default_factory=dict)
159
+ last_control_action: Optional[str] = None
160
+
161
+ # Agent activity log
162
+ agent_activity_logs: List[Dict[str, str]] = field(default_factory=list)
163
+
164
+
165
+ # Utility functions for parsing LLM JSON responses
166
+ def parse_json_response(response_text: str, fallback_data: Dict) -> Dict:
167
+ """Parse JSON response with repair capability"""
168
+ try:
169
+ # Try to find JSON in the response
170
+ json_start = response_text.find('{')
171
+ json_end = response_text.rfind('}') + 1
172
+
173
+ if json_start >= 0 and json_end > json_start:
174
+ json_text = response_text[json_start:json_end]
175
+ try:
176
+ return json.loads(json_text)
177
+ except json.JSONDecodeError:
178
+ # Try to repair JSON
179
+ repaired_json = repair_json(json_text)
180
+ return json.loads(repaired_json)
181
+
182
+ # If no JSON found, return fallback
183
+ return fallback_data
184
+ except Exception as e:
185
+ logger.warning(f"JSON parsing failed: {e}, using fallback")
186
+ return fallback_data
187
+
188
+
189
+ def parse_task_analysis_response(response_text: str) -> Dict:
190
+ """Parse task analysis JSON response for simple response detection"""
191
+ fallback = {
192
+ "is_simple_response": False,
193
+ "reasoning": "Failed to parse response, defaulting to complex task",
194
+ "simple_response_content": None
195
+ }
196
+ return parse_json_response(response_text, fallback)
197
+
198
+
199
+ def parse_supervisor_response(response_text: str) -> Dict:
200
+ """Parse supervisor agent JSON response"""
201
+ fallback = {
202
+ "action": "summary_generation",
203
+ "reasoning": "Failed to parse response, defaulting to summary generation",
204
+ "todo_items": [],
205
+ "task_type": "single",
206
+ "tasks_to_execute": [],
207
+ "summary_content": None
208
+ }
209
+ result = parse_json_response(response_text, fallback)
210
+
211
+ # Ensure todo_items is always a list for actions that modify todos
212
+ if result.get("action") in ["generate_todos", "update_todos"]:
213
+ if "todo_items" not in result or not isinstance(result["todo_items"], list):
214
+ result["todo_items"] = []
215
+
216
+ return result
217
+
218
+
219
+ def parse_todo_generation_response(response_text: str) -> List[str]:
220
+ """Parse todo generation JSON response"""
221
+ fallback = {
222
+ "todo_items": ["Complete the original task"]
223
+ }
224
+ result = parse_json_response(response_text, fallback)
225
+ return result.get("todo_items", fallback["todo_items"])[:5] # Max 5 items
226
+
227
+
228
+ def parse_execution_planning_response(response_text: str) -> ExecutionMode:
229
+ """Parse execution planning JSON response"""
230
+ fallback = {
231
+ "execution_mode": "single",
232
+ "max_parallel_agents": 3,
233
+ "reasoning": "Default single mode"
234
+ }
235
+ result = parse_json_response(response_text, fallback)
236
+
237
+ return ExecutionMode(
238
+ mode=result.get("execution_mode", "single"),
239
+ max_parallel_agents=result.get("max_parallel_agents", 3),
240
+ reason=result.get("reasoning", "Default execution mode")
241
+ )
242
+
243
+
244
+ def parse_todo_update_response(response_text: str) -> Dict:
245
+ """Parse todo update JSON response"""
246
+ fallback = {
247
+ "additional_tasks": [],
248
+ "next_action": "summary_generation",
249
+ "reasoning": "Default action"
250
+ }
251
+ return parse_json_response(response_text, fallback)
252
+
253
+
254
+ def parse_report_decision_response(response_text: str) -> ReportRequirement:
255
+ """Parse report decision JSON response"""
256
+ fallback = {
257
+ "needs_report": False,
258
+ "report_type": "none",
259
+ "reasoning": "Default no report"
260
+ }
261
+ result = parse_json_response(response_text, fallback)
262
+
263
+ return ReportRequirement(
264
+ needs_report=result.get("needs_report", False),
265
+ report_type=result.get("report_type", "none"),
266
+ reason=result.get("reasoning", "Default report decision")
267
+ )
268
+
269
+
270
+ def format_upload_files_list(upload_files: Optional[List[str]] = None) -> str:
271
+ """Format uploaded file list for LLM prompt"""
272
+ if upload_files is None:
273
+ return ""
274
+ return "\n".join(
275
+ [f"{i + 1}. [{os.path.basename(file_path)}](file:///{file_path})" for i, file_path in enumerate(upload_files)])
276
+
277
+
278
+ def format_todo_list(todo_list: List[TodoItem]) -> str:
279
+ """Format todo list for LLM prompt"""
280
+ return "\n".join([f"{i + 1}. {item.task}" for i, item in enumerate(todo_list)])
281
+
282
+
283
+ def format_completed_todos(completed_todos: List[TodoItem]) -> str:
284
+ """Format completed todos for LLM prompt"""
285
+ return "\n".join([f"✅ {item.task} - {item.result or 'Completed'}" for item in completed_todos])
286
+
287
+
288
+ def format_browser_results(browser_results: List[BrowserTaskResult]) -> str:
289
+ """Format browser results for LLM prompt"""
290
+ result_text = []
291
+ for result in browser_results:
292
+ status = "✅ Success" if result.success else "❌ Failed"
293
+ result_text.append(f"{status}: {result.task}")
294
+ if result.result:
295
+ result_text.append(f" Result: {result.result}...")
296
+ if result.error:
297
+ result_text.append(f" Error: {result.error}")
298
+ return "\n".join(result_text)
299
+
300
+
301
+ def format_todo_list_markdown(todo_list: List[TodoItem]) -> str:
302
+ """Format todo list as markdown"""
303
+ if not todo_list:
304
+ return "No todo items"
305
+
306
+ markdown_lines = []
307
+ for i, item in enumerate(todo_list):
308
+ if item.status == "completed":
309
+ status_symbol = "- [x] "
310
+ else:
311
+ status_symbol = "- [ ] "
312
+ markdown_lines.append(f"{status_symbol}{item.task}")
313
+
314
+ return "\n".join(markdown_lines)
315
+
316
+
317
+ def log_agent_activity(state: VibeSurfState, agent_name: str, agent_status: str, agent_msg: str) -> None:
318
+ """Log agent activity to the activity log"""
319
+ activity_entry = {
320
+ "agent_name": agent_name,
321
+ "agent_status": agent_status, # working, result, error
322
+ "agent_msg": agent_msg
323
+ }
324
+ state.agent_activity_logs.append(activity_entry)
325
+ logger.info(f"📝 Logged activity: {agent_name} - {agent_status}:\n{agent_msg}")
326
+
327
+
328
+ def create_browser_agent_step_callback(state: VibeSurfState, agent_name: str):
329
+ """Create a step callback function for browser-use agent to log each step"""
330
+
331
+ def step_callback(browser_state_summary, agent_output, step_num: int) -> None:
332
+ """Callback function to log browser agent step information"""
333
+ try:
334
+ # Format step information as markdown
335
+ step_msg = f"## Step {step_num}\n\n"
336
+
337
+ # Add thinking if present
338
+ if agent_output.thinking:
339
+ step_msg += f"**💡 Thinking:**\n{agent_output.thinking}\n\n"
340
+
341
+ # Add evaluation if present
342
+ if agent_output.evaluation_previous_goal:
343
+ step_msg += f"**👍 Evaluation:**\n{agent_output.evaluation_previous_goal}\n\n"
344
+
345
+ # Add memory if present
346
+ # if agent_output.memory:
347
+ # step_msg += f"**🧠 Memory:** {agent_output.memory}\n\n"
348
+
349
+ # Add next goal if present
350
+ if agent_output.next_goal:
351
+ step_msg += f"**🎯 Next Goal:**\n{agent_output.next_goal}\n\n"
352
+
353
+ # Add action summary
354
+ if agent_output.action and len(agent_output.action) > 0:
355
+ action_count = len(agent_output.action)
356
+ step_msg += f"**⚡ Actions:**\n"
357
+
358
+ # Add brief action details
359
+ for i, action in enumerate(agent_output.action): # Limit to first 3 actions to avoid too much detail
360
+ action_data = action.model_dump(exclude_unset=True)
361
+ action_name = next(iter(action_data.keys())) if action_data else 'unknown'
362
+ action_params = json.dumps(action_data[action_name], ensure_ascii=False) if action_name in action_data else ""
363
+ step_msg += f"- [x] {action_name}: {action_params}\n"
364
+ else:
365
+ step_msg += f"**⚡ Actions:** No actions\n"
366
+
367
+ # Log the step activity
368
+ log_agent_activity(state, agent_name, "working", step_msg.strip())
369
+
370
+ except Exception as e:
371
+ logger.error(f"❌ Error in step callback for {agent_name}: {e}")
372
+ # Log a simple fallback message
373
+ log_agent_activity(state, agent_name, "step", f"Step {step_num} completed")
374
+
375
+ return step_callback
376
+
377
+
378
+ def ensure_directories(state: VibeSurfState) -> None:
379
+ """Ensure proper directory structure"""
380
+ # Create session directory: workspace_dir/session_id/
381
+ state.session_dir = os.path.join(state.workspace_dir, state.session_id)
382
+ os.makedirs(state.session_dir, exist_ok=True)
383
+
384
+ # Create task directory: workspace_dir/session_id/task_id/
385
+ state.task_dir = os.path.join(state.session_dir, state.task_id)
386
+ os.makedirs(state.task_dir, exist_ok=True)
387
+
388
+ # Create subdirectories for different output types
389
+ os.makedirs(os.path.join(state.task_dir, "screenshots"), exist_ok=True)
390
+ os.makedirs(os.path.join(state.task_dir, "reports"), exist_ok=True)
391
+ os.makedirs(os.path.join(state.task_dir, "logs"), exist_ok=True)
392
+
393
+
394
+ # Control-aware node wrapper
395
+ async def control_aware_node(node_func, state: VibeSurfState, node_name: str) -> VibeSurfState:
396
+ """
397
+ Wrapper for workflow nodes that adds control state checking
398
+ """
399
+ # Check control state before executing node
400
+ if state.stopped:
401
+ logger.info(f"🛑 Node {node_name} skipped - workflow stopped")
402
+ return state
403
+
404
+ # Handle pause state
405
+ while state.paused or state.should_pause:
406
+ if not state.paused and state.should_pause:
407
+ logger.info(f"⏸️ Node {node_name} pausing workflow")
408
+ state.paused = True
409
+ state.should_pause = False
410
+ state.control_timestamps["paused"] = datetime.now()
411
+
412
+ logger.debug(f"⏸️ Node {node_name} waiting - workflow paused")
413
+ await asyncio.sleep(0.5) # Check every 500ms
414
+
415
+ # Allow stopping while paused
416
+ if state.stopped or state.should_stop:
417
+ logger.info(f"🛑 Node {node_name} stopped while paused")
418
+ state.stopped = True
419
+ state.should_stop = False
420
+ state.control_timestamps["stopped"] = datetime.now()
421
+ return state
422
+
423
+ # Check for stop signal
424
+ if state.should_stop:
425
+ logger.info(f"🛑 Node {node_name} stopping workflow")
426
+ state.stopped = True
427
+ state.should_stop = False
428
+ state.control_timestamps["stopped"] = datetime.now()
429
+ return state
430
+
431
+ # Execute the actual node
432
+ logger.debug(f"▶️ Executing node: {node_name}")
433
+ state.last_control_action = f"executing_{node_name}"
434
+
435
+ try:
436
+ return await node_func(state)
437
+ except Exception as e:
438
+ logger.error(f"❌ Node {node_name} failed: {e}")
439
+ raise
440
+
441
+
442
+ # LangGraph Nodes
443
+
444
+ async def supervisor_agent_node(state: VibeSurfState) -> VibeSurfState:
445
+ """
446
+ Core supervisor agent node - manages todos, assigns tasks, and coordinates workflow
447
+ """
448
+ return await control_aware_node(_supervisor_agent_node_impl, state, "supervisor_agent")
449
+
450
+
451
+ def format_browser_tabs(tabs: Optional[List[TabInfo]] = None) -> str:
452
+ if not tabs:
453
+ return ""
454
+ return "\n".join([f"[{i}] Page Title: {item.title}, Page Url: {item.url}, Page ID: {item.target_id}" for i, item in enumerate(tabs)])
455
+
456
+
457
+ async def _supervisor_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
458
+ """Implementation of supervisor agent node - core workflow controller"""
459
+ logger.info("🎯 Supervisor Agent: Managing workflow and task coordination...")
460
+
461
+ supervisor_message_history = state.supervisor_message_history
462
+
463
+ # Build supervisor user prompt with current context
464
+ if state.prev_browser_results:
465
+ browser_results_md = format_browser_results(state.prev_browser_results)
466
+ supervisor_message_history.append(AssistantMessage(
467
+ content=f"Previous Browser Execution Results: \n{browser_results_md}"))
468
+ elif state.generated_report_path:
469
+ supervisor_message_history.append(AssistantMessage(content=f"Generated Report Path: {state.generated_report_path}"))
470
+
471
+ if state.todo_list:
472
+ supervisor_message_history.append(UserMessage(
473
+ content=f"Completed Todos:\n{format_completed_todos(state.completed_todos)}\nCurrent Todos:\n{format_todo_list(state.todo_list)}"))
474
+
475
+ browser_tabs = await state.browser_manager.main_browser_session.get_tabs()
476
+ browser_tabs_md = format_browser_tabs(browser_tabs)
477
+ if browser_tabs_md:
478
+ supervisor_message_history.append(UserMessage(
479
+ content=f"Available Browser Tabs:\n{browser_tabs_md}"))
480
+
481
+ # Reset prev_browser_results
482
+ state.prev_browser_results = []
483
+ try:
484
+ response = await state.llm.ainvoke(supervisor_message_history)
485
+ # add result to message history
486
+ supervisor_message_history.append(AssistantMessage(content=response.completion))
487
+
488
+ supervisor_result = parse_supervisor_response(response.completion)
489
+
490
+ action = supervisor_result["action"]
491
+ reasoning = supervisor_result["reasoning"]
492
+ # Log agent activity
493
+ log_agent_activity(state, "supervisor_agent", "thinking", f"{reasoning}")
494
+
495
+ state.supervisor_action = action
496
+
497
+ logger.info(f"🎯 Supervisor decision: {action} - {reasoning}")
498
+
499
+ # Handle different actions
500
+ if action == "generate_todos":
501
+ # Generate initial todos
502
+ todo_items = supervisor_result.get("todo_items", [])
503
+ if todo_items:
504
+ state.todo_list = [TodoItem(task=task) for task in todo_items]
505
+ todo_todo_list_md = format_todo_list_markdown(state.todo_list)
506
+ supervisor_message_history.append(
507
+ UserMessage(content=f"Successfully generated todo list:\n{todo_todo_list_md}"))
508
+ log_agent_activity(state, "supervisor_agent", "result", f"Todo List:\n\n{todo_todo_list_md}")
509
+ # Continue in supervisor to assign tasks
510
+ state.current_step = "supervisor_agent"
511
+
512
+ elif action == "update_todos":
513
+ # Replace all remaining todos with the new list
514
+ todo_items = supervisor_result.get("todo_items", [])
515
+ if todo_items:
516
+ # Clear current todo list and replace with new items
517
+ state.todo_list = [TodoItem(task=task) for task in todo_items]
518
+ todo_todo_list_md = format_todo_list_markdown(state.completed_todos + state.todo_list)
519
+ supervisor_message_history.append(
520
+ UserMessage(content=f"Successfully Updated todo list:\n{todo_todo_list_md}"))
521
+ log_agent_activity(state, "supervisor_agent", "result", f"Todo List:\n\n{todo_todo_list_md}")
522
+ else:
523
+ # If no todo_items provided, clear the list
524
+ state.todo_list = []
525
+ todo_todo_list_md = format_todo_list_markdown(state.completed_todos + state.todo_list)
526
+ supervisor_message_history.append(
527
+ UserMessage(content=f"Cleared todo list - all tasks completed:\n{todo_todo_list_md}"))
528
+ log_agent_activity(state, "supervisor_agent", "result",
529
+ f"Cleared todo list - all tasks completed\n{todo_todo_list_md}")
530
+
531
+ # Continue in supervisor to assign tasks
532
+ state.current_step = "supervisor_agent"
533
+
534
+ elif action == "assign_browser_task":
535
+ # Assign browser tasks
536
+ task_type = supervisor_result.get("task_type", "single")
537
+ tasks_to_execute = supervisor_result.get("tasks_to_execute", [])
538
+
539
+ if tasks_to_execute:
540
+ tasks_to_execute_new = []
541
+ todo_indices = [] # Track which todo items are being executed
542
+
543
+ for task_item in tasks_to_execute:
544
+ if isinstance(task_item, list):
545
+ # Format: [page_index, todo_index]
546
+ page_index, todo_index = task_item
547
+ if todo_index < len(state.todo_list):
548
+ task_description = state.todo_list[todo_index].task
549
+ tasks_to_execute_new.append([browser_tabs[page_index].target_id, task_description])
550
+ todo_indices.append(todo_index)
551
+ else:
552
+ # Format: todo_index
553
+ todo_index = task_item
554
+ if todo_index < len(state.todo_list):
555
+ task_description = state.todo_list[todo_index].task
556
+ tasks_to_execute_new.append(task_description)
557
+ todo_indices.append(todo_index)
558
+
559
+ state.execution_mode = ExecutionMode(
560
+ mode=task_type,
561
+ max_parallel_agents=5 if task_type == "parallel" else 1,
562
+ reason=reasoning
563
+ )
564
+ state.pending_tasks = tasks_to_execute_new
565
+ state.pending_todo_indices = todo_indices # Store which todo indices are being executed
566
+ state.current_step = "browser_task_execution"
567
+
568
+ log_agent_activity(state, "supervisor_agent", "result",
569
+ f"Assigned {len(tasks_to_execute)} browser tasks ({task_type} mode)")
570
+ else:
571
+ # No tasks to execute, continue in supervisor
572
+ state.current_step = "supervisor_agent"
573
+ supervisor_message_history.append(
574
+ UserMessage(content=f"No tasks to execute. Please provide browser tasks to execute."))
575
+
576
+ elif action == "assign_report_task":
577
+ # Assign report generation task
578
+ state.current_step = "report_task_execution"
579
+ log_agent_activity(state, "supervisor_agent", "result", "Assigned report generation task")
580
+
581
+ elif action == "simple_response":
582
+ # Use provided content or generate if not provided
583
+ state.current_step = "simple_response"
584
+ state.simple_response = supervisor_result["simple_response_content"]
585
+ state.is_complete = True
586
+ log_agent_activity(state, "supervisor_agent", "result", state.simple_response)
587
+ elif action == "summary_generation":
588
+ # Handle summary generation directly in supervisor
589
+ summary_content = supervisor_result.get("summary_content")
590
+
591
+ if summary_content:
592
+ # Use LLM-provided summary content
593
+ state.final_summary = summary_content
594
+ state.is_complete = True
595
+ state.current_step = "summary_generation"
596
+ log_agent_activity(state, "supervisor_agent", "result", f"{summary_content}")
597
+ else:
598
+ # Generate summary using the same logic as the old summary generation node
599
+ state.current_step = "supervisor_agent"
600
+ supervisor_message_history.append(
601
+ UserMessage(content=f"The summary content is empty. Please provide summary content if you think all requirements have been accomplished."))
602
+ else:
603
+ # Unknown action, default to complete workflow
604
+ state.current_step = "summary_generation"
605
+ log_agent_activity(state, "supervisor_agent", "error", f"Unknown action: {action}")
606
+
607
+ return state
608
+
609
+ except Exception as e:
610
+ logger.error(f"❌ Supervisor agent failed: {e}")
611
+ state.current_step = "summary_generation"
612
+ log_agent_activity(state, "supervisor_agent", "error", f"Supervisor failed: {str(e)}")
613
+ return state
614
+
615
+
616
+ async def browser_task_execution_node(state: VibeSurfState) -> VibeSurfState:
617
+ """
618
+ Execute browser tasks assigned by supervisor agent
619
+ """
620
+ return await control_aware_node(_browser_task_execution_node_impl, state, "browser_task_execution")
621
+
622
+
623
+ async def _browser_task_execution_node_impl(state: VibeSurfState) -> VibeSurfState:
624
+ """Implementation of browser task execution node"""
625
+ logger.info("🚀 Executing browser tasks assigned by supervisor...")
626
+
627
+ # Log agent activity
628
+ log_agent_activity(state, "browser_task_executor", "working",
629
+ f"Executing {len(state.pending_tasks)} browser tasks in {state.execution_mode.mode if state.execution_mode else 'single'} mode")
630
+
631
+ # Setup file organization
632
+ ensure_directories(state)
633
+
634
+ try:
635
+ if state.execution_mode and state.execution_mode.mode == "parallel":
636
+ # Execute tasks in parallel
637
+ results = await execute_parallel_browser_tasks(state)
638
+ else:
639
+ # Execute tasks in single mode
640
+ results = await execute_single_browser_tasks(state)
641
+
642
+ # Update browser results
643
+ state.prev_browser_results = copy.deepcopy(results)
644
+ state.browser_results.extend(results)
645
+
646
+ # Mark corresponding todos as completed using indices
647
+ for i, todo_index in enumerate(state.pending_todo_indices):
648
+ if todo_index < len(state.todo_list) and state.todo_list[todo_index].status != "completed":
649
+ todo = state.todo_list[todo_index]
650
+ todo.status = "completed"
651
+ if i < len(results):
652
+ todo.result = results[i].result if results[i].success else None
653
+ todo.error = results[i].error if not results[i].success else None
654
+ state.completed_todos.append(todo)
655
+
656
+ # Remove completed todos from the todo list
657
+ # Sort indices in reverse order to avoid index shifting issues
658
+ for todo_index in sorted(state.pending_todo_indices, reverse=True):
659
+ if todo_index < len(state.todo_list):
660
+ state.todo_list.pop(todo_index)
661
+
662
+ # Clear pending tasks and indices
663
+ state.pending_tasks = []
664
+ state.pending_todo_indices = []
665
+
666
+ # Return to supervisor for next decision
667
+ state.current_step = "supervisor_agent"
668
+
669
+ # Log result
670
+ successful_tasks = sum(1 for result in results if result.success)
671
+ log_agent_activity(state, "browser_task_executor", "result",
672
+ f"Browser execution completed: {successful_tasks}/{len(results)} tasks successful")
673
+
674
+ logger.info(f"✅ Browser task execution completed with {len(results)} results")
675
+ return state
676
+
677
+ except Exception as e:
678
+ logger.error(f"❌ Browser task execution failed: {e}")
679
+
680
+ # Create error results for pending tasks
681
+ error_results = []
682
+ for i, task in enumerate(state.pending_tasks):
683
+ # Get the actual task description for the error result
684
+ if isinstance(task, list):
685
+ task_description = task[1] # [target_id, task_description]
686
+ else:
687
+ task_description = task
688
+ error_results.append(BrowserTaskResult(
689
+ agent_id="error",
690
+ task=task_description,
691
+ success=False,
692
+ error=str(e)
693
+ ))
694
+
695
+ state.browser_results.extend(error_results)
696
+ state.pending_tasks = []
697
+ state.pending_todo_indices = []
698
+ state.current_step = "supervisor_agent"
699
+
700
+ log_agent_activity(state, "browser_task_executor", "error", f"Browser execution failed: {str(e)}")
701
+ return state
702
+
703
+
704
+ async def report_task_execution_node(state: VibeSurfState) -> VibeSurfState:
705
+ """
706
+ Execute HTML report generation task assigned by supervisor agent
707
+ """
708
+ return await control_aware_node(_report_task_execution_node_impl, state, "report_task_execution")
709
+
710
+
711
+ async def _report_task_execution_node_impl(state: VibeSurfState) -> VibeSurfState:
712
+ """Implementation of report task execution node"""
713
+ logger.info("📄 Executing HTML report generation task...")
714
+
715
+ # Log agent activity
716
+ log_agent_activity(state, "report_task_executor", "working", "Generating HTML report")
717
+
718
+ try:
719
+ # Use ReportWriterAgent to generate HTML report
720
+ report_writer = ReportWriterAgent(
721
+ llm=state.llm,
722
+ workspace_dir=state.task_dir
723
+ )
724
+
725
+ report_data = {
726
+ "original_task": state.original_task,
727
+ "execution_results": state.browser_results,
728
+ "report_type": "detailed", # Default to detailed report
729
+ "upload_files": state.upload_files
730
+ }
731
+
732
+ report_path = await report_writer.generate_report(report_data)
733
+
734
+ state.generated_report_path = report_path
735
+
736
+ # Return to supervisor for next decision
737
+ state.current_step = "supervisor_agent"
738
+
739
+ log_agent_activity(state, "report_task_executor", "result",
740
+ f"HTML report generated successfully at: `{report_path}`")
741
+
742
+ logger.info(f"✅ Report generated: {report_path}")
743
+ return state
744
+
745
+ except Exception as e:
746
+ logger.error(f"❌ Report generation failed: {e}")
747
+ state.current_step = "supervisor_agent"
748
+ log_agent_activity(state, "report_task_executor", "error", f"Report generation failed: {str(e)}")
749
+ return state
750
+
751
+
752
+ async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTaskResult]:
753
+ """Execute pending tasks in parallel using multiple browser agents"""
754
+ logger.info("🔄 Executing pending tasks in parallel...")
755
+
756
+ # Register agents with browser manager
757
+ agents = []
758
+ pending_tasks = state.pending_tasks[:state.execution_mode.max_parallel_agents]
759
+ bu_agent_ids = []
760
+ register_sessions = []
761
+ for i, task in enumerate(pending_tasks):
762
+ agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
763
+ if isinstance(task, list):
764
+ target_id, task_description = task
765
+ else:
766
+ task_description = task
767
+ target_id = None
768
+ register_sessions.append(
769
+ state.browser_manager.register_agent(agent_id, target_id=target_id)
770
+ )
771
+ bu_agent_ids.append(agent_id)
772
+ agent_browser_sessions = await asyncio.gather(*register_sessions)
773
+
774
+ for i, task in enumerate(pending_tasks):
775
+ agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
776
+ if isinstance(task, list):
777
+ target_id, task_description = task
778
+ else:
779
+ task_description = task
780
+ try:
781
+ # Log agent creation
782
+ log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "working",
783
+ f"{task_description}")
784
+
785
+ # Create BrowserUseAgent for each task
786
+ if state.upload_files:
787
+ upload_files_md = format_upload_files_list(state.upload_files)
788
+ bu_task = task_description + f"\nAvailable uploaded files:\n{upload_files_md}\n"
789
+ else:
790
+ bu_task = task_description
791
+
792
+ # Create step callback for this agent
793
+ agent_name = f"browser_use_agent-{i + 1}-{state.task_id[-4:]}"
794
+ step_callback = create_browser_agent_step_callback(state, agent_name)
795
+
796
+ agent = BrowserUseAgent(
797
+ task=bu_task,
798
+ llm=state.llm,
799
+ browser_session=agent_browser_sessions[i],
800
+ controller=state.vibesurf_controller,
801
+ task_id=f"{state.task_id}-{i + 1}",
802
+ file_system_path=state.task_dir,
803
+ register_new_step_callback=step_callback,
804
+ 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."
805
+ )
806
+ agents.append(agent)
807
+
808
+ # Track agent in VibeSurfAgent for control coordination
809
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
810
+ state.vibesurf_agent._running_agents[agent_id] = agent
811
+ logger.debug(f"🔗 Registered parallel agent {agent_id} for control coordination")
812
+
813
+ except Exception as e:
814
+ logger.error(f"❌ Failed to create agent {agent_id}: {e}")
815
+ log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
816
+ f"Failed to create agent: {str(e)}")
817
+
818
+ # Execute all agents in parallel
819
+ try:
820
+ histories = await asyncio.gather(*[agent.run() for agent in agents], return_exceptions=True)
821
+
822
+ # Process results
823
+ results = []
824
+ for i, (agent, history) in enumerate(zip(agents, histories)):
825
+ agent_id = f"agent-{i + 1}-{state.task_id[-4:]}"
826
+ if isinstance(history, Exception):
827
+ results.append(BrowserTaskResult(
828
+ agent_id=f"agent-{i + 1}",
829
+ task=pending_tasks[i],
830
+ success=False,
831
+ error=str(history)
832
+ ))
833
+ # Log error
834
+ log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
835
+ f"Task failed: {str(history)}")
836
+ else:
837
+ results.append(BrowserTaskResult(
838
+ agent_id=f"agent-{i + 1}",
839
+ task=pending_tasks[i],
840
+ success=history.is_successful(),
841
+ result=history.final_result() if hasattr(history, 'final_result') else "Task completed",
842
+ error=str(history.errors()) if history.has_errors() and not history.is_successful() else ""
843
+ ))
844
+ # Log result
845
+ if history.is_successful():
846
+ result_text = history.final_result() if hasattr(history, 'final_result') else "Task completed"
847
+ log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "result",
848
+ f"Task completed successfully: \n{result_text}")
849
+ else:
850
+ error_text = str(history.errors()) if history.has_errors() else "Unknown error"
851
+ log_agent_activity(state, f"browser_use_agent-{i + 1}-{state.task_id[-4:]}", "error",
852
+ f"Task failed: {error_text}")
853
+
854
+ return results
855
+
856
+ finally:
857
+ # Remove agents from control tracking and cleanup browser sessions
858
+ for i, agent_id in enumerate(bu_agent_ids):
859
+ if not isinstance(pending_tasks[i], list):
860
+ await state.browser_manager.unregister_agent(agent_id, close_tabs=True)
861
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
862
+ state.vibesurf_agent._running_agents.pop(agent_id, None)
863
+ logger.debug(f"🔗 Unregistered parallel agent {agent_id} from control coordination")
864
+
865
+
866
+ async def execute_single_browser_tasks(state: VibeSurfState) -> List[BrowserTaskResult]:
867
+ """Execute pending tasks in single mode one by one"""
868
+ logger.info("🔄 Executing pending tasks in single mode...")
869
+
870
+ results = []
871
+ for i, task in enumerate(state.pending_tasks):
872
+ if isinstance(task, list):
873
+ target_id, task_description = task
874
+ else:
875
+ task_description = task
876
+ logger.info(f"🔄 Executing task ({i + 1}/{len(state.pending_tasks)}): {task_description}")
877
+
878
+ agent_id = f"agent-single-{state.task_id[-4:]}-{i}"
879
+
880
+ # Log agent activity
881
+ log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "working", f"{task_description}")
882
+
883
+ try:
884
+ await state.browser_manager._get_active_target()
885
+ if state.upload_files:
886
+ upload_files_md = format_upload_files_list(state.upload_files)
887
+ bu_task = task_description + f"\nAvailable user uploaded files:\n{upload_files_md}\n"
888
+ else:
889
+ bu_task = task_description
890
+ # Create step callback for this agent
891
+ agent_name = f"browser_use_agent-{state.task_id[-4:]}"
892
+ step_callback = create_browser_agent_step_callback(state, agent_name)
893
+
894
+ agent = BrowserUseAgent(
895
+ task=bu_task,
896
+ llm=state.llm,
897
+ browser_session=state.browser_manager.main_browser_session,
898
+ controller=state.vibesurf_controller,
899
+ task_id=f"{state.task_id}-{i}",
900
+ file_system_path=state.task_dir,
901
+ register_new_step_callback=step_callback,
902
+ 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."
903
+ )
904
+
905
+ # Track agent in VibeSurfAgent for control coordination
906
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
907
+ state.vibesurf_agent._running_agents[agent_id] = agent
908
+ logger.debug(f"🔗 Registered single agent {agent_id} for control coordination")
909
+
910
+ try:
911
+ history = await agent.run()
912
+
913
+ result = BrowserTaskResult(
914
+ agent_id=agent_id,
915
+ task=task,
916
+ success=history.is_successful(),
917
+ result=history.final_result() if hasattr(history, 'final_result') else "Task completed",
918
+ error=str(history.errors()) if history.has_errors() and not history.is_successful() else ""
919
+ )
920
+
921
+ # Log result
922
+ if result.success:
923
+ log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "result",
924
+ f"Task completed successfully: \n{result.result}")
925
+ else:
926
+ log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "error",
927
+ f"Task failed: {result.error}")
928
+
929
+ results.append(result)
930
+ finally:
931
+ # Remove agent from control tracking
932
+ if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
933
+ state.vibesurf_agent._running_agents.pop(agent_id, None)
934
+ logger.debug(f"🔗 Unregistered single agent {agent_id} from control coordination")
935
+
936
+ except Exception as e:
937
+ logger.error(f"❌ Single task execution failed: {e}")
938
+ log_agent_activity(state, f"browser_use_agent-{state.task_id[-4:]}", "error",
939
+ f"Task execution failed: {str(e)}")
940
+ results.append(BrowserTaskResult(
941
+ agent_id=agent_id,
942
+ task=task,
943
+ success=False,
944
+ error=str(e)
945
+ ))
946
+
947
+ return results
948
+
949
+
950
+ def route_after_supervisor_agent(state: VibeSurfState) -> str:
951
+ """Route based on supervisor agent decisions"""
952
+ if state.current_step == "browser_task_execution":
953
+ return "browser_task_execution"
954
+ elif state.current_step == "report_task_execution":
955
+ return "report_task_execution"
956
+ elif state.current_step == "summary_generation":
957
+ return "summary_generation" # Summary generated, go to END
958
+ elif state.current_step == "simple_response":
959
+ return "simple_response"
960
+ elif state.current_step == "supervisor_agent":
961
+ return "supervisor_agent" # Continue in supervisor loop
962
+ else:
963
+ return "END" # Default fallback - complete workflow
964
+
965
+
966
+ def route_after_browser_task_execution(state: VibeSurfState) -> str:
967
+ """Route back to supervisor after browser task completion"""
968
+ return "supervisor_agent"
969
+
970
+
971
+ def route_after_report_task_execution(state: VibeSurfState) -> str:
972
+ """Route back to supervisor after report task completion"""
973
+ return "supervisor_agent"
974
+
975
+
976
+ def should_continue(state: VibeSurfState) -> str:
977
+ """Main continuation logic"""
978
+ if state.is_complete:
979
+ return "END"
980
+ else:
981
+ return "continue"
982
+
983
+
984
+ def create_vibe_surf_workflow() -> StateGraph:
985
+ """Create the simplified LangGraph workflow with supervisor agent as core controller"""
986
+
987
+ workflow = StateGraph(VibeSurfState)
988
+
989
+ # Add nodes for simplified architecture
990
+ workflow.add_node("supervisor_agent", supervisor_agent_node)
991
+ workflow.add_node("browser_task_execution", browser_task_execution_node)
992
+ workflow.add_node("report_task_execution", report_task_execution_node)
993
+
994
+ # Set entry point
995
+ workflow.set_entry_point("supervisor_agent")
996
+
997
+ # Supervisor agent routes to different execution nodes or END
998
+ workflow.add_conditional_edges(
999
+ "supervisor_agent",
1000
+ route_after_supervisor_agent,
1001
+ {
1002
+ "browser_task_execution": "browser_task_execution",
1003
+ "report_task_execution": "report_task_execution",
1004
+ "summary_generation": END,
1005
+ "supervisor_agent": "supervisor_agent",
1006
+ "simple_response": END,
1007
+ "END": END
1008
+ }
1009
+ )
1010
+
1011
+ # Execution nodes return to supervisor
1012
+ workflow.add_conditional_edges(
1013
+ "browser_task_execution",
1014
+ route_after_browser_task_execution,
1015
+ {
1016
+ "supervisor_agent": "supervisor_agent"
1017
+ }
1018
+ )
1019
+
1020
+ workflow.add_conditional_edges(
1021
+ "report_task_execution",
1022
+ route_after_report_task_execution,
1023
+ {
1024
+ "supervisor_agent": "supervisor_agent"
1025
+ }
1026
+ )
1027
+
1028
+ return workflow
1029
+
1030
+
1031
+ class VibeSurfAgent:
1032
+ """Main LangGraph-based VibeSurfAgent with comprehensive control capabilities"""
1033
+
1034
+ def __init__(
1035
+ self,
1036
+ llm: BaseChatModel,
1037
+ browser_manager: BrowserManager,
1038
+ controller: VibeSurfController,
1039
+ workspace_dir: str = "./workspace",
1040
+ ):
1041
+ """Initialize VibeSurfAgent with required components"""
1042
+ self.llm = llm
1043
+ self.browser_manager = browser_manager
1044
+ self.controller = controller
1045
+ self.workspace_dir = workspace_dir
1046
+ os.makedirs(self.workspace_dir, exist_ok=True)
1047
+ self.cur_session_id = None
1048
+ self.message_history = self.load_message_history()
1049
+ self.activity_logs = self.load_activity_logs()
1050
+
1051
+ # Create LangGraph workflow
1052
+ self.workflow = create_vibe_surf_workflow()
1053
+ self.app = self.workflow.compile()
1054
+
1055
+ # Control state management
1056
+ self._control_lock = asyncio.Lock()
1057
+ self._current_state: Optional[VibeSurfState] = None
1058
+ self._running_agents: Dict[str, Any] = {} # Track running BrowserUseAgent instances
1059
+ self._execution_task: Optional[asyncio.Task] = None
1060
+
1061
+ logger.info("🌊 VibeSurfAgent initialized with LangGraph workflow and control capabilities")
1062
+
1063
+ def load_message_history(self, message_history_path: Optional[str] = None):
1064
+ if message_history_path is None:
1065
+ message_history_path = os.path.join(self.workspace_dir, "message_history.pkl")
1066
+ if not os.path.exists(message_history_path):
1067
+ return defaultdict(list)
1068
+ with open(message_history_path, "rb") as f:
1069
+ message_history = pickle.load(f)
1070
+ logger.info(f"Loading message history from {message_history_path}")
1071
+ for session_id in message_history:
1072
+ logger.info(f"{session_id} has {len(message_history[session_id])} messages.")
1073
+ return message_history
1074
+
1075
+ def save_message_history(self, message_history_path: Optional[str] = None):
1076
+ if message_history_path is None:
1077
+ message_history_path = os.path.join(self.workspace_dir, "message_history.pkl")
1078
+
1079
+ with open(message_history_path, "wb") as f:
1080
+ logger.info(f"Saving message history with {len(self.message_history)} sessions to {message_history_path}")
1081
+ pickle.dump(self.message_history, f)
1082
+
1083
+ def load_activity_logs(self, activity_logs_path: Optional[str] = None):
1084
+ if activity_logs_path is None:
1085
+ activity_logs_path = os.path.join(self.workspace_dir, "activity_logs.pkl")
1086
+ if not os.path.exists(activity_logs_path):
1087
+ return defaultdict(list)
1088
+ with open(activity_logs_path, "rb") as f:
1089
+ activity_logs = pickle.load(f)
1090
+ logger.info(f"Loading activity logs from {activity_logs_path}")
1091
+ for session_id in activity_logs:
1092
+ logger.info(f"{session_id} has {len(activity_logs[session_id])} activity logs.")
1093
+ return activity_logs
1094
+
1095
+ def save_activity_logs(self, activity_logs_path: Optional[str] = None):
1096
+ if activity_logs_path is None:
1097
+ activity_logs_path = os.path.join(self.workspace_dir, "activity_logs.pkl")
1098
+
1099
+ with open(activity_logs_path, "wb") as f:
1100
+ logger.info(f"Saving activity logs with {len(self.activity_logs)} sessions to {activity_logs_path}")
1101
+ pickle.dump(self.activity_logs, f)
1102
+
1103
+ async def stop(self, reason: str = None) -> ControlResult:
1104
+ """
1105
+ Stop the vibesurf execution immediately
1106
+
1107
+ Args:
1108
+ reason: Optional reason for stopping
1109
+
1110
+ Returns:
1111
+ ControlResult with operation status
1112
+ """
1113
+ try:
1114
+ async with self._control_lock:
1115
+ reason = reason or "Manual stop requested"
1116
+ logger.info(f"🛑 Stopping agent execution: {reason}")
1117
+
1118
+ if self.cur_session_id in self.message_history:
1119
+ supervisor_message_history = self.message_history[self.cur_session_id]
1120
+ supervisor_message_history.append(UserMessage(
1121
+ content=f"🛑 Stopping agent execution: {reason}"))
1122
+
1123
+ if self._current_state:
1124
+ self._current_state.should_stop = True
1125
+ self._current_state.stopped = True
1126
+ self._current_state.control_timestamps["stopped"] = datetime.now()
1127
+ self._current_state.control_reasons["stopped"] = reason
1128
+ self._current_state.last_control_action = "stop"
1129
+
1130
+ # Stop all running agents with timeout
1131
+ try:
1132
+ await asyncio.wait_for(self._stop_all_agents(reason), timeout=3.0)
1133
+ except asyncio.TimeoutError:
1134
+ logger.warning("⚠️ Agent stopping timed out, continuing with task cancellation")
1135
+
1136
+ # Cancel execution task if running
1137
+ if self._execution_task and not self._execution_task.done():
1138
+ self._execution_task.cancel()
1139
+ try:
1140
+ await asyncio.wait_for(self._execution_task, timeout=2.0)
1141
+ except (asyncio.CancelledError, asyncio.TimeoutError):
1142
+ logger.debug("🛑 Execution task cancelled or timed out")
1143
+
1144
+ logger.info(f"✅ VibeSurf execution stopped: {reason}")
1145
+ return ControlResult(
1146
+ success=True,
1147
+ message=f"VibeSurf stopped successfully: {reason}",
1148
+ details={"reason": reason}
1149
+ )
1150
+
1151
+ except asyncio.TimeoutError:
1152
+ error_msg = f"Stop operation timed out after 10 seconds"
1153
+ logger.error(error_msg)
1154
+ return ControlResult(
1155
+ success=False,
1156
+ message=error_msg,
1157
+ details={"timeout": True}
1158
+ )
1159
+ except Exception as e:
1160
+ error_msg = f"Failed to stop VibeSurf: {str(e)}"
1161
+ logger.error(error_msg)
1162
+ return ControlResult(
1163
+ success=False,
1164
+ message=error_msg,
1165
+ details={"error": str(e)}
1166
+ )
1167
+
1168
+ async def pause(self, reason: str = None) -> ControlResult:
1169
+ """
1170
+ Pause the VibeSurf execution
1171
+
1172
+ Args:
1173
+ reason: Optional reason for pausing
1174
+
1175
+ Returns:
1176
+ ControlResult with operation status
1177
+ """
1178
+ async with self._control_lock:
1179
+ try:
1180
+ reason = reason or "Manual pause requested"
1181
+ logger.info(f"⏸️ Pausing agent execution: {reason}")
1182
+
1183
+ if self.cur_session_id in self.message_history:
1184
+ supervisor_message_history = self.message_history[self.cur_session_id]
1185
+ supervisor_message_history.append(UserMessage(
1186
+ content=f"⏸️ Pausing agent execution: {reason}"))
1187
+
1188
+ if self._current_state:
1189
+ self._current_state.should_pause = True
1190
+ self._current_state.control_timestamps["pause_requested"] = datetime.now()
1191
+ self._current_state.control_reasons["paused"] = reason
1192
+ self._current_state.last_control_action = "pause"
1193
+
1194
+ # Pause all running agents
1195
+ await self._pause_all_agents(reason)
1196
+
1197
+ logger.info(f"✅ VibeSurf execution paused: {reason}")
1198
+ return ControlResult(
1199
+ success=True,
1200
+ message=f"VibeSurf paused successfully: {reason}",
1201
+ details={"reason": reason}
1202
+ )
1203
+
1204
+ except Exception as e:
1205
+ error_msg = f"Failed to pause VibeSurf: {str(e)}"
1206
+ logger.error(error_msg)
1207
+ return ControlResult(
1208
+ success=False,
1209
+ message=error_msg,
1210
+ details={"error": str(e)}
1211
+ )
1212
+
1213
+ async def resume(self, reason: str = None) -> ControlResult:
1214
+ """
1215
+ Resume the VibeSurf execution
1216
+
1217
+ Args:
1218
+ reason: Optional reason for resuming
1219
+
1220
+ Returns:
1221
+ ControlResult with operation status
1222
+ """
1223
+ async with self._control_lock:
1224
+ try:
1225
+ reason = reason or "Manual resume requested"
1226
+ logger.info(f"▶️ Resuming agent execution: {reason}")
1227
+
1228
+ if self.cur_session_id in self.message_history:
1229
+ supervisor_message_history = self.message_history[self.cur_session_id]
1230
+ supervisor_message_history.append(UserMessage(
1231
+ content=f"▶️ Resuming agent execution: {reason}"))
1232
+
1233
+ if self._current_state:
1234
+ self._current_state.paused = False
1235
+ self._current_state.should_pause = False
1236
+ self._current_state.control_timestamps["resumed"] = datetime.now()
1237
+ self._current_state.control_reasons["resumed"] = reason
1238
+ self._current_state.last_control_action = "resume"
1239
+
1240
+ # Resume all paused agents
1241
+ await self._resume_all_agents(reason)
1242
+
1243
+ logger.info(f"✅ VibeSurf execution resumed: {reason}")
1244
+ return ControlResult(
1245
+ success=True,
1246
+ message=f"VibeSurf resumed successfully: {reason}",
1247
+ details={"reason": reason}
1248
+ )
1249
+
1250
+ except Exception as e:
1251
+ error_msg = f"Failed to resume VibeSurf: {str(e)}"
1252
+ logger.error(error_msg)
1253
+ return ControlResult(
1254
+ success=False,
1255
+ message=error_msg,
1256
+ details={"error": str(e)}
1257
+ )
1258
+
1259
+ async def pause_agent(self, agent_id: str, reason: str = None) -> ControlResult:
1260
+ """
1261
+ Pause a specific agent
1262
+
1263
+ Args:
1264
+ agent_id: ID of the agent to pause
1265
+ reason: Optional reason for pausing
1266
+
1267
+ Returns:
1268
+ ControlResult with operation status
1269
+ """
1270
+ async with self._control_lock:
1271
+ try:
1272
+ reason = reason or f"Manual pause requested for agent {agent_id}"
1273
+ logger.info(f"⏸️ Pausing agent {agent_id}: {reason}")
1274
+
1275
+ # Update state tracking
1276
+ if self._current_state:
1277
+ self._current_state.paused_agents.add(agent_id)
1278
+ if agent_id not in self._current_state.agent_control_states:
1279
+ self._current_state.agent_control_states[agent_id] = {}
1280
+ self._current_state.agent_control_states[agent_id]["paused"] = True
1281
+ self._current_state.agent_control_states[agent_id]["pause_reason"] = reason
1282
+ self._current_state.agent_control_states[agent_id]["pause_timestamp"] = datetime.now()
1283
+
1284
+ # Pause the specific agent if it's running
1285
+ agent = self._running_agents.get(agent_id)
1286
+ if agent:
1287
+ if hasattr(agent, 'pause'):
1288
+ await agent.pause()
1289
+ logger.info(f"✅ Agent {agent_id} paused successfully")
1290
+ else:
1291
+ logger.warning(f"⚠️ Agent {agent_id} not found")
1292
+
1293
+ return ControlResult(
1294
+ success=True,
1295
+ message=f"Agent {agent_id} paused successfully: {reason}",
1296
+ details={"agent_id": agent_id, "reason": reason}
1297
+ )
1298
+
1299
+ except Exception as e:
1300
+ error_msg = f"Failed to pause agent {agent_id}: {str(e)}"
1301
+ logger.error(error_msg)
1302
+ return ControlResult(
1303
+ success=False,
1304
+ message=error_msg,
1305
+ details={"agent_id": agent_id, "error": str(e)}
1306
+ )
1307
+
1308
+ async def resume_agent(self, agent_id: str, reason: str = None) -> ControlResult:
1309
+ """
1310
+ Resume a specific agent
1311
+
1312
+ Args:
1313
+ agent_id: ID of the agent to resume
1314
+ reason: Optional reason for resuming
1315
+
1316
+ Returns:
1317
+ ControlResult with operation status
1318
+ """
1319
+ async with self._control_lock:
1320
+ try:
1321
+ reason = reason or f"Manual resume requested for agent {agent_id}"
1322
+ logger.info(f"▶️ Resuming agent {agent_id}: {reason}")
1323
+
1324
+ # Update state tracking
1325
+ if self._current_state:
1326
+ self._current_state.paused_agents.discard(agent_id)
1327
+ if agent_id not in self._current_state.agent_control_states:
1328
+ self._current_state.agent_control_states[agent_id] = {}
1329
+ self._current_state.agent_control_states[agent_id]["paused"] = False
1330
+ self._current_state.agent_control_states[agent_id]["resume_reason"] = reason
1331
+ self._current_state.agent_control_states[agent_id]["resume_timestamp"] = datetime.now()
1332
+
1333
+ # Resume the specific agent if it's running
1334
+ agent = self._running_agents.get(agent_id)
1335
+ if agent:
1336
+ if hasattr(agent, 'resume'):
1337
+ await agent.resume()
1338
+ logger.info(f"✅ Agent {agent_id} resumed successfully")
1339
+ else:
1340
+ logger.warning(f"⚠️ Agent {agent_id} not found")
1341
+
1342
+ return ControlResult(
1343
+ success=True,
1344
+ message=f"Agent {agent_id} resumed successfully: {reason}",
1345
+ details={"agent_id": agent_id, "reason": reason}
1346
+ )
1347
+
1348
+ except Exception as e:
1349
+ error_msg = f"Failed to resume agent {agent_id}: {str(e)}"
1350
+ logger.error(error_msg)
1351
+ return ControlResult(
1352
+ success=False,
1353
+ message=error_msg,
1354
+ details={"agent_id": agent_id, "error": str(e)}
1355
+ )
1356
+
1357
+ def get_status(self) -> VibeSurfStatus:
1358
+ """
1359
+ Get current status of the VibeSurf and all agents
1360
+
1361
+ Returns:
1362
+ VibeSurfStatus with current state information
1363
+ """
1364
+ try:
1365
+ # Determine overall status
1366
+ if not self._current_state:
1367
+ overall_status = "idle"
1368
+ elif self._current_state.stopped:
1369
+ overall_status = "stopped"
1370
+ elif self._current_state.paused or self._current_state.should_pause:
1371
+ overall_status = "paused"
1372
+ elif self._current_state.is_complete:
1373
+ overall_status = "idle"
1374
+ else:
1375
+ overall_status = "running"
1376
+
1377
+ # Build agent statuses
1378
+ agent_statuses = {}
1379
+
1380
+ # Add status for tracked running agents
1381
+ for agent_id, agent in self._running_agents.items():
1382
+ status = "running"
1383
+ current_action = None
1384
+ error_message = None
1385
+ pause_reason = None
1386
+
1387
+ # Check if agent is paused
1388
+ if self._current_state and agent_id in self._current_state.paused_agents:
1389
+ status = "paused"
1390
+ agent_state = self._current_state.agent_control_states.get(agent_id, {})
1391
+ pause_reason = agent_state.get("pause_reason")
1392
+ elif self._current_state and self._current_state.stopped:
1393
+ status = "stopped"
1394
+
1395
+ # Get current action if available
1396
+ if agent and hasattr(agent, 'state'):
1397
+ try:
1398
+ if hasattr(agent.state, 'last_result') and agent.state.last_result:
1399
+ current_action = f"Last action completed"
1400
+ else:
1401
+ current_action = "Executing task"
1402
+ except:
1403
+ current_action = "Unknown"
1404
+
1405
+ agent_statuses[agent_id] = AgentStatus(
1406
+ agent_id=agent_id,
1407
+ status=status,
1408
+ current_action=current_action,
1409
+ error_message=error_message,
1410
+ pause_reason=pause_reason
1411
+ )
1412
+
1413
+ # Build progress information
1414
+ progress = {}
1415
+ if self._current_state:
1416
+ progress = {
1417
+ "current_step": self._current_state.current_step,
1418
+ "completed_todos": len(self._current_state.completed_todos),
1419
+ "total_todos": len(self._current_state.todo_list),
1420
+ "current_task_index": self._current_state.current_task_index,
1421
+ "is_complete": self._current_state.is_complete,
1422
+ "last_control_action": self._current_state.last_control_action
1423
+ }
1424
+
1425
+ return VibeSurfStatus(
1426
+ overall_status=overall_status,
1427
+ agent_statuses=agent_statuses,
1428
+ progress=progress,
1429
+ active_step=self._current_state.current_step if self._current_state else None
1430
+ )
1431
+
1432
+ except Exception as e:
1433
+ logger.error(f"❌ Failed to get status: {e}")
1434
+ return VibeSurfStatus(
1435
+ overall_status="error",
1436
+ agent_statuses={},
1437
+ progress={"error": str(e)}
1438
+ )
1439
+
1440
+ async def _stop_all_agents(self, reason: str) -> None:
1441
+ """Stop all running agents"""
1442
+ for agent_id, agent in self._running_agents.items():
1443
+ try:
1444
+ # Also try to pause if available as a fallback
1445
+ if agent and hasattr(agent, 'stop'):
1446
+ await agent.stop()
1447
+ logger.debug(f"⏸️ stop agent {agent_id}")
1448
+ except Exception as e:
1449
+ logger.warning(f"⚠️ Failed to stop agent {agent_id}: {e}")
1450
+
1451
+ async def _pause_all_agents(self, reason: str) -> None:
1452
+ """Pause all running agents"""
1453
+ for agent_id, agent in self._running_agents.items():
1454
+ try:
1455
+ if hasattr(agent, 'pause'):
1456
+ await agent.pause()
1457
+ logger.debug(f"⏸️ Paused agent {agent_id}")
1458
+ if self._current_state:
1459
+ self._current_state.paused_agents.add(agent_id)
1460
+ except Exception as e:
1461
+ logger.warning(f"⚠️ Failed to pause agent {agent_id}: {e}")
1462
+
1463
+ async def _resume_all_agents(self, reason: str) -> None:
1464
+ """Resume all paused agents"""
1465
+ for agent_id, agent in self._running_agents.items():
1466
+ try:
1467
+ if hasattr(agent, 'resume'):
1468
+ await agent.resume()
1469
+ logger.debug(f"▶️ Resumed agent {agent_id}")
1470
+ if self._current_state:
1471
+ self._current_state.paused_agents.discard(agent_id)
1472
+ except Exception as e:
1473
+ logger.warning(f"⚠️ Failed to resume agent {agent_id}: {e}")
1474
+
1475
+ async def run(
1476
+ self,
1477
+ task: str,
1478
+ upload_files: Optional[List[str]] = None,
1479
+ session_id: Optional[str] = None,
1480
+ ) -> str:
1481
+ """
1482
+ Main execution method that returns markdown summary with control capabilities
1483
+
1484
+ Args:
1485
+ task: User task to execute
1486
+ upload_files: Optional list of file paths that user has uploaded
1487
+
1488
+ Returns:
1489
+ str: Markdown summary of execution results
1490
+ """
1491
+ logger.info(f"🚀 Starting VibeSurfAgent execution for task: {task[:100]}...")
1492
+ agent_activity_logs = None
1493
+ try:
1494
+ session_id = session_id or self.cur_session_id or uuid7str()
1495
+ if session_id != self.cur_session_id:
1496
+ self.cur_session_id = session_id
1497
+
1498
+ if self.cur_session_id not in self.message_history:
1499
+ logger.info(f"{self.cur_session_id} not found in message_history, create a new one.")
1500
+ self.message_history[self.cur_session_id] = []
1501
+ supervisor_message_history = self.message_history[self.cur_session_id]
1502
+ if not supervisor_message_history:
1503
+ supervisor_message_history.append(SystemMessage(content=SUPERVISOR_AGENT_SYSTEM_PROMPT))
1504
+ if upload_files and not isinstance(upload_files, list):
1505
+ upload_files = [upload_files]
1506
+ upload_files_md = format_upload_files_list(upload_files)
1507
+ supervisor_message_history.append(
1508
+ UserMessage(
1509
+ content=f"* User's New Request:\n{task}\n* Uploaded Files for Completing Task:\n{upload_files_md}\n")
1510
+ )
1511
+ logger.info(f"* User's New Request:\n{task}\n* Uploaded Files for Completing Task:\n{upload_files_md}\n")
1512
+
1513
+ if self.cur_session_id not in self.activity_logs:
1514
+ self.activity_logs[self.cur_session_id] = []
1515
+ agent_activity_logs = self.activity_logs[self.cur_session_id]
1516
+ activity_entry = {
1517
+ "agent_name": 'user',
1518
+ "agent_status": 'request', # working, result, error
1519
+ "agent_msg": f"{task}\nUpload Files:\n{upload_files_md}\n" if upload_files else f"{task}"
1520
+ }
1521
+ agent_activity_logs.append(activity_entry)
1522
+
1523
+ # Initialize state
1524
+ initial_state = VibeSurfState(
1525
+ original_task=task,
1526
+ upload_files=upload_files or [],
1527
+ workspace_dir=self.workspace_dir,
1528
+ browser_manager=self.browser_manager,
1529
+ vibesurf_controller=self.controller,
1530
+ agent_activity_logs=agent_activity_logs,
1531
+ supervisor_message_history=supervisor_message_history,
1532
+ llm=self.llm,
1533
+ session_id=session_id,
1534
+ vibesurf_agent=self # Reference to VibeSurfAgent for control coordination
1535
+ )
1536
+
1537
+ # Set current state for control operations
1538
+ async with self._control_lock:
1539
+ self._current_state = initial_state
1540
+ self._running_agents.clear() # Clear any previous agents
1541
+
1542
+ async def _execute_workflow():
1543
+ """Internal workflow execution with proper state management"""
1544
+ try:
1545
+ # Run without checkpoints
1546
+ logger.info("🔄 Executing LangGraph workflow...")
1547
+ return await self.app.ainvoke(initial_state)
1548
+ finally:
1549
+ # Clean up running agents
1550
+ async with self._control_lock:
1551
+ self._running_agents.clear()
1552
+
1553
+ # Execute workflow as a task for control management
1554
+ self._execution_task = asyncio.create_task(_execute_workflow())
1555
+ final_state = await self._execution_task
1556
+
1557
+ # Update current state reference
1558
+ async with self._control_lock:
1559
+ self._current_state = final_state
1560
+
1561
+ # Get final result
1562
+ result = await self._get_result(final_state)
1563
+ logger.info("✅ VibeSurfAgent execution completed")
1564
+ return result
1565
+
1566
+ except asyncio.CancelledError:
1567
+ logger.info("🛑 VibeSurfAgent execution was cancelled")
1568
+ return f"# Task Execution Cancelled\n\n**Task:** {task}\n\nExecution was stopped by user request."
1569
+ except Exception as e:
1570
+ logger.error(f"❌ VibeSurfAgent execution failed: {e}")
1571
+ return f"# Task Execution Failed\n\n**Task:** {task}\n\n**Error:** {str(e)}\n\nPlease try again or contact support."
1572
+ finally:
1573
+ # Reset state
1574
+ self.save_message_history()
1575
+ self.save_activity_logs()
1576
+ if agent_activity_logs:
1577
+ activity_entry = {
1578
+ "agent_name": "VibeSurfAgent",
1579
+ "agent_status": "done", # working, result, error
1580
+ "agent_msg": "Finish Task."
1581
+ }
1582
+ agent_activity_logs.append(activity_entry)
1583
+ async with self._control_lock:
1584
+ self._current_state = None
1585
+ self._execution_task = None
1586
+ self._running_agents.clear()
1587
+
1588
+ def get_activity_logs(self, session_id: Optional[str] = None, message_index: Optional[int] = None) -> Optional[
1589
+ List[Dict]]:
1590
+ if session_id is None:
1591
+ session_id = self.cur_session_id
1592
+
1593
+ logger.info(f"📊 GET_ACTIVITY_LOGS DEBUG - Session: {session_id}, Message Index: {message_index}, Current Session: {self.cur_session_id}")
1594
+
1595
+ # Ensure session_id exists in activity_logs
1596
+ if session_id not in self.activity_logs:
1597
+ logger.warning(f"⚠️ Session {session_id} not found in activity_logs. Available sessions: {list(self.activity_logs.keys())}")
1598
+ return None
1599
+
1600
+ session_logs = self.activity_logs[session_id]
1601
+ logger.info(f"📋 Session {session_id} has {len(session_logs)} activity logs")
1602
+
1603
+ if message_index is None:
1604
+ logger.info(f"📤 Returning all {len(session_logs)} activity logs for session {session_id}")
1605
+ return session_logs
1606
+ else:
1607
+ if message_index >= len(session_logs):
1608
+ logger.debug(f"⚠️ Message index {message_index} out of range for session {session_id} (max index: {len(session_logs) - 1})")
1609
+ return None
1610
+ else:
1611
+ activity_log = session_logs[message_index]
1612
+ logger.info(f"📤 Returning activity log at index {message_index}: {activity_log.get('agent_name', 'unknown')} - {activity_log.get('agent_status', 'unknown')}")
1613
+ return activity_log
1614
+
1615
+ async def _get_result(self, state) -> str:
1616
+ """Get the final result from execution with simplified workflow support"""
1617
+ # Handle both dict and dataclass state types due to LangGraph serialization
1618
+ simple_response = state.get('simple_response') if isinstance(state, dict) else getattr(state, 'simple_response',
1619
+ None)
1620
+ final_summary = state.get('final_summary') if isinstance(state, dict) else getattr(state, 'final_summary', None)
1621
+ original_task = state.get('original_task') if isinstance(state, dict) else getattr(state, 'original_task',
1622
+ 'Unknown task')
1623
+
1624
+ # Fallback for cases where state doesn't support logging
1625
+ if simple_response:
1626
+ return f"# Response\n\n{simple_response}"
1627
+ elif final_summary:
1628
+ return final_summary
1629
+ else:
1630
+ return f"# Task Execution Completed\n\n**Task:** {original_task}\n\nTask execution completed but no detailed result available."
1631
+
1632
+ workflow = create_vibe_surf_workflow()