vibesurf 0.1.16__py3-none-any.whl → 0.1.18__py3-none-any.whl

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

Potentially problematic release.


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

vibe_surf/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.16'
32
- __version_tuple__ = version_tuple = (0, 1, 16)
31
+ __version__ = version = '0.1.18'
32
+ __version_tuple__ = version_tuple = (0, 1, 18)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -566,12 +566,6 @@ class BrowserUseAgent(Agent):
566
566
  agent_run_error = 'Agent stopped programmatically'
567
567
  break
568
568
 
569
- while self.state.paused:
570
- await asyncio.sleep(0.2) # Small delay to prevent CPU spinning
571
- if self.state.stopped: # Allow stopping while paused
572
- agent_run_error = 'Agent stopped programmatically while paused'
573
- break
574
-
575
569
  if on_step_start is not None:
576
570
  await on_step_start(self)
577
571
 
@@ -820,12 +814,12 @@ class BrowserUseAgent(Agent):
820
814
  i = global_action_index + local_index
821
815
 
822
816
  # Original sequential execution logic continues here...
823
- if i > 0:
824
- # ONLY ALLOW TO CALL `done` IF IT IS A SINGLE ACTION
825
- if action.model_dump(exclude_unset=True).get('done') is not None:
826
- msg = f'Done action is allowed only as a single action - stopped after action {i} / {total_actions}.'
827
- self.logger.debug(msg)
828
- break
817
+ # if i > 0:
818
+ # # ONLY ALLOW TO CALL `done` IF IT IS A SINGLE ACTION
819
+ # if action.model_dump(exclude_unset=True).get('done') is not None:
820
+ # msg = f'Done action is allowed only as a single action - stopped after action {i} / {total_actions}.'
821
+ # self.logger.debug(msg)
822
+ # break
829
823
 
830
824
  # DOM synchronization check - verify element indexes are still valid AFTER first action
831
825
  if action.get_index() is not None and i != 0:
@@ -32,7 +32,7 @@ class ReportTaskResult(BaseModel):
32
32
  class ReportWriterAgent:
33
33
  """Agent responsible for generating HTML reports using LLM-controlled flow"""
34
34
 
