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.
- vibe_surf/__init__.py +12 -0
- vibe_surf/_version.py +34 -0
- vibe_surf/agents/__init__.py +0 -0
- vibe_surf/agents/browser_use_agent.py +1106 -0
- vibe_surf/agents/prompts/__init__.py +1 -0
- vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
- vibe_surf/agents/report_writer_agent.py +360 -0
- vibe_surf/agents/vibe_surf_agent.py +1632 -0
- vibe_surf/backend/__init__.py +0 -0
- vibe_surf/backend/api/__init__.py +3 -0
- vibe_surf/backend/api/activity.py +243 -0
- vibe_surf/backend/api/config.py +740 -0
- vibe_surf/backend/api/files.py +322 -0
- vibe_surf/backend/api/models.py +257 -0
- vibe_surf/backend/api/task.py +300 -0
- vibe_surf/backend/database/__init__.py +13 -0
- vibe_surf/backend/database/manager.py +129 -0
- vibe_surf/backend/database/models.py +164 -0
- vibe_surf/backend/database/queries.py +922 -0
- vibe_surf/backend/database/schemas.py +100 -0
- vibe_surf/backend/llm_config.py +182 -0
- vibe_surf/backend/main.py +137 -0
- vibe_surf/backend/migrations/__init__.py +16 -0
- vibe_surf/backend/migrations/init_db.py +303 -0
- vibe_surf/backend/migrations/seed_data.py +236 -0
- vibe_surf/backend/shared_state.py +601 -0
- vibe_surf/backend/utils/__init__.py +7 -0
- vibe_surf/backend/utils/encryption.py +164 -0
- vibe_surf/backend/utils/llm_factory.py +225 -0
- vibe_surf/browser/__init__.py +8 -0
- vibe_surf/browser/agen_browser_profile.py +130 -0
- vibe_surf/browser/agent_browser_session.py +416 -0
- vibe_surf/browser/browser_manager.py +296 -0
- vibe_surf/browser/utils.py +790 -0
- vibe_surf/browser/watchdogs/__init__.py +0 -0
- vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
- vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
- vibe_surf/chrome_extension/background.js +558 -0
- vibe_surf/chrome_extension/config.js +48 -0
- vibe_surf/chrome_extension/content.js +284 -0
- vibe_surf/chrome_extension/dev-reload.js +47 -0
- vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
- vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
- vibe_surf/chrome_extension/icons/logo.png +0 -0
- vibe_surf/chrome_extension/manifest.json +53 -0
- vibe_surf/chrome_extension/popup.html +134 -0
- vibe_surf/chrome_extension/scripts/api-client.js +473 -0
- vibe_surf/chrome_extension/scripts/main.js +491 -0
- vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
- vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
- vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
- vibe_surf/chrome_extension/sidepanel.html +347 -0
- vibe_surf/chrome_extension/styles/animations.css +471 -0
- vibe_surf/chrome_extension/styles/components.css +670 -0
- vibe_surf/chrome_extension/styles/main.css +2307 -0
- vibe_surf/chrome_extension/styles/settings.css +1100 -0
- vibe_surf/cli.py +357 -0
- vibe_surf/controller/__init__.py +0 -0
- vibe_surf/controller/file_system.py +53 -0
- vibe_surf/controller/mcp_client.py +68 -0
- vibe_surf/controller/vibesurf_controller.py +616 -0
- vibe_surf/controller/views.py +37 -0
- vibe_surf/llm/__init__.py +21 -0
- vibe_surf/llm/openai_compatible.py +237 -0
- vibesurf-0.1.0.dist-info/METADATA +97 -0
- vibesurf-0.1.0.dist-info/RECORD +70 -0
- vibesurf-0.1.0.dist-info/WHEEL +5 -0
- vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
- vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
- 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
|
+
}
|