vibesurf 0.1.21__py3-none-any.whl → 0.1.23__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.21'
32
- __version_tuple__ = version_tuple = (0, 1, 21)
31
+ __version__ = version = '0.1.23'
32
+ __version_tuple__ = version_tuple = (0, 1, 23)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -525,7 +525,7 @@ class BrowserUseAgent(Agent):
525
525
  signal_handler.register()
526
526
 
527
527
  try:
528
- self._log_agent_run()
528
+ await self._log_agent_run()
529
529
 
530
530
  self.logger.debug(
531
531
  f'🔧 Agent setup: Task ID {self.task_id[-4:]}, Session ID {self.session_id[-4:]}, Browser Session ID {self.browser_session.id[-4:] if self.browser_session else "None"}'
@@ -65,6 +65,15 @@ You will receive contextual information including:
65
65
  - Include file references in task descriptions when relevant
66
66
  - All file operations automatically resolve relative to the workspace directory
67
67
 
68
+ ## Skills Command Processing
69
+ - When users input commands in `/skill_name` format, please use the corresponding skill action:
70
+ - **Tab Targeting[Optional]**: Such as `/crawl @1234` → Execute `skill_crawl` with tab_id "1234"
71
+ - **Parameter Processing**: Sometimes user provide uncompleted or simple prompt, please convert it to correct and optimized params. Such as convert natural language to valid JavaScript for code skill
72
+ - **Special Cases**: `skill_deep_research` only returns guidelines only, then follow guidelines to conduct actual research
73
+ - **Execution Policy**: Skill actions execute only once (no need to retry if errors occur), and all results - whether successful or failed - should be presented to users in structured markdown format.
74
+ - **Follow-up Operations**: When users input skill operations without specifying additional tasks, do not automatically perform subsequent operations. Only perform additional tool operations when users specifically request actions like saving results to files or writing reports.
75
+ - After `/search` completion, NEVER use browser agent to deeply investigate search result (unless explicitly emphasized by the user). The user usually only need the search results. Just return the search results.
76
+
68
77
  ## Language Adaptability
69
78
 
70
79
  **Critical**: Your output language must match the user's request language. If the user communicates in Chinese, respond in Chinese. If in English, respond in English. Maintain consistency throughout the interaction.
@@ -77,6 +86,7 @@ Before executing any action:
77
86
  3. **Plan Resource Usage**: Consider tab management and session optimization
78
87
  4. **Validate Completeness**: Ensure all user requirements are addressed
79
88
 
89
+
80
90
  Execute with precision, leverage concurrent capabilities for efficiency, and deliver professional results that exceed expectations.
81
91
  """
82
92
 
@@ -86,4 +96,5 @@ EXTEND_BU_SYSTEM_PROMPT = """
86
96
  * Regarding file operations, please note that you need the full relative path (including subfolders), not just the file name.
87
97
  * Especially when a file operation reports an error, please reflect whether the file path is not written correctly, such as the subfolder is not written.
88
98
  * If you are operating on files in the filesystem, be sure to use relative paths (relative to the workspace dir) instead of absolute paths.
99
+ * If you are typing in the search box, please use Enter key to search instead of clicking.
89
100
  """
@@ -479,6 +479,16 @@ async def _vibesurf_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
479
479
  llm=vibesurf_agent.llm,
480
480
  file_system=vibesurf_agent.file_system,
481
481
  )
482
+ if "skill_search" in action_name or "skill_crawl" in action_name or "skill_summary" in action_name:
483
+ state.current_step = "END"
484
+ # Format final response
485
+ final_response = f"{result.extracted_content}"
486
+ await log_agent_activity(state, agent_name, "result", final_response)
487
+ state.final_response = final_response
488
+ logger.debug(final_response)
489
+ state.is_complete = True
490
+ return state
491
+
482
492
  state.current_step = "vibesurf_agent"
483
493
  if result.extracted_content:
484
494
  vibesurf_agent.message_history.append(
@@ -1479,8 +1489,9 @@ Please continue with your assigned work, incorporating this guidance only if it'
1479
1489
  """
1480
1490
  activity_entry = {
1481
1491
  "agent_name": 'user',
1482
- "agent_status": 'request', # working, result, error
1483
- "agent_msg": f"{new_task}"
1492
+ "agent_status": 'additional_request', # working, result, error
1493
+ "agent_msg": f"{new_task}",
1494
+ "timestamp": datetime.now().isoformat()
1484
1495
  }
1485
1496
  self.activity_logs.append(activity_entry)
1486
1497
 
@@ -0,0 +1,38 @@
1
+ """
2
+ Agent API endpoints
3
+ """
4
+ from typing import List
5
+ from fastapi import APIRouter, HTTPException
6
+
7
+ from vibe_surf.logger import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+ router = APIRouter(prefix="/agent", tags=["agent"])
12
+
13
+
14
+ @router.get("/get_all_skills", response_model=List[str])
15
+ async def get_all_skills():
16
+ """
17
+ Get all available skill names from the VibeSurf tools registry.
18
+ Returns skill names with the 'skill_' prefix removed.
19
+ """
20
+ try:
21
+ from ..shared_state import vibesurf_tools
22
+ if not vibesurf_tools:
23
+ raise HTTPException(status_code=500, detail="VibeSurf tools not initialized")
24
+
25
+ # Get all action names from the registry
26
+ all_actions = vibesurf_tools.registry.registry.actions.keys()
27
+
28
+ # Filter for actions that start with 'skill_' and remove the prefix
29
+ skill_names = [
30
+ action_name.replace('skill_', '')
31
+ for action_name in all_actions
32
+ if action_name.startswith('skill_')
33
+ ]
34
+ logger.info(skill_names)
35
+ return skill_names
36
+
37
+ except Exception as e:
38
+ raise HTTPException(status_code=500, detail=f"Failed to get skills: {str(e)}")
@@ -287,7 +287,7 @@ async def stop_task(control_request: TaskControlRequest):
287
287
  active_task["end_time"] = datetime.now()
288
288
 
289
289
  # Clear active task
290
- # clear_active_task()
290
+ clear_active_task()
291
291
 
292
292
  return {
293
293
  "success": True,
vibe_surf/backend/main.py CHANGED
@@ -21,6 +21,7 @@ from .api.activity import router as activity_router
21
21
  from .api.config import router as config_router
22
22
  from .api.browser import router as browser_router
23
23
  from .api.voices import router as voices_router
24
+ from .api.agent import router as agent_router
24
25
 
25
26
  # Import shared state
26
27
  from . import shared_state
@@ -53,6 +54,7 @@ app.include_router(activity_router, prefix="/api", tags=["activity"])
53
54
  app.include_router(config_router, prefix="/api", tags=["config"])
54
55
  app.include_router(browser_router, prefix="/api", tags=["browser"])
55
56
  app.include_router(voices_router, prefix="/api", tags=["voices"])
57
+ app.include_router(agent_router, prefix="/api", tags=["agent"])
56
58
 
57
59
  # Global variable to control browser monitoring task
58
60
  browser_monitor_task = None
@@ -43,6 +43,7 @@ from browser_use.browser.events import (
43
43
  TabClosedEvent,
44
44
  TabCreatedEvent,
45
45
  )
46
+ from browser_use.browser.profile import BrowserProfile, ProxySettings
46
47
 
47
48
  DEFAULT_BROWSER_PROFILE = AgentBrowserProfile()
48
49
 
@@ -94,7 +95,7 @@ class AgentBrowserSession(BrowserSession):
94
95
  deterministic_rendering: bool | None = None,
95
96
  allowed_domains: list[str] | None = None,
96
97
  keep_alive: bool | None = None,
97
- proxy: any | None = None,
98
+ proxy: ProxySettings | None = None,
98
99
  enable_default_extensions: bool | None = None,
99
100
  window_size: dict | None = None,
100
101
  window_position: dict | None = None,
@@ -406,11 +407,11 @@ class AgentBrowserSession(BrowserSession):
406
407
  self.logger.info('🚫 VibeSurfBrowserSession: AboutBlankWatchdog disabled - no DVD animation will be shown')
407
408
 
408
409
  # Initialize DownloadsWatchdog
409
- DownloadsWatchdog.model_rebuild()
410
- self._downloads_watchdog = DownloadsWatchdog(event_bus=self.event_bus, browser_session=self)
411
- self._downloads_watchdog.attach_to_session()
412
- if self.browser_profile.auto_download_pdfs:
413
- self.logger.info('📄 PDF auto-download enabled for this session')
410
+ # DownloadsWatchdog.model_rebuild()
411
+ # self._downloads_watchdog = DownloadsWatchdog(event_bus=self.event_bus, browser_session=self)
412
+ # self._downloads_watchdog.attach_to_session()
413
+ # if self.browser_profile.auto_download_pdfs:
414
+ # self.logger.info('📄 PDF auto-download enabled for this session')
414
415
 
415
416
  # Initialize LocalBrowserWatchdog
416
417
  LocalBrowserWatchdog.model_rebuild()
@@ -135,12 +135,17 @@ class VibeSurfBackground {
135
135
  console.error('[VibeSurf] Failed to open side panel:', error);
136
136
 
137
137
  // Show notification with helpful message
138
- await chrome.notifications.create({
139
- type: 'basic',
140
- iconUrl: chrome.runtime.getURL('icons/icon48.png') || '',
141
- title: 'VibeSurf',
142
- message: 'Side panel failed. Please update Chrome to the latest version or try right-clicking the extension icon.'
143
- });
138
+ try {
139
+ await chrome.notifications.create({
140
+ type: 'basic',
141
+ iconUrl: '', // Use empty string to avoid icon issues
142
+ title: 'VibeSurf',
143
+ message: 'Side panel failed. Please update Chrome to the latest version or try right-clicking the extension icon.'
144
+ });
145
+ } catch (notifError) {
146
+ console.warn('[VibeSurf] Notification failed:', notifError);
147
+ // Don't throw, just log the warning
148
+ }
144
149
 
145
150
  // Fallback: try to open in new tab
146
151
  try {
@@ -365,56 +370,35 @@ class VibeSurfBackground {
365
370
  }
366
371
 
367
372
  async showNotification(data) {
368
- const { title, message, type = 'info', iconUrl = 'icons/icon48.png' } = data;
373
+ const { title, message, type = 'info', iconUrl } = data;
369
374
 
370
375
  // Map custom types to valid Chrome notification types
371
376
  const validType = ['basic', 'image', 'list', 'progress'].includes(type) ? type : 'basic';
372
377
 
373
- // Validate icon URL and provide fallback
374
- let validatedIconUrl = iconUrl;
375
- try {
376
- // Check if icon URL is accessible
377
- if (iconUrl && iconUrl !== 'icons/icon48.png') {
378
- // For custom icons, validate they exist
379
- const response = await fetch(iconUrl, { method: 'HEAD' });
380
- if (!response.ok) {
381
- validatedIconUrl = 'icons/icon48.png';
382
- }
383
- } else {
384
- // Use default icon, check if it exists
385
- const defaultIconUrl = chrome.runtime.getURL('icons/icon48.png');
386
- const response = await fetch(defaultIconUrl, { method: 'HEAD' });
387
- if (response.ok) {
388
- validatedIconUrl = defaultIconUrl;
389
- } else {
390
- // Fallback to logo.png if icon48.png doesn't exist
391
- const logoUrl = chrome.runtime.getURL('icons/logo.png');
392
- const logoResponse = await fetch(logoUrl, { method: 'HEAD' });
393
- if (logoResponse.ok) {
394
- validatedIconUrl = logoUrl;
395
- } else {
396
- // If no icons work, use empty string (browser will use default)
397
- validatedIconUrl = '';
398
- }
399
- }
400
- }
401
- } catch (error) {
402
- console.warn('[VibeSurf] Icon validation failed, using fallback:', error);
403
- // Use empty string as fallback (browser will use default icon)
404
- validatedIconUrl = '';
405
- }
378
+ // Simplified icon handling - try available icons without validation
379
+ let finalIconUrl = '';
380
+
381
+ // Try to use extension icons in order of preference, but don't validate with fetch
382
+ const iconCandidates = [
383
+ iconUrl ? chrome.runtime.getURL(iconUrl) : null,
384
+ chrome.runtime.getURL('icons/icon48.png'),
385
+ chrome.runtime.getURL('icons/logo.png')
386
+ ].filter(Boolean);
387
+
388
+ // Use the first candidate, or empty string as fallback
389
+ finalIconUrl = iconCandidates[0] || '';
406
390
 
407
391
  try {
408
392
  const notificationId = await chrome.notifications.create({
409
393
  type: validType,
410
- iconUrl: validatedIconUrl,
394
+ iconUrl: finalIconUrl,
411
395
  title: title || 'VibeSurf',
412
396
  message
413
397
  });
414
398
 
415
399
  return { notificationId };
416
400
  } catch (error) {
417
- console.error('[VibeSurf] Failed to create notification:', error);
401
+ console.warn('[VibeSurf] Notification with icon failed, trying without icon:', error);
418
402
  // Try once more with empty icon URL
419
403
  try {
420
404
  const notificationId = await chrome.notifications.create({
@@ -432,12 +416,17 @@ class VibeSurfBackground {
432
416
  }
433
417
 
434
418
  async showWelcomeNotification() {
435
- await chrome.notifications.create({
436
- type: 'basic',
437
- iconUrl: 'icons/icon48.png',
438
- title: 'Welcome to VibeSurf!',
439
- message: 'Click the VibeSurf icon in the toolbar to start automating your browsing tasks.'
440
- });
419
+ try {
420
+ await chrome.notifications.create({
421
+ type: 'basic',
422
+ iconUrl: '', // Use empty string to avoid icon issues
423
+ title: 'Welcome to VibeSurf!',
424
+ message: 'Click the VibeSurf icon in the toolbar to start automating your browsing tasks.'
425
+ });
426
+ } catch (error) {
427
+ console.warn('[VibeSurf] Welcome notification failed:', error);
428
+ // Don't throw, just log the warning
429
+ }
441
430
  }
442
431
 
443
432
  async checkBackendStatus(backendUrl = null) {
@@ -558,7 +547,40 @@ class VibeSurfBackground {
558
547
  return { success: false, error: 'No file URL provided' };
559
548
  }
560
549
 
550
+ // Add a unique request ID to track duplicate calls
551
+ const requestId = Date.now() + Math.random();
552
+ console.log(`[VibeSurf] openFileUrl called with ID: ${requestId}, URL: ${fileUrl}`);
553
+
561
554
  try {
555
+ // Validate URL format before attempting to open
556
+ try {
557
+ new URL(fileUrl);
558
+ } catch (urlError) {
559
+ console.warn('[VibeSurf] Invalid URL format:', fileUrl, urlError);
560
+ return { success: false, error: 'Invalid file URL format' };
561
+ }
562
+
563
+ // Check if this is an HTTP/HTTPS URL and handle it appropriately
564
+ if (fileUrl.startsWith('http://') || fileUrl.startsWith('https://')) {
565
+ console.log(`[VibeSurf] Detected HTTP(S) URL, creating tab for: ${fileUrl}`);
566
+
567
+ // Try to create a new tab with the URL
568
+ const tab = await chrome.tabs.create({
569
+ url: fileUrl,
570
+ active: true
571
+ });
572
+
573
+ if (tab && tab.id) {
574
+ console.log(`[VibeSurf] Successfully opened HTTP(S) URL in tab: ${tab.id} (request: ${requestId})`);
575
+ return { success: true, tabId: tab.id };
576
+ } else {
577
+ console.warn(`[VibeSurf] Tab creation returned but no tab ID for request: ${requestId}`);
578
+ return { success: false, error: 'Failed to create tab - no tab ID returned' };
579
+ }
580
+ }
581
+
582
+ // For file:// URLs, try the original approach
583
+ console.log(`[VibeSurf] Attempting to open file URL: ${fileUrl} (request: ${requestId})`);
562
584
 
563
585
  // Try to create a new tab with the file URL
564
586
  const tab = await chrome.tabs.create({
@@ -567,16 +589,25 @@ class VibeSurfBackground {
567
589
  });
568
590
 
569
591
  if (tab && tab.id) {
592
+ console.log(`[VibeSurf] Successfully opened file in tab: ${tab.id} (request: ${requestId})`);
570
593
  return { success: true, tabId: tab.id };
571
594
  } else {
572
- return { success: false, error: 'Failed to create tab' };
595
+ console.warn(`[VibeSurf] Tab creation returned but no tab ID for request: ${requestId}`);
596
+ return { success: false, error: 'Failed to create tab - no tab ID returned' };
573
597
  }
574
598
 
575
599
  } catch (error) {
576
- console.error('[VibeSurf] Error opening file URL:', error);
600
+ console.error(`[VibeSurf] Error opening file URL (request: ${requestId}):`, error);
601
+
602
+ // Provide more specific error messages
603
+ let errorMessage = error.message || 'Unknown error opening file';
604
+ if (error.message && error.message.includes('file://')) {
605
+ errorMessage = 'Browser security restricts opening local files. Try copying the file path and opening manually.';
606
+ }
607
+
577
608
  return {
578
609
  success: false,
579
- error: error.message || 'Unknown error opening file'
610
+ error: errorMessage
580
611
  };
581
612
  }
582
613
  }
@@ -16,8 +16,7 @@
16
16
  "contextMenus",
17
17
  "tabs",
18
18
  "clipboardWrite",
19
- "scripting",
20
- "microphone"
19
+ "scripting"
21
20
  ],
22
21
  "host_permissions": [
23
22
  "http://localhost:*/*",
@@ -49,15 +48,7 @@
49
48
 
50
49
  "web_accessible_resources": [
51
50
  {
52
- "resources": [
53
- "sidepanel.html",
54
- "permission-request.html",
55
- "permission-iframe.html",
56
- "styles/*",
57
- "scripts/*",
58
- "config.js",
59
- "icons/*"
60
- ],
51
+ "resources": ["sidepanel.html", "styles/*", "scripts/*", "config.js", "icons/*"],
61
52
  "matches": ["<all_urls>"]
62
53
  }
63
54
  ]
@@ -498,6 +498,11 @@ class VibeSurfAPIClient {
498
498
  return this.get('/browser/all-tabs');
499
499
  }
500
500
 
501
+ // Agent APIs - Get available skills
502
+ async getAllSkills() {
503
+ return this.get('/agent/get_all_skills');
504
+ }
505
+
501
506
  // Utility methods
502
507
  delay(ms) {
503
508
  return new Promise(resolve => setTimeout(resolve, ms));
@@ -275,14 +275,28 @@ class VibeSurfFileManager {
275
275
  async handleFileLink(filePath) {
276
276
  // Prevent multiple simultaneous calls
277
277
  if (this.state.isHandlingFileLink) {
278
+ console.log('[FileManager] File link handling already in progress, skipping...');
278
279
  return;
279
280
  }
280
281
 
281
282
  this.state.isHandlingFileLink = true;
282
283
 
283
284
  try {
284
- // First decode the URL-encoded path
285
- let decodedPath = decodeURIComponent(filePath);
285
+ console.log('[FileManager] Handling file link:', filePath);
286
+
287
+ // Validate input
288
+ if (!filePath || typeof filePath !== 'string') {
289
+ throw new Error('Invalid file path provided');
290
+ }
291
+
292
+ // First decode the URL-encoded path safely
293
+ let decodedPath;
294
+ try {
295
+ decodedPath = decodeURIComponent(filePath);
296
+ } catch (decodeError) {
297
+ console.warn('[FileManager] Failed to decode URL, using original path:', decodeError);
298
+ decodedPath = filePath;
299
+ }
286
300
 
287
301
  // Remove file:// protocol prefix and normalize
288
302
  let cleanPath = decodedPath.replace(/^file:\/\/\//, '').replace(/^file:\/\//, '');
@@ -300,21 +314,28 @@ class VibeSurfFileManager {
300
314
  `file:///${cleanPath}` :
301
315
  `file:///${cleanPath.replace(/^\//, '')}`; // Remove leading slash and add triple slash
302
316
 
317
+ console.log('[FileManager] Processed file URL:', fileUrl);
318
+
303
319
  // Show user notification about the action
304
320
  this.emit('notification', {
305
321
  message: `Opening file: ${cleanPath}`,
306
322
  type: 'info'
307
323
  });
308
324
 
309
- // Use setTimeout to prevent UI blocking
325
+ // Use setTimeout to prevent UI blocking and ensure proper cleanup
310
326
  setTimeout(async () => {
311
327
  try {
312
328
  // For user-clicked file links, use OPEN_FILE_URL to keep tab open
313
329
  // This prevents the auto-close behavior in OPEN_FILE_SYSTEM
314
- const fileOpenResponse = await chrome.runtime.sendMessage({
315
- type: 'OPEN_FILE_URL',
316
- data: { fileUrl: fileUrl }
317
- });
330
+ const fileOpenResponse = await Promise.race([
331
+ chrome.runtime.sendMessage({
332
+ type: 'OPEN_FILE_URL',
333
+ data: { fileUrl: fileUrl }
334
+ }),
335
+ new Promise((_, reject) =>
336
+ setTimeout(() => reject(new Error('File open timeout')), 5000)
337
+ )
338
+ ]);
318
339
 
319
340
  if (fileOpenResponse && fileOpenResponse.success) {
320
341
  this.emit('notification', {
@@ -322,12 +343,14 @@ class VibeSurfFileManager {
322
343
  type: 'success'
323
344
  });
324
345
  return;
346
+ } else if (fileOpenResponse && fileOpenResponse.error) {
347
+ console.warn('[FileManager] Background script file open failed:', fileOpenResponse.error);
325
348
  }
326
349
 
327
- // If OPEN_FILE_URL fails, try direct browser open
350
+ // If OPEN_FILE_URL fails, try direct browser open with additional safety
328
351
  try {
329
352
  const opened = window.open(fileUrl, '_blank', 'noopener,noreferrer');
330
- if (opened) {
353
+ if (opened && !opened.closed) {
331
354
  this.emit('notification', {
332
355
  message: 'File opened in browser',
333
356
  type: 'success'
@@ -339,14 +362,32 @@ class VibeSurfFileManager {
339
362
  }
340
363
 
341
364
  // Last resort: Copy path to clipboard
342
- this.copyToClipboardFallback(fileUrl);
365
+ await this.copyToClipboardFallback(fileUrl);
343
366
 
344
367
  } catch (error) {
345
368
  console.error('[FileManager] Error in async file handling:', error);
369
+
370
+ // Provide more helpful error messages
371
+ let userMessage = 'Unable to open file';
372
+ if (error.message.includes('timeout')) {
373
+ userMessage = 'File open operation timed out. Try copying the path manually.';
374
+ } else if (error.message.includes('protocol')) {
375
+ userMessage = 'Browser security restricts opening local files. File path copied to clipboard.';
376
+ } else {
377
+ userMessage = `Unable to open file: ${error.message}`;
378
+ }
379
+
346
380
  this.emit('notification', {
347
- message: `Unable to open file: ${error.message}`,
381
+ message: userMessage,
348
382
  type: 'error'
349
383
  });
384
+
385
+ // Fallback to clipboard
386
+ try {
387
+ await this.copyToClipboardFallback(fileUrl);
388
+ } catch (clipboardError) {
389
+ console.error('[FileManager] Clipboard fallback also failed:', clipboardError);
390
+ }
350
391
  } finally {
351
392
  this.state.isHandlingFileLink = false;
352
393
  }
@@ -355,7 +396,7 @@ class VibeSurfFileManager {
355
396
  } catch (error) {
356
397
  console.error('[FileManager] Error handling file link:', error);
357
398
  this.emit('notification', {
358
- message: `Unable to open file: ${error.message}`,
399
+ message: `File link processing failed: ${error.message}`,
359
400
  type: 'error'
360
401
  });
361
402
  this.state.isHandlingFileLink = false;
@@ -189,6 +189,12 @@ class VibeSurfApp {
189
189
  // Periodic backend health check
190
190
  setInterval(async () => {
191
191
  try {
192
+ // Check if apiClient exists and is initialized
193
+ if (!this.apiClient || typeof this.apiClient.healthCheck !== 'function') {
194
+ console.warn('[VibeSurf] Health check skipped - API client not available');
195
+ return;
196
+ }
197
+
192
198
  const healthCheck = await this.apiClient.healthCheck();
193
199
 
194
200
  if (healthCheck.status === 'healthy') {
@@ -334,7 +340,7 @@ class VibeSurfApp {
334
340
  <div class="input-container">
335
341
  <div class="input-main">
336
342
  <div class="textarea-container">
337
- <textarea id="task-input" class="task-input" placeholder="Describe your browsing task..." rows="3"></textarea>
343
+ <textarea id="task-input" class="task-input" placeholder="Ask anything (/ for skills, @ to specify tab)" rows="3"></textarea>
338
344
  <div class="input-actions">
339
345
  <button id="attach-file-btn" class="action-btn attach-btn" title="Attach Files">
340
346
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -413,18 +419,37 @@ class VibeSurfApp {
413
419
 
414
420
  // Cleanup method
415
421
  destroy() {
416
- console.log('[VibeSurf] Cleaning up application...');
417
-
418
- if (this.uiManager) {
419
- this.uiManager.destroy();
422
+ // Prevent multiple cleanup calls
423
+ if (this.isDestroying || !this.isInitialized) {
424
+ console.log('[VibeSurf] Cleanup already in progress or app not initialized, skipping...');
425
+ return;
420
426
  }
421
427
 
422
- if (this.sessionManager) {
423
- this.sessionManager.destroy();
424
- }
428
+ this.isDestroying = true;
429
+ console.log('[VibeSurf] Cleaning up application...');
425
430
 
426
- this.isInitialized = false;
427
- console.log('[VibeSurf] Application cleanup complete');
431
+ try {
432
+ if (this.uiManager) {
433
+ this.uiManager.destroy();
434
+ this.uiManager = null;
435
+ }
436
+
437
+ if (this.sessionManager) {
438
+ this.sessionManager.destroy();
439
+ this.sessionManager = null;
440
+ }
441
+
442
+ if (this.apiClient) {
443
+ this.apiClient = null;
444
+ }
445
+
446
+ this.isInitialized = false;
447
+ console.log('[VibeSurf] Application cleanup complete');
448
+ } catch (error) {
449
+ console.error('[VibeSurf] Error during cleanup:', error);
450
+ } finally {
451
+ this.isDestroying = false;
452
+ }
428
453
  }
429
454
 
430
455
  // Get application status
@@ -456,11 +481,21 @@ document.addEventListener('DOMContentLoaded', async () => {
456
481
 
457
482
  // Handle page unload
458
483
  window.addEventListener('beforeunload', () => {
459
- if (window.vibeSurfApp) {
484
+ if (window.vibeSurfApp && window.vibeSurfApp.isInitialized && !window.vibeSurfApp.isDestroying) {
485
+ console.log('[VibeSurf] Page unloading, cleaning up...');
460
486
  window.vibeSurfApp.destroy();
461
487
  }
462
488
  });
463
489
 
490
+ // Handle visibility change to prevent unnecessary cleanup
491
+ document.addEventListener('visibilitychange', () => {
492
+ if (document.visibilityState === 'hidden') {
493
+ console.log('[VibeSurf] Page hidden, but not cleaning up (might be tab switch)');
494
+ } else if (document.visibilityState === 'visible') {
495
+ console.log('[VibeSurf] Page visible again');
496
+ }
497
+ });
498
+
464
499
  // Make app accessible for debugging
465
500
  if (typeof window !== 'undefined') {
466
501
  window.VibeSurfApp = VibeSurfApp;