35
- def __init__(self, llm: BaseChatModel, workspace_dir: str, step_callback=None, thinking_mode: bool = True):
35
+ def __init__(self, llm: BaseChatModel, workspace_dir: str, step_callback=None, use_thinking: bool = True):
36
36
  """
37
37
  Initialize ReportWriterAgent
38
38
 
@@ -44,7 +44,7 @@ class ReportWriterAgent:
44
44
  self.llm = llm
45
45
  self.workspace_dir = os.path.abspath(workspace_dir)
46
46
  self.step_callback = step_callback
47
- self.thinking_mode = thinking_mode
47
+ self.use_thinking = use_thinking
48
48
 
49
49
  # Initialize file system and tools
50
50
  self.file_system = CustomFileSystem(self.workspace_dir)
@@ -52,7 +52,7 @@ class ReportWriterAgent:
52
52
 
53
53
  # Setup action model and agent output
54
54
  self.ActionModel = self.tools.registry.create_action_model()
55
- if self.thinking_mode:
55
+ if self.use_thinking:
56
56
  self.AgentOutput = CustomAgentOutput.type_with_custom_actions(self.ActionModel)
57
57
  else:
58
58
  self.AgentOutput = CustomAgentOutput.type_with_custom_actions_no_thinking(self.ActionModel)
@@ -38,6 +38,7 @@ from vibe_surf.browser.browser_manager import BrowserManager
38
38
  from vibe_surf.tools.browser_use_tools import BrowserUseTools
39
39
  from vibe_surf.tools.vibesurf_tools import VibeSurfTools
40
40
  from vibe_surf.tools.file_system import CustomFileSystem
41
+ from vibe_surf.agents.views import VibeSurfAgentSettings
41
42
 
42
43
  from vibe_surf.logger import get_logger
43
44
 
@@ -213,11 +214,11 @@ def create_browser_agent_step_callback(state: VibeSurfState, agent_name: str):
213
214
  step_msg = f"## Step {step_num}\n\n"
214
215
 
215
216
  # Add thinking if present
216
- if agent_output.thinking:
217
+ if hasattr(agent_output, 'thinking') and agent_output.thinking:
217
218
  step_msg += f"**💡 Thinking:**\n{agent_output.thinking}\n\n"
218
219
 
219
220
  # Add evaluation if present
220
- if agent_output.evaluation_previous_goal:
221
+ if hasattr(agent_output, 'evaluation_previous_goal') and agent_output.evaluation_previous_goal:
221
222
  step_msg += f"**👍 Evaluation:**\n{agent_output.evaluation_previous_goal}\n\n"
222
223
 
223
224
  # Add memory if present
@@ -225,7 +226,7 @@ def create_browser_agent_step_callback(state: VibeSurfState, agent_name: str):
225
226
  # step_msg += f"**🧠 Memory:** {agent_output.memory}\n\n"
226
227
 
227
228
  # Add next goal if present
228
- if agent_output.next_goal:
229
+ if hasattr(agent_output, 'next_goal') and agent_output.next_goal:
229
230
  step_msg += f"**🎯 Next Goal:**\n{agent_output.next_goal}\n\n"
230
231
 
231
232
  # Add action summary
@@ -352,7 +353,7 @@ async def _vibesurf_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
352
353
  # Create action model and agent output using VibeSurfTools
353
354
  vibesurf_agent = state.vibesurf_agent
354
355
  ActionModel = vibesurf_agent.tools.registry.create_action_model()
355
- if vibesurf_agent.thinking_mode:
356
+ if vibesurf_agent.settings.agent_mode == "thinking":
356
357
  AgentOutput = CustomAgentOutput.type_with_custom_actions(ActionModel)
357
358
  else:
358
359
  AgentOutput = CustomAgentOutput.type_with_custom_actions_no_thinking(ActionModel)
@@ -632,7 +633,9 @@ async def execute_parallel_browser_tasks(state: VibeSurfState) -> List[BrowserTa
632
633
  file_system_path=str(bu_agent_workdir),
633
634
  register_new_step_callback=step_callback,
634
635
  extend_system_message=EXTEND_BU_SYSTEM_PROMPT,
635
- token_cost_service=state.vibesurf_agent.token_cost_service
636
+ token_cost_service=state.vibesurf_agent.token_cost_service,
637
+ flash_mode=state.vibesurf_agent.settings.agent_mode == "flash",
638
+ use_thinking=state.vibesurf_agent.settings.agent_mode == "thinking"
636
639
  )
637
640
  agents.append(agent)
638
641
 
@@ -783,7 +786,9 @@ async def execute_single_browser_tasks(state: VibeSurfState) -> BrowserTaskResul
783
786
  file_system_path=str(bu_agent_workdir),
784
787
  register_new_step_callback=step_callback,
785
788
  extend_system_message=EXTEND_BU_SYSTEM_PROMPT,
786
- token_cost_service=state.vibesurf_agent.token_cost_service
789
+ token_cost_service=state.vibesurf_agent.token_cost_service,
790
+ flash_mode=state.vibesurf_agent.settings.agent_mode == "flash",
791
+ use_thinking=state.vibesurf_agent.settings.agent_mode == "thinking"
787
792
  )
788
793
  if state.vibesurf_agent and hasattr(state.vibesurf_agent, '_running_agents'):
789
794
  state.vibesurf_agent._running_agents[agent_id] = agent
@@ -861,7 +866,7 @@ async def _report_task_execution_node_impl(state: VibeSurfState) -> VibeSurfStat
861
866
  llm=state.vibesurf_agent.llm,
862
867
  workspace_dir=str(state.vibesurf_agent.file_system.get_dir()),
863
868
  step_callback=step_callback,
864
- thinking_mode=state.vibesurf_agent.thinking_mode,
869
+ use_thinking=state.vibesurf_agent.settings.agent_mode == "thinking",
865
870
  )
866
871
 
867
872
  # Register report writer agent for control coordination
@@ -997,19 +1002,17 @@ class VibeSurfAgent:
997
1002
  browser_manager: BrowserManager,
998
1003
  tools: VibeSurfTools,
999
1004
  workspace_dir: str = "./workspace",
1000
- thinking_mode: bool = True,
1001
- calculate_token_cost: bool = True,
1005
+ settings: Optional[VibeSurfAgentSettings] = None
1002
1006
  ):
1003
1007
  """Initialize VibeSurfAgent with required components"""
1004
1008
  self.llm: BaseChatModel = llm
1005
- self.calculate_token_cost = calculate_token_cost
1006
- self.token_cost_service = TokenCost(include_cost=calculate_token_cost)
1009
+ self.settings = settings or VibeSurfAgentSettings()
1010
+ self.token_cost_service = TokenCost(include_cost=self.settings.calculate_cost)
1007
1011
  self.token_cost_service.register_llm(llm)
1008
1012
  self.browser_manager: BrowserManager = browser_manager
1009
1013
  self.tools: VibeSurfTools = tools
1010
1014
  self.workspace_dir = workspace_dir
1011
1015
  os.makedirs(self.workspace_dir, exist_ok=True)
1012
- self.thinking_mode = thinking_mode
1013
1016
 
1014
1017
  self.cur_session_id = None
1015
1018
  self.file_system: Optional[CustomFileSystem] = None
@@ -1540,7 +1543,7 @@ Please continue with your assigned work, incorporating this guidance only if it'
1540
1543
  task: str,
1541
1544
  upload_files: Optional[List[str]] = None,
1542
1545
  session_id: Optional[str] = None,
1543
- thinking_mode: bool = True
1546
+ agent_mode: str = "thinking"
1544
1547
  ) -> str | None:
1545
1548
  """
