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,599 @@
1
+ // Session Manager - Handles session lifecycle and activity monitoring
2
+ // Manages session creation, activity polling, and history
3
+
4
+ class VibeSurfSessionManager {
5
+ constructor(apiClient) {
6
+ this.apiClient = apiClient;
7
+ this.currentSession = null;
8
+ this.activityLogs = [];
9
+ this.pollingInterval = null;
10
+ this.pollingFrequency = 1000; // 1 second
11
+ this.isPolling = false;
12
+ this.eventListeners = new Map();
13
+
14
+ this.bindMethods();
15
+ }
16
+
17
+ bindMethods() {
18
+ this.handleActivityUpdate = this.handleActivityUpdate.bind(this);
19
+ this.pollActivity = this.pollActivity.bind(this);
20
+ }
21
+
22
+ // Event system for UI updates
23
+ on(event, callback) {
24
+ if (!this.eventListeners.has(event)) {
25
+ this.eventListeners.set(event, []);
26
+ }
27
+ this.eventListeners.get(event).push(callback);
28
+ }
29
+
30
+ off(event, callback) {
31
+ if (this.eventListeners.has(event)) {
32
+ const callbacks = this.eventListeners.get(event);
33
+ const index = callbacks.indexOf(callback);
34
+ if (index > -1) {
35
+ callbacks.splice(index, 1);
36
+ }
37
+ }
38
+ }
39
+
40
+ emit(event, data) {
41
+ if (this.eventListeners.has(event)) {
42
+ this.eventListeners.get(event).forEach(callback => {
43
+ try {
44
+ callback(data);
45
+ } catch (error) {
46
+ console.error(`[SessionManager] Event callback error for ${event}:`, error);
47
+ }
48
+ });
49
+ }
50
+ }
51
+
52
+ // Session management
53
+ async createSession(prefix = 'vibesurf_') {
54
+ const sessionId = await this.apiClient.generateSessionId(prefix);
55
+
56
+ this.currentSession = {
57
+ id: sessionId,
58
+ createdAt: new Date().toISOString(),
59
+ status: 'active',
60
+ taskHistory: [],
61
+ currentTask: null
62
+ };
63
+
64
+ // Clear previous activity logs
65
+ this.activityLogs = [];
66
+
67
+
68
+ // Store session in background
69
+ await this.storeSessionData();
70
+
71
+ this.emit('sessionCreated', { sessionId, session: this.currentSession });
72
+
73
+ return sessionId;
74
+ }
75
+
76
+ async loadSession(sessionId) {
77
+ try {
78
+ // Stop current polling
79
+ this.stopActivityPolling();
80
+
81
+
82
+ // Load session data from background
83
+ const response = await chrome.runtime.sendMessage({
84
+ type: 'GET_SESSION_DATA',
85
+ data: { sessionId }
86
+ });
87
+
88
+
89
+ // Check if response is valid and has sessionData
90
+ if (response && response.sessionData) {
91
+ this.currentSession = {
92
+ id: sessionId,
93
+ ...response.sessionData
94
+ };
95
+
96
+ // Load activity logs from backend
97
+ await this.loadSessionActivity();
98
+
99
+ this.emit('sessionLoaded', { sessionId, session: this.currentSession });
100
+
101
+ return true;
102
+ } else if (response && response.error) {
103
+ console.error('[SessionManager] Background error:', response.error);
104
+ this.emit('sessionError', { error: response.error, sessionId });
105
+ return false;
106
+ } else {
107
+ // Session not found in storage - create a new session with this ID
108
+
109
+ this.currentSession = {
110
+ id: sessionId,
111
+ createdAt: new Date().toISOString(),
112
+ status: 'active',
113
+ taskHistory: [],
114
+ currentTask: null
115
+ };
116
+
117
+ // Clear activity logs for new session
118
+ this.activityLogs = [];
119
+
120
+ // Try to load any existing activity logs from backend
121
+ await this.loadSessionActivity();
122
+
123
+ // Store the new session
124
+ await this.storeSessionData();
125
+
126
+ this.emit('sessionLoaded', { sessionId, session: this.currentSession });
127
+
128
+ return true;
129
+ }
130
+ } catch (error) {
131
+ console.error('[SessionManager] Failed to load session:', error);
132
+ this.emit('sessionError', { error: error.message, sessionId });
133
+ return false;
134
+ }
135
+ }
136
+
137
+ async loadSessionActivity() {
138
+ if (!this.currentSession) {
139
+ console.warn('[SessionManager] No current session for activity loading');
140
+ return;
141
+ }
142
+
143
+ try {
144
+ const response = await this.apiClient.getSessionActivity(this.currentSession.id);
145
+
146
+
147
+ // Check multiple possible response formats
148
+ let activityLogs = null;
149
+ if (response && response.data && response.data.activity_logs) {
150
+ activityLogs = response.data.activity_logs;
151
+ } else if (response && response.activity_logs) {
152
+ activityLogs = response.activity_logs;
153
+ } else if (response && Array.isArray(response)) {
154
+ activityLogs = response;
155
+ }
156
+
157
+ if (activityLogs && Array.isArray(activityLogs)) {
158
+ this.activityLogs = activityLogs;
159
+
160
+
161
+ // Add timestamps to logs that don't have them
162
+ this.activityLogs.forEach(log => {
163
+ if (!log.timestamp) {
164
+ log.timestamp = new Date().toISOString();
165
+ }
166
+ });
167
+
168
+ this.emit('activityLogsLoaded', {
169
+ sessionId: this.currentSession.id,
170
+ logs: this.activityLogs
171
+ });
172
+ } else {
173
+ // No existing activity logs
174
+ this.activityLogs = [];
175
+ this.lastMessageIndex = 0;
176
+ }
177
+ } catch (error) {
178
+ console.error('[SessionManager] ❌ Failed to load session activity:', error);
179
+ console.error('[SessionManager] Error details:', {
180
+ message: error.message,
181
+ stack: error.stack,
182
+ sessionId: this.currentSession?.id
183
+ });
184
+ // Reset to safe defaults
185
+ this.activityLogs = [];
186
+ }
187
+ }
188
+
189
+ getCurrentSession() {
190
+ return this.currentSession;
191
+ }
192
+
193
+ getCurrentSessionId() {
194
+ return this.currentSession?.id || null;
195
+ }
196
+
197
+ // Task management
198
+ async submitTask(taskData) {
199
+ if (!this.currentSession) {
200
+ throw new Error('No active session. Please create a session first.');
201
+ }
202
+
203
+ const taskPayload = {
204
+ session_id: this.currentSession.id,
205
+ ...taskData
206
+ };
207
+
208
+ try {
209
+ const response = await this.apiClient.submitTask(taskPayload);
210
+
211
+ // Update current session with task info
212
+ this.currentSession.currentTask = {
213
+ taskId: response.task_id,
214
+ description: taskData.task_description,
215
+ llmProfile: taskData.llm_profile_name,
216
+ status: 'submitted',
217
+ submittedAt: new Date().toISOString()
218
+ };
219
+
220
+ // Start activity polling
221
+ this.startActivityPolling();
222
+
223
+ // Store updated session
224
+ await this.storeSessionData();
225
+
226
+ this.emit('taskSubmitted', {
227
+ sessionId: this.currentSession.id,
228
+ task: this.currentSession.currentTask,
229
+ response
230
+ });
231
+
232
+ return response;
233
+ } catch (error) {
234
+ console.error('[SessionManager] Task submission failed:', error);
235
+ this.emit('taskError', { error: error.message, sessionId: this.currentSession.id });
236
+ throw error;
237
+ }
238
+ }
239
+
240
+ async pauseTask(reason = 'User requested pause') {
241
+ try {
242
+ const response = await this.apiClient.pauseTask(reason);
243
+
244
+ if (this.currentSession?.currentTask) {
245
+ this.currentSession.currentTask.status = 'paused';
246
+ this.currentSession.currentTask.pausedAt = new Date().toISOString();
247
+ await this.storeSessionData();
248
+ }
249
+
250
+ // Stop polling when task is paused
251
+ this.stopActivityPolling();
252
+
253
+ this.emit('taskPaused', { sessionId: this.currentSession?.id, response });
254
+
255
+ return response;
256
+ } catch (error) {
257
+ console.error('[SessionManager] Task pause failed:', error);
258
+ this.emit('taskError', { error: error.message, action: 'pause' });
259
+ throw error;
260
+ }
261
+ }
262
+
263
+ async resumeTask(reason = 'User requested resume') {
264
+ try {
265
+ const response = await this.apiClient.resumeTask(reason);
266
+
267
+ if (this.currentSession?.currentTask) {
268
+ this.currentSession.currentTask.status = 'running';
269
+ this.currentSession.currentTask.resumedAt = new Date().toISOString();
270
+ await this.storeSessionData();
271
+ }
272
+
273
+ // Restart polling when task is resumed
274
+ this.startActivityPolling();
275
+
276
+ this.emit('taskResumed', { sessionId: this.currentSession?.id, response });
277
+
278
+ return response;
279
+ } catch (error) {
280
+ console.error('[SessionManager] Task resume failed:', error);
281
+ this.emit('taskError', { error: error.message, action: 'resume' });
282
+ throw error;
283
+ }
284
+ }
285
+
286
+ async stopTask(reason = 'User requested stop') {
287
+ try {
288
+ const response = await this.apiClient.stopTask(reason);
289
+
290
+ if (this.currentSession?.currentTask) {
291
+ this.currentSession.currentTask.status = 'stopped';
292
+ this.currentSession.currentTask.stoppedAt = new Date().toISOString();
293
+ await this.storeSessionData();
294
+ }
295
+
296
+ // Stop polling when task is stopped
297
+ this.stopActivityPolling();
298
+
299
+ this.emit('taskStopped', { sessionId: this.currentSession?.id, response });
300
+
301
+ return response;
302
+ } catch (error) {
303
+ console.error('[SessionManager] Task stop failed:', error);
304
+ this.emit('taskError', { error: error.message, action: 'stop' });
305
+ throw error;
306
+ }
307
+ }
308
+
309
+ // Activity polling
310
+ startActivityPolling() {
311
+ if (this.isPolling) {
312
+ return;
313
+ }
314
+
315
+
316
+ this.isPolling = true;
317
+ // Use arrow function to preserve 'this' context
318
+ this.pollingInterval = setInterval(() => {
319
+ this.pollActivity();
320
+ }, this.pollingFrequency);
321
+
322
+ this.emit('pollingStarted', { sessionId: this.currentSession?.id });
323
+ }
324
+
325
+ stopActivityPolling() {
326
+ if (this.pollingInterval) {
327
+ clearInterval(this.pollingInterval);
328
+ this.pollingInterval = null;
329
+ }
330
+
331
+ this.isPolling = false;
332
+ this.emit('pollingStopped', { sessionId: this.currentSession?.id });
333
+ }
334
+
335
+ async pollActivity() {
336
+ if (!this.currentSession) {
337
+ this.stopActivityPolling();
338
+ return;
339
+ }
340
+
341
+ try {
342
+ // ✅ 使用当前logs数量作为message_index,确保获取下一个预期的log
343
+ const requestIndex = this.activityLogs.length;
344
+
345
+ console.log(`[SessionManager] 🔄 Polling activity at index ${requestIndex}, current logs: ${this.activityLogs.length}`);
346
+
347
+ // Poll for new activity at the next expected index
348
+ const response = await this.apiClient.pollSessionActivity(
349
+ this.currentSession.id,
350
+ requestIndex
351
+ );
352
+
353
+ // Check both possible response formats
354
+ const activityLog = response?.activity_log || response?.data?.activity_log;
355
+ const totalAvailable = response?.total_available || response?.data?.total_available;
356
+
357
+ // ✅ 关键逻辑:只有当获取到新log且与上一个不同时才处理
358
+ if (response && activityLog) {
359
+ const prevActivityLog = this.activityLogs.length > 0 ? this.activityLogs[this.activityLogs.length - 1] : null;
360
+
361
+ // 检查是否为新的、不重复的activity log
362
+ const isNewLog = !prevActivityLog || !this.areLogsEqual(prevActivityLog, activityLog);
363
+
364
+ if (isNewLog) {
365
+ // New activity log received
366
+ const newLog = { ...activityLog };
367
+
368
+ // Add timestamp if not present
369
+ if (!newLog.timestamp) {
370
+ newLog.timestamp = new Date().toISOString();
371
+ }
372
+
373
+ this.activityLogs.push(newLog);
374
+
375
+ console.log(`[SessionManager] ✅ New unique activity received: ${newLog.agent_name} - ${newLog.agent_status}`);
376
+
377
+ await this.handleActivityUpdate(newLog);
378
+
379
+ this.emit('newActivity', {
380
+ sessionId: this.currentSession.id,
381
+ activity: newLog,
382
+ allLogs: this.activityLogs
383
+ });
384
+
385
+ // Check if task is completed or terminated
386
+ const terminalStatuses = ['done', 'completed', 'finished', 'error', 'failed',
387
+ 'terminated', 'stopped', 'cancelled', 'aborted'];
388
+
389
+ if (terminalStatuses.includes(newLog.agent_status?.toLowerCase())) {
390
+ this.stopActivityPolling();
391
+
392
+ if (this.currentSession.currentTask) {
393
+ this.currentSession.currentTask.status = newLog.agent_status;
394
+ this.currentSession.currentTask.completedAt = new Date().toISOString();
395
+ await this.storeSessionData();
396
+ }
397
+
398
+ this.emit('taskCompleted', {
399
+ sessionId: this.currentSession.id,
400
+ status: newLog.agent_status,
401
+ finalActivity: newLog
402
+ });
403
+ }
404
+ } else {
405
+ console.log(`[SessionManager] 🔄 Duplicate log detected, skipping`);
406
+ }
407
+ } else {
408
+ // No new activity at this index
409
+ console.log(`[SessionManager] 🔄 No new activity at index ${requestIndex}, waiting...`);
410
+
411
+ // Check if we're behind based on server total
412
+ if (totalAvailable && totalAvailable > this.activityLogs.length) {
413
+ console.log(`[SessionManager] 🔄 Syncing logs: have ${this.activityLogs.length}, server has ${totalAvailable}`);
414
+ await this.syncActivityLogs();
415
+ }
416
+ }
417
+ } catch (error) {
418
+ // Enhanced error logging for debugging
419
+ console.error(`[SessionManager] ❌ Activity polling error at index ${this.activityLogs.length}:`, {
420
+ error: error.message,
421
+ sessionId: this.currentSession?.id,
422
+ currentLogsLength: this.activityLogs.length,
423
+ stack: error.stack
424
+ });
425
+
426
+ // Only emit polling errors for non-timeout/not-found errors
427
+ if (!error.message.includes('timeout') && !error.message.includes('No activity log found')) {
428
+ this.emit('pollingError', { error: error.message, sessionId: this.currentSession?.id });
429
+ }
430
+ }
431
+ }
432
+
433
+ // ✅ 新增:比较两个activity log是否相等的辅助方法
434
+ areLogsEqual(log1, log2) {
435
+ if (!log1 || !log2) return false;
436
+
437
+ return log1.agent_name === log2.agent_name &&
438
+ log1.agent_status === log2.agent_status &&
439
+ log1.agent_msg === log2.agent_msg;
440
+ }
441
+
442
+ async syncActivityLogs() {
443
+ if (!this.currentSession) return;
444
+
445
+ try {
446
+
447
+ // Get all activity logs from server
448
+ const response = await this.apiClient.getSessionActivity(this.currentSession.id);
449
+
450
+ // Check both possible response formats
451
+ const activityLogs = response?.activity_logs || response?.data?.activity_logs;
452
+
453
+ if (activityLogs) {
454
+ const serverLogs = activityLogs;
455
+ const missingLogs = serverLogs.slice(this.activityLogs.length);
456
+
457
+ if (missingLogs.length > 0) {
458
+
459
+ for (const log of missingLogs) {
460
+ // Add timestamp if not present
461
+ if (!log.timestamp) {
462
+ log.timestamp = new Date().toISOString();
463
+ }
464
+
465
+ this.activityLogs.push(log);
466
+
467
+ this.emit('newActivity', {
468
+ sessionId: this.currentSession.id,
469
+ activity: log,
470
+ allLogs: this.activityLogs
471
+ });
472
+ }
473
+ }
474
+ }
475
+ } catch (error) {
476
+ console.error(`[SessionManager] ❌ Failed to sync activity logs:`, error);
477
+ }
478
+ }
479
+
480
+ async handleActivityUpdate(activityLog) {
481
+ // Update current task status based on activity
482
+ if (this.currentSession?.currentTask && activityLog.agent_status) {
483
+ this.currentSession.currentTask.status = activityLog.agent_status;
484
+ await this.storeSessionData();
485
+ }
486
+
487
+ // Store activity in background for persistence
488
+ await chrome.runtime.sendMessage({
489
+ type: 'STORE_SESSION_DATA',
490
+ data: {
491
+ sessionId: this.currentSession.id,
492
+ lastActivity: activityLog,
493
+ activityCount: this.activityLogs.length
494
+ }
495
+ });
496
+ }
497
+
498
+ // History management
499
+ async getSessionHistory() {
500
+ try {
501
+ const response = await chrome.runtime.sendMessage({
502
+ type: 'GET_SESSION_DATA'
503
+ });
504
+
505
+ return response.sessions || [];
506
+ } catch (error) {
507
+ console.error('[SessionManager] Failed to get session history:', error);
508
+ return [];
509
+ }
510
+ }
511
+
512
+ async getSessionTasks(sessionId) {
513
+ try {
514
+ const response = await this.apiClient.getSessionTasks(sessionId);
515
+ return response.data?.tasks || [];
516
+ } catch (error) {
517
+ console.error('[SessionManager] Failed to get session tasks:', error);
518
+ return [];
519
+ }
520
+ }
521
+
522
+ // Storage helpers
523
+ async storeSessionData() {
524
+ if (!this.currentSession) return;
525
+
526
+ try {
527
+ await chrome.runtime.sendMessage({
528
+ type: 'STORE_SESSION_DATA',
529
+ data: {
530
+ sessionId: this.currentSession.id,
531
+ ...this.currentSession,
532
+ activityLogs: this.activityLogs,
533
+ lastUpdated: new Date().toISOString()
534
+ }
535
+ });
536
+ } catch (error) {
537
+ console.error('[SessionManager] Failed to store session data:', error);
538
+ }
539
+ }
540
+
541
+ // File management for session
542
+ async uploadFiles(files) {
543
+ if (!this.currentSession) {
544
+ throw new Error('No active session for file upload');
545
+ }
546
+
547
+ try {
548
+ const response = await this.apiClient.uploadFiles(files, this.currentSession.id);
549
+
550
+ this.emit('filesUploaded', {
551
+ sessionId: this.currentSession.id,
552
+ files: response.files
553
+ });
554
+
555
+ return response;
556
+ } catch (error) {
557
+ console.error('[SessionManager] File upload failed:', error);
558
+ this.emit('fileUploadError', { error: error.message, sessionId: this.currentSession.id });
559
+ throw error;
560
+ }
561
+ }
562
+
563
+ // Cleanup
564
+ destroy() {
565
+ this.stopActivityPolling();
566
+ this.eventListeners.clear();
567
+ this.currentSession = null;
568
+ this.activityLogs = [];
569
+
570
+ }
571
+
572
+ // Status helpers
573
+ isSessionActive() {
574
+ return this.currentSession && this.currentSession.status === 'active';
575
+ }
576
+
577
+ hasActiveTask() {
578
+ return this.currentSession?.currentTask &&
579
+ ['submitted', 'running', 'paused'].includes(this.currentSession.currentTask.status);
580
+ }
581
+
582
+ getTaskStatus() {
583
+ return this.currentSession?.currentTask?.status || null;
584
+ }
585
+
586
+ getActivityLogs() {
587
+ return [...this.activityLogs]; // Return copy
588
+ }
589
+
590
+ getLatestActivity() {
591
+ return this.activityLogs[this.activityLogs.length - 1] || null;
592
+ }
593
+
594
+ }
595
+
596
+ // Export for use in other modules
597
+ if (typeof window !== 'undefined') {
598
+ window.VibeSurfSessionManager = VibeSurfSessionManager;
599
+ }