1546
1549
  Main execution method that returns markdown summary with control capabilities
@@ -1554,7 +1557,7 @@ Please continue with your assigned work, incorporating this guidance only if it'
1554
1557
  """
1555
1558
  logger.info(f"🚀 Starting VibeSurfAgent execution for task: {task}. Powered by LLM model: {self.llm.model_name}")
1556
1559
  try:
1557
- self.thinking_mode = thinking_mode
1560
+ self.settings.agent_mode = agent_mode
1558
1561
  session_id = session_id or self.cur_session_id or uuid7str()
1559
1562
  if session_id != self.cur_session_id:
1560
1563
  # Load session-specific data when switching sessions
vibe_surf/agents/views.py CHANGED
@@ -66,11 +66,13 @@ class VibeSurfAgentSettings(BaseModel):
66
66
  max_history_items: int | None = None
67
67
  include_token_cost: bool = False
68
68
 
69
- calculate_cost: bool = False
69
+ calculate_cost: bool = True
70
70
  include_tool_call_examples: bool = False
71
71
  llm_timeout: int = 60 # Timeout in seconds for LLM calls
72
72
  step_timeout: int = 180 # Timeout in seconds for each step
73
73
 
74
+ agent_mode: str = "thinking" # thinking, no-thinking, flash
75
+
74
76
 
75
77
  class CustomAgentOutput(BaseModel):
76
78
  model_config = ConfigDict(arbitrary_types_allowed=True, extra='forbid')
@@ -117,7 +119,7 @@ class CustomAgentOutput(BaseModel):
117
119
  return schema
118
120
 
119
121
  model = create_model(
120
- 'AgentOutput',
122
+ 'AgentOutputNoThinking',
121
123
  __base__=AgentOutputNoThinking,
122
124
  action=(
123
125
  list[custom_actions], # type: ignore
@@ -106,6 +106,7 @@ class TaskCreateRequest(BaseModel):
106
106
  llm_profile_name: str = Field(description="LLM profile name to use")
107
107
  upload_files_path: Optional[str] = Field(default=None, description="Path to uploaded files")
108
108
  mcp_server_config: Optional[Dict[str, Any]] = Field(default=None, description="MCP server configuration")
109
+ agent_mode: str = Field(default="thinking", description="Agent mode: 'thinking', 'no-thinking', or 'flash'")
109
110
 
110
111
  class TaskControlRequest(BaseModel):
111
112
  """Request model for task control operations (pause/resume/stop)"""
@@ -121,6 +122,7 @@ class TaskResponse(BaseModel):
121
122
  upload_files_path: Optional[str] = None
122
123
  workspace_dir: Optional[str] = None
123
124
  mcp_server_config: Optional[Dict[str, Any]] = None
125
+ agent_mode: str = "thinking"
124
126
  task_result: Optional[str] = None
125
127
  error_message: Optional[str] = None
126
128
  report_path: Optional[str] = None
@@ -145,6 +147,7 @@ class TaskResponse(BaseModel):
145
147
  upload_files_path=task.upload_files_path,
146
148
  workspace_dir=task.workspace_dir,
147
149
  mcp_server_config=task.mcp_server_config,
150
+ agent_mode=task.agent_mode,
148
151
  task_result=task.task_result,
149
152
  error_message=task.error_message,
150
153
  report_path=task.report_path,
@@ -117,7 +117,8 @@ async def submit_task(
117
117
  mcp_server_config=mcp_server_config,
118
118
  llm_profile_name=task_request.llm_profile_name,
119
119
  workspace_dir=workspace_dir,
120
- task_status="pending"
120
+ task_status="pending",
121
+ agent_mode=task_request.agent_mode
121
122
  )
122
123
  await db.commit()
123
124
 
@@ -129,6 +130,7 @@ async def submit_task(
129
130
  task=task_request.task_description,
130
131
  llm_profile_name=task_request.llm_profile_name,
131
132
  upload_files=task_request.upload_files_path,
133
+ agent_mode=task_request.agent_mode,
132
134
  db_session=db
133
135
  )
134
136
 
@@ -6,18 +6,145 @@ with optimized configuration for real-time operations.
6
6
  """
7
7
 
8
8
  import asyncio
9
+ import os
10
+ import glob
11
+ import pdb
12
+ import re
13
+ from pathlib import Path
14
+ from typing import AsyncGenerator, List, Tuple, Optional
15
+ import logging
16
+ import aiosqlite
9
17
  from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
10
18
  from sqlalchemy.orm import sessionmaker
11
19
  from sqlalchemy.pool import StaticPool
12
20
  from .models import Base
13
- from typing import AsyncGenerator
14
- import logging
15
21
 
16
22
  from vibe_surf.logger import get_logger
17
23
 
18
24
  logger = get_logger(__name__)
19
25
 
20
26
 
27
+ class DBMigrationManager:
28
+ """Simplified database migration manager."""
29
+
30
+ def __init__(self, db_path: str = None):
31
+ """Initialize migration manager
32
+
33
+ Args:
34
+ db_path: Database file path. Will be extracted from database_url if not provided.
35
+ """
36
+ if db_path is None:
37
+ # Extract database path from shared_state
38
+ from .. import shared_state
39
+ database_url = os.getenv(
40
+ 'VIBESURF_DATABASE_URL',
41
+ f'sqlite+aiosqlite:///{os.path.join(shared_state.workspace_dir, "vibe_surf.db")}'
42
+ )
43
+ self.db_path = database_url
44
+ else:
45
+ self.db_path = db_path
46
+
47
+ # Extract path from sqlite URL
48
+ if self.db_path.startswith('sqlite+aiosqlite:///'):
49
+ self.db_path = self.db_path[20:] # Remove 'sqlite+aiosqlite:///' prefix
50
+ else:
51
+ raise ValueError(f"Migration manager only supports SQLite databases, got: {self.db_path}")
52
+
53
+ self.migrations_dir = Path(__file__).parent / "migrations"
54
+
55
+ async def get_db_version(self) -> int:
56
+ """Get current database version."""
57
+ try:
58
+ async with aiosqlite.connect(self.db_path) as db:
59
+ cursor = await db.execute("PRAGMA user_version;")
60
+ result = await cursor.fetchone()
61
+ return result[0] if result else 0
62
+ except Exception:
63
+ return 0
64
+
65
+ async def set_db_version(self, version: int) -> None:
66
+ """Set database version."""
67
+ async with aiosqlite.connect(self.db_path) as db:
68
+ await db.execute(f"PRAGMA user_version = {version};")
69
+ await db.commit()
70
+
71
+ def get_migration_files(self) -> List[Tuple[int, str]]:
72
+ """Get migration files sorted by version. Returns (version, filepath) tuples."""
73
+ migrations = []
74
+ pattern = str(self.migrations_dir / "v*.sql")
75
+
76
+ for filepath in glob.glob(pattern):
77
+ filename = os.path.basename(filepath)
78
+ match = re.match(r'v(\d+)_.*\.sql$', filename)
79
+ if match:
80
+ version = int(match.group(1))
81
+ migrations.append((version, filepath))
82
+ else:
83
+ logger.warning(f"Migration file {filename} doesn't match pattern v000_description.sql")
84
+
85
+ return sorted(migrations, key=lambda x: x[0])
86
+
87
+ async def init_database(self) -> None:
88
+ """Initialize database directory if needed."""
89
+ db_dir = os.path.dirname(self.db_path)
90
+ os.makedirs(db_dir, exist_ok=True)
91
+
92
+ current_version = await self.get_db_version()
93
+
94
+ if current_version == 0:
95
+ logger.info("Database version is 0, will be initialized via migrations...")
96
+ else:
97
+ logger.info(f"Database already exists (version {current_version})")
98
+
99
+ async def apply_migrations(self, target_version: Optional[int] = None) -> int:
100
+ """Apply pending migrations. Returns final version."""
101
+ await self.init_database() # Ensure database exists
102
+
103
+ current_version = await self.get_db_version()
104
+ migrations = self.get_migration_files()
105
+
106
+ if not migrations:
107
+ logger.info("No migration files found")
108
+ return current_version
109
+
110
+ if target_version is None:
111
+ target_version = max(m[0] for m in migrations)
112
+
113
+ logger.info(f"Current version: {current_version}, Target: {target_version}")
114
+
115
+ if current_version >= target_version:
116
+ logger.info("Database is up to date")
117
+ return current_version
118
+
119
+ applied = 0
120
+ async with aiosqlite.connect(self.db_path) as db:
121
+ await db.execute("PRAGMA foreign_keys = ON;")
122
+
123
+ for version, filepath in migrations:
124
+ if version <= current_version or version > target_version:
125
+ continue
126
+
127
+ try:
128
+ logger.info(f"Applying migration v{version:03d}: {os.path.basename(filepath)}")
129
+
130
+ with open(filepath, 'r', encoding='utf-8') as f:
131
+ sql_content = f.read()
132
+
133
+ await db.executescript(sql_content)
134
+ await self.set_db_version(version)
135
+ applied += 1
136
+
137
+ logger.info(f"Successfully applied migration v{version:03d}")
138
+
139
+ except Exception as e:
140
+ logger.error(f"Migration v{version:03d} failed: {e}")
141
+ raise RuntimeError(f"Migration failed at version {version}: {e}")
142
+
143
+ final_version = await self.get_db_version()
144
+ logger.info(f"Applied {applied} migrations. Final version: {final_version}")
145
+ return final_version
146
+
147
+
21
148
  class DatabaseManager:
22
149
  """Database connection and session management"""
23
150
 
@@ -61,11 +188,38 @@ class DatabaseManager:
61
188
  class_=AsyncSession,
62
189
  expire_on_commit=False
63
190
  )
191
+
192
+ # Initialize migration manager for SQLite databases
193
+ if self.database_url.startswith('sqlite'):
194
+ try:
195
+ self.migration_manager = DBMigrationManager(db_path=self.database_url)
196
+ except Exception as e:
197
+ logger.warning(f"Failed to initialize migration manager: {e}")
198
+ self.migration_manager = None
199
+ else:
200
+ self.migration_manager = None
201
+ logger.info("Migration manager is only supported for SQLite databases")
64
202
 
65
- async def create_tables(self):
66
- """Create all database tables"""
203
+ async def create_tables(self, use_migrations: bool = True):
204
+ """Create all database tables
205
+
206
+ Args:
207
+ use_migrations: If True, use migration system. If False, use direct table creation.
208
+ """
209
+ if use_migrations and self.migration_manager:
210
+ logger.info("🔄 Using migration system to initialize database...")
211
+ try:
212
+ await self.migration_manager.apply_migrations()
213
+ logger.info("✅ Database initialized via migrations")
214
+ return
215
+ except Exception as e:
216
+ logger.warning(f"Migration failed, falling back to direct table creation: {e}")
217
+
218
+ # Fallback to direct table creation
219
+ logger.info("🔄 Using direct table creation...")
67
220
  async with self.engine.begin() as conn:
68
221
  await conn.run_sync(Base.metadata.create_all)
222
+ logger.info("✅ Database initialized via direct table creation")
69
223
 
70
224
  async def drop_tables(self):
71
225
  """Drop all database tables"""
@@ -84,6 +238,35 @@ class DatabaseManager:
84
238
  finally:
85
239
  await session.close()
86
240
 
241
+ async def apply_migrations(self, target_version: Optional[int] = None) -> int:
242
+ """Apply database migrations
243
+
244
+ Args:
245
+ target_version: Target migration version. If None, applies all available migrations.
246
+
247
+ Returns:
248
+ Final database version after applying migrations.
249
+
250
+ Raises:
251
+ RuntimeError: If migration manager is not available or migration fails.
252
+ """
253
+ if not self.migration_manager:
254
+ raise RuntimeError("Migration manager is not available. Only SQLite databases support migrations.")
255
+
256
+ return await self.migration_manager.apply_migrations(target_version)
257
+
258
+ async def get_db_version(self) -> int:
259
+ """Get current database version
260
+
261
+ Returns:
262
+ Current database version, or 0 if not available.
263
+ """
264
+ if not self.migration_manager:
265
+ logger.warning("Migration manager not available, cannot get database version")
266
+ return 0
267
+
268
+ return await self.migration_manager.get_db_version()
269
+
87
270
  async def close(self):
88
271
  """Close database connections"""
89
272
  await self.engine.dispose()
@@ -0,0 +1,118 @@
1
+ -- Migration: v001_initial_schema.sql
2
+ -- Description: Initial database schema creation
3
+ -- Version: 0.0.1
4
+
5
+ -- Enable foreign keys
6
+ PRAGMA foreign_keys = ON;
7
+
8
+ -- Create LLM Profiles table
9
+ CREATE TABLE IF NOT EXISTS llm_profiles (
10
+ profile_id VARCHAR(36) NOT NULL PRIMARY KEY,
11
+ profile_name VARCHAR(100) NOT NULL UNIQUE,
12
+ provider VARCHAR(50) NOT NULL,
13
+ model VARCHAR(100) NOT NULL,
14
+ base_url VARCHAR(500),
15
+ encrypted_api_key TEXT,
16
+ temperature JSON,
17
+ max_tokens JSON,
18
+ top_p JSON,
19
+ frequency_penalty JSON,
20
+ seed JSON,
21
+ provider_config JSON,
22
+ description TEXT,
23
+ is_active BOOLEAN NOT NULL DEFAULT 1,
24
+ is_default BOOLEAN NOT NULL DEFAULT 0,
25
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
26
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
27
+ last_used_at DATETIME
28
+ );
29
+
30
+ -- Create Tasks table
31
+ CREATE TABLE IF NOT EXISTS tasks (
32
+ task_id VARCHAR(36) NOT NULL PRIMARY KEY,
33
+ session_id VARCHAR(36) NOT NULL,
34
+ task_description TEXT NOT NULL,
35
+ status VARCHAR(9) NOT NULL DEFAULT 'pending',
36
+ llm_profile_name VARCHAR(100) NOT NULL,
37
+ upload_files_path VARCHAR(500),
38
+ workspace_dir VARCHAR(500),
39
+ mcp_server_config TEXT,
40
+ task_result TEXT,
41
+ error_message TEXT,
42
+ report_path VARCHAR(500),
43
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
44
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
45
+ started_at DATETIME,
46
+ completed_at DATETIME,
47
+ task_metadata JSON,
48
+ CHECK (status IN ('pending', 'running', 'paused', 'completed', 'failed', 'stopped'))
49
+ );
50
+
51
+ -- Create Uploaded Files table
52
+ CREATE TABLE IF NOT EXISTS uploaded_files (
53
+ file_id VARCHAR(36) NOT NULL PRIMARY KEY,
54
+ original_filename VARCHAR(255) NOT NULL,
55
+ stored_filename VARCHAR(255) NOT NULL,
56
+ file_path TEXT NOT NULL,
57
+ session_id VARCHAR(255),
58
+ file_size BIGINT NOT NULL,
59
+ mime_type VARCHAR(100) NOT NULL,
60
+ upload_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
61
+ relative_path TEXT NOT NULL,
62
+ is_deleted BOOLEAN NOT NULL DEFAULT 0,
63
+ deleted_at DATETIME
64
+ );
65
+
66
+ -- Create MCP Profiles table
67
+ CREATE TABLE IF NOT EXISTS mcp_profiles (
68
+ mcp_id VARCHAR(36) NOT NULL PRIMARY KEY,
69
+ display_name VARCHAR(100) NOT NULL UNIQUE,
70
+ mcp_server_name VARCHAR(100) NOT NULL UNIQUE,
71
+ mcp_server_params JSON NOT NULL,
72
+ description TEXT,
73
+ is_active BOOLEAN NOT NULL DEFAULT 1,
74
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
75
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
76
+ last_used_at DATETIME
77
+ );
78
+
79
+ -- Create indexes for performance
80
+ CREATE INDEX IF NOT EXISTS idx_llm_profiles_name ON llm_profiles(profile_name);
81
+ CREATE INDEX IF NOT EXISTS idx_llm_profiles_active ON llm_profiles(is_active);
82
+ CREATE INDEX IF NOT EXISTS idx_llm_profiles_default ON llm_profiles(is_default);
83
+ CREATE INDEX IF NOT EXISTS idx_llm_profiles_provider ON llm_profiles(provider);
84
+
85
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
86
+ CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);
87
+ CREATE INDEX IF NOT EXISTS idx_tasks_llm_profile ON tasks(llm_profile_name);
88
+ CREATE INDEX IF NOT EXISTS idx_tasks_created ON tasks(created_at);
89
+
90
+ CREATE INDEX IF NOT EXISTS idx_uploaded_files_session_time ON uploaded_files(session_id, upload_time);
91
+ CREATE INDEX IF NOT EXISTS idx_uploaded_files_active ON uploaded_files(is_deleted, upload_time);
92
+ CREATE INDEX IF NOT EXISTS idx_uploaded_files_filename ON uploaded_files(original_filename);
93
+
94
+ CREATE INDEX IF NOT EXISTS idx_mcp_profiles_display_name ON mcp_profiles(display_name);
95
+ CREATE INDEX IF NOT EXISTS idx_mcp_profiles_server_name ON mcp_profiles(mcp_server_name);
96
+ CREATE INDEX IF NOT EXISTS idx_mcp_profiles_active ON mcp_profiles(is_active);
97
+
98
+ -- Create triggers for automatic timestamp updates
99
+ CREATE TRIGGER IF NOT EXISTS update_llm_profiles_updated_at
100
+ AFTER UPDATE ON llm_profiles
101
+ FOR EACH ROW
102
+ BEGIN
103
+ UPDATE llm_profiles SET updated_at = CURRENT_TIMESTAMP WHERE profile_id = OLD.profile_id;
104
+ END;
105
+
106
+ CREATE TRIGGER IF NOT EXISTS update_tasks_updated_at
107
+ AFTER UPDATE ON tasks
108
+ FOR EACH ROW
109
+ BEGIN
110
+ UPDATE tasks SET updated_at = CURRENT_TIMESTAMP WHERE task_id = OLD.task_id;
111
+ END;
112
+
113
+ CREATE TRIGGER IF NOT EXISTS update_mcp_profiles_updated_at
114
+ AFTER UPDATE ON mcp_profiles
115
+ FOR EACH ROW
116
+ BEGIN
117
+ UPDATE mcp_profiles SET updated_at = CURRENT_TIMESTAMP WHERE mcp_id = OLD.mcp_id;
118
+ END;
@@ -0,0 +1,6 @@
1
+ -- Migration: v002_add_agent_mode.sql
2
+ -- Description: Add agent_mode column to tasks table
3
+ -- Version: 0.0.2
4
+
5
+ -- Add agent_mode column to tasks table with default value 'thinking'
6
+ ALTER TABLE tasks ADD COLUMN agent_mode VARCHAR(50) DEFAULT 'thinking';
@@ -71,7 +71,7 @@ class Task(Base):
71
71
 
72
72
  # Task definition
73
73
  task_description = Column(Text, nullable=False)
74
- status = Column(Enum(TaskStatus), nullable=False, default=TaskStatus.PENDING)
74
+ status = Column(Enum(TaskStatus, values_callable=lambda obj: [e.value for e in obj]), nullable=False, default=TaskStatus.PENDING)
75
75
 
76
76
  # LLM Profile reference (instead of storing LLM config directly)
77
77
  llm_profile_name = Column(String(100), nullable=False) # Reference to LLMProfile.profile_name
@@ -83,6 +83,9 @@ class Task(Base):
83
83
  # Configuration (JSON strings without API keys)
84
84
  mcp_server_config = Column(Text, nullable=True) # MCP server config as JSON string
85
85
 
86
+ # Agent execution mode
87
+ agent_mode = Column(String(50), nullable=False, default='thinking') # Agent mode: 'thinking' or 'direct'
88
+
86
89
  # Results
87
90
  task_result = Column(Text, nullable=True) # Final markdown result
88
91
  error_message = Column(Text, nullable=True)
@@ -454,7 +454,8 @@ class TaskQueries:
454
454
  task_result: Optional[str] = None,
455
455
  task_status: str = "pending",
456
456
  error_message: Optional[str] = None,
457
- report_path: Optional[str] = None
457
+ report_path: Optional[str] = None,
458
+ agent_mode: str = "thinking"
458
459
  ) -> Task:
459
460
  """Create or update a task record"""
460
461
  try:
@@ -507,7 +508,8 @@ class TaskQueries:
507
508
  mcp_server_config=mcp_server_config_json,
508
509
  task_result=task_result,
509
510
  error_message=error_message,
510
- report_path=report_path
511
+ report_path=report_path,
512
+ agent_mode=agent_mode
511
513
  )
512
514
 
513
515
  db.add(task)
@@ -106,6 +106,7 @@ async def execute_task_background(
106
106
  task: str,
107
107
  llm_profile_name: str,
108
108
  upload_files: Optional[List[str]] = None,
109
+ agent_mode: str = "thinking",
109
110
  db_session=None
110
111
  ):
111
112
  """Background task execution function for single task with LLM profile support"""
@@ -141,7 +142,8 @@ async def execute_task_background(
141
142
  result = await vibesurf_agent.run(
142
143
  task=task,
143
144
  upload_files=upload_files,
144
- session_id=session_id
145
+ session_id=session_id,
146
+ agent_mode=agent_mode
145
147
  )
146
148
 
147
149
  # Update task status to completed
@@ -400,8 +402,8 @@ async def initialize_vibesurf_components():
400
402
 
401
403
  db_manager = DatabaseManager(database_url)
402
404
 
403
- # Initialize database tables
404
- await db_manager.create_tables()
405
+ # Initialize database tables with migration support
406
+ await db_manager.create_tables(use_migrations=True)
405
407
  logger.info("✅ Database manager initialized successfully")
406
408
 
407
409
  # Initialize LLM from default profile (if available) or fallback to environment variables