sam-coder-cli 1.0.43 → 1.0.44

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.
@@ -1,905 +0,0 @@
1
- const WebSocket = require('ws');
2
- const http = require('http');
3
- const EventEmitter = require('events');
4
- const readline = require('readline');
5
- const chalk = require('chalk');
6
- const { v4: uuidv4 } = require('uuid');
7
- const { spawn, execSync } = require('child_process');
8
-
9
- async function isPortInUse(port) {
10
- return new Promise((resolve) => {
11
- const net = require('net');
12
- const server = net.createServer()
13
- .once('error', () => resolve(true))
14
- .once('listening', () => {
15
- server.close();
16
- resolve(false);
17
- })
18
- .listen(port);
19
- });
20
- }
21
-
22
- async function findAvailablePort(startPort = 8080, maxAttempts = 10) {
23
- let port = startPort;
24
- let attempts = 0;
25
-
26
- while (attempts < maxAttempts) {
27
- const inUse = await isPortInUse(port);
28
- if (!inUse) {
29
- return port;
30
- }
31
- port++;
32
- attempts++;
33
- }
34
- throw new Error(`Could not find an available port after ${maxAttempts} attempts`);
35
- }
36
-
37
- async function ensureServerRunning(port = 8080, serverId = null) {
38
- // First try to connect to an existing server by ID if provided
39
- if (serverId) {
40
- try {
41
- console.log(chalk.blue(`Attempting to connect to existing server ${serverId}...`));
42
- // Try to connect to the server
43
- const ws = new WebSocket(`ws://${serverId}`);
44
-
45
- // Wait for connection or timeout
46
- const connectionResult = await Promise.race([
47
- new Promise(resolve => ws.once('open', () => resolve(true))),
48
- new Promise(resolve => setTimeout(() => resolve(false), 2000))
49
- ]);
50
-
51
- if (connectionResult) {
52
- console.log(chalk.green(`✅ Connected to existing server at ${serverId}`));
53
- ws.terminate();
54
- return `ws://${serverId}`;
55
- }
56
- ws.terminate();
57
- } catch (error) {
58
- console.log(chalk.yellow(`Could not connect to server ${serverId}, starting a new server...`));
59
- }
60
- }
61
-
62
- // If no server ID provided or connection failed, start a new server
63
- try {
64
- const availablePort = await findAvailablePort(port);
65
- const isDefaultPort = (availablePort === port);
66
-
67
- if (!isDefaultPort) {
68
- console.log(chalk.yellow(`Port ${port} is in use, trying port ${availablePort}...`));
69
- }
70
-
71
- console.log('Starting local multiplayer server...');
72
- // Start server in a detached process
73
- const serverProcess = spawn('node', [__filename, '--server', '--port', availablePort.toString()], {
74
- detached: true,
75
- stdio: 'ignore',
76
- windowsHide: true
77
- });
78
-
79
- serverProcess.unref();
80
-
81
- // Wait for server to start
82
- await new Promise(resolve => setTimeout(resolve, 1000));
83
- console.log(chalk.green(`Local multiplayer server started on port ${availablePort}`));
84
- return `ws://localhost:${availablePort}`;
85
- } catch (error) {
86
- console.error(chalk.red('Failed to start multiplayer server:'), error.message);
87
- throw error;
88
- }
89
- }
90
-
91
- class MultiplayerServer {
92
- constructor(port = 8080, isChildProcess = false) {
93
- this.port = port;
94
- this.sessions = new Map();
95
- this.server = http.createServer();
96
- this.wss = new WebSocket.Server({ server: this.server });
97
- this.setupEventHandlers();
98
- }
99
-
100
- setupEventHandlers() {
101
- this.wss.on('connection', (ws) => {
102
- let clientId = uuidv4();
103
- let sessionId = null;
104
- let clientInfo = {};
105
-
106
- ws.on('message', (message) => {
107
- try {
108
- const data = JSON.parse(message);
109
- this.handleMessage(ws, clientId, data);
110
- } catch (error) {
111
- console.error('Error parsing message:', error);
112
- }
113
- });
114
-
115
- ws.on('close', () => {
116
- if (sessionId && this.sessions.has(sessionId)) {
117
- const session = this.sessions.get(sessionId);
118
- delete session.clients[clientId];
119
- this.broadcastToSession(sessionId, {
120
- type: 'agent_left',
121
- clientId,
122
- clientInfo
123
- });
124
-
125
- if (Object.keys(session.clients).length === 0) {
126
- this.sessions.delete(sessionId);
127
- }
128
- }
129
- });
130
- });
131
- }
132
-
133
- handleMessage(ws, clientId, data) {
134
- switch (data.type) {
135
- case 'create_session':
136
- this.handleCreateSession(ws, clientId, data);
137
- break;
138
- case 'join_session':
139
- this.handleJoinSession(ws, clientId, data);
140
- break;
141
- case 'update_work':
142
- case 'chat':
143
- case 'task_update':
144
- this.broadcastToSession(data.sessionId, {
145
- ...data,
146
- clientId,
147
- timestamp: new Date().toISOString()
148
- });
149
- break;
150
- default:
151
- console.log('Unknown message type:', data.type);
152
- }
153
- }
154
-
155
- handleCreateSession(ws, clientId, data) {
156
- const sessionId = uuidv4();
157
- this.sessions.set(sessionId, {
158
- id: sessionId,
159
- clients: {},
160
- workHistory: [],
161
- createdAt: new Date().toISOString()
162
- });
163
-
164
- this.handleJoinSession(ws, clientId, {
165
- ...data,
166
- sessionId,
167
- isHost: true
168
- });
169
- }
170
-
171
- handleJoinSession(ws, clientId, data) {
172
- const { sessionId, clientInfo } = data;
173
-
174
- if (!this.sessions.has(sessionId)) {
175
- ws.send(JSON.stringify({
176
- type: 'error',
177
- message: 'Session not found'
178
- }));
179
- return;
180
- }
181
-
182
- const session = this.sessions.get(sessionId);
183
-
184
- if (Object.keys(session.clients).length >= 4) {
185
- ws.send(JSON.stringify({
186
- type: 'error',
187
- message: 'Session is full (max 4 agents)'
188
- }));
189
- return;
190
- }
191
-
192
- // Add client to session
193
- session.clients[clientId] = {
194
- ws,
195
- clientId,
196
- clientInfo: {
197
- ...clientInfo,
198
- joinedAt: new Date().toISOString(),
199
- isHost: data.isHost || false
200
- }
201
- };
202
-
203
- // Send welcome message with session info
204
- ws.send(JSON.stringify({
205
- type: 'session_joined',
206
- sessionId,
207
- clientId,
208
- clients: Object.values(session.clients).map(c => c.clientInfo),
209
- workHistory: session.workHistory
210
- }));
211
-
212
- // Notify other clients
213
- this.broadcastToSession(sessionId, {
214
- type: 'agent_joined',
215
- clientId,
216
- clientInfo: session.clients[clientId].clientInfo
217
- }, clientId);
218
- }
219
-
220
- broadcastToSession(sessionId, message, excludeClientId = null) {
221
- if (!this.sessions.has(sessionId)) return;
222
-
223
- const session = this.sessions.get(sessionId);
224
- const messageStr = JSON.stringify(message);
225
-
226
- Object.entries(session.clients).forEach(([id, client]) => {
227
- if (id !== excludeClientId && client.ws.readyState === WebSocket.OPEN) {
228
- client.ws.send(messageStr);
229
- }
230
- });
231
- }
232
-
233
- start() {
234
- return new Promise((resolve) => {
235
- this.server.listen(this.port, () => {
236
- if (!this.isChildProcess) {
237
- console.log(`Multiplayer server running on ws://localhost:${this.port}`);
238
- }
239
- resolve();
240
- });
241
- });
242
- }
243
-
244
- static async startAsChildProcess(port = 8080) {
245
- const server = new MultiplayerServer(port, true);
246
- await server.start();
247
- // Keep process alive
248
- process.on('SIGINT', () => process.exit());
249
- }
250
- }
251
-
252
- // Start server if this file is run directly
253
- if (require.main === module && process.argv.includes('--server')) {
254
- const port = parseInt(process.argv[process.argv.indexOf('--port') + 1]) || 8080;
255
- MultiplayerServer.startAsChildProcess(port);
256
- }
257
-
258
- class MultiplayerClient extends EventEmitter {
259
- static TASK_TYPES = {
260
- RESEARCH: 'research',
261
- CODE: 'code',
262
- DEBUG: 'debug',
263
- DOCUMENT: 'document'
264
- };
265
-
266
- static AGENT_ROLES = {
267
- LEAD: 'Lead',
268
- DEVELOPER: 'Developer',
269
- RESEARCHER: 'Researcher',
270
- REVIEWER: 'Reviewer'
271
- };
272
- constructor(options = {}) {
273
- super();
274
- this.serverUrl = options.serverUrl || 'ws://localhost:8080';
275
- this.clientId = options.clientId || uuidv4();
276
- this.name = options.name || `Agent-${this.clientId.substr(0, 4)}`;
277
- this.role = options.role || MultiplayerClient.AGENT_ROLES.DEVELOPER;
278
- this.model = options.model || 'default';
279
- this.sessionId = options.sessionId || null;
280
- this.isHost = options.isHost || false;
281
- this.connected = false;
282
- this.ws = null;
283
- this.workHistory = [];
284
- this.clients = [];
285
- this.currentTask = null;
286
- this.taskQueue = [];
287
- this.pendingTasks = new Map();
288
- this.taskResults = new Map();
289
- this.rl = options.rl || readline.createInterface({
290
- input: process.stdin,
291
- output: process.stdout
292
- });
293
-
294
- // Bind methods
295
- this.processTaskQueue = this.processTaskQueue.bind(this);
296
- this.completeTask = this.completeTask.bind(this);
297
- }
298
-
299
- async connect(serverId = null) {
300
- return new Promise((resolve, reject) => {
301
- const connectWithRetry = async () => {
302
- try {
303
- // If no server URL is provided, try to start a local server or connect to the specified server ID
304
- if (this.serverUrl === 'ws://localhost:8080') {
305
- try {
306
- this.serverUrl = await ensureServerRunning(8080, serverId);
307
- } catch (error) {
308
- console.error(chalk.red('Failed to start local server:'), error.message);
309
- reject(error);
310
- return;
311
- }
312
- }
313
-
314
- console.log(chalk.blue(`Connecting to ${this.serverUrl}...`));
315
- this.ws = new WebSocket(this.serverUrl);
316
-
317
- const connectionTimeout = setTimeout(() => {
318
- this.ws.terminate();
319
- reject(new Error('Connection timeout'));
320
- }, 10000); // 10 seconds timeout
321
-
322
- this.ws.on('open', () => {
323
- clearTimeout(connectionTimeout);
324
- this.connected = true;
325
- console.log(chalk.green('✅ Connected to multiplayer server'));
326
-
327
- // Send client info
328
- this.send({
329
- type: 'client_info',
330
- clientId: this.clientId,
331
- name: this.name,
332
- role: this.role,
333
- model: this.model
334
- });
335
-
336
- this.emit('connected');
337
- resolve();
338
- });
339
-
340
- this.ws.on('message', (data) => {
341
- try {
342
- const message = JSON.parse(data);
343
- this.handleMessage(message);
344
- } catch (error) {
345
- console.error('Error parsing message:', error);
346
- }
347
- });
348
-
349
- this.ws.on('close', () => {
350
- this.connected = false;
351
- this.emit('disconnected');
352
- console.log(chalk.yellow('\n🔌 Disconnected from multiplayer server'));
353
- });
354
-
355
- this.ws.on('error', (error) => {
356
- clearTimeout(connectionTimeout);
357
- console.error('WebSocket error:', error.message);
358
- if (!this.connected) {
359
- reject(error);
360
- }
361
- this.emit('error', error);
362
- });
363
-
364
- } catch (error) {
365
- console.error('Connection error:', error.message);
366
- reject(error);
367
- }
368
- };
369
-
370
- connectWithRetry();
371
- });
372
- }
373
-
374
- handleMessage(message) {
375
- switch (message.type) {
376
- case 'session_joined':
377
- this.handleSessionJoined(message);
378
- break;
379
- case 'session_created':
380
- this.sessionId = message.sessionId;
381
- this.clientId = message.clientId;
382
- this.isHost = message.isHost;
383
- this.emit('session_created', message);
384
- break;
385
- case 'client_updated':
386
- this.handleClientUpdated(message);
387
- break;
388
- case 'agent_joined':
389
- this.handleAgentJoined(message);
390
- break;
391
- case 'agent_left':
392
- this.handleAgentLeft(message);
393
- break;
394
- case 'chat':
395
- this.handleChatMessage(message);
396
- break;
397
- case 'work_update':
398
- this.handleWorkUpdate(message);
399
- break;
400
- case 'task_assigned':
401
- this.handleTaskAssigned(message);
402
- break;
403
- case 'task_completed':
404
- this.handleTaskCompleted(message);
405
- break;
406
- case 'task_update':
407
- this.handleTaskUpdate(message);
408
- break;
409
- case 'error':
410
- console.error('Error:', message.message);
411
- break;
412
- default:
413
- console.log('Unknown message type:', message.type);
414
- }
415
- }
416
-
417
- handleSessionJoined(message) {
418
- this.sessionId = message.sessionId;
419
- this.clients = message.clients || [];
420
- this.workHistory = message.workHistory || [];
421
-
422
- // Update host status if this client is the host
423
- if (message.isHost !== undefined) {
424
- this.isHost = message.isHost;
425
- }
426
-
427
- // Process any pending tasks
428
- if (message.pendingTasks && message.pendingTasks.length > 0) {
429
- message.pendingTasks.forEach(task => {
430
- this.emit('task_assigned', task);
431
- });
432
- }
433
-
434
- // Emit session_joined with host status
435
- this.emit('session_joined', {
436
- ...message,
437
- isHost: this.isHost
438
- });
439
- }
440
-
441
- handleClientUpdated(data) {
442
- const clientIndex = this.clients.findIndex(c => c.id === data.clientId);
443
- if (clientIndex !== -1) {
444
- this.clients[clientIndex] = { ...this.clients[clientIndex], ...data.clientInfo };
445
- }
446
- this.emit('client_updated', data);
447
- }
448
-
449
- handleAgentJoined(message) {
450
- this.clients.push({
451
- ...message.clientInfo,
452
- clientId: message.clientId
453
- });
454
-
455
- if (message.clientId !== this.clientId) {
456
- console.log(chalk.blue(`\n${message.clientInfo.name || 'Agent'} has joined the session`));
457
- }
458
- this.emit('agent_joined', message);
459
- }
460
-
461
- handleAgentLeft(message) {
462
- const clientIndex = this.clients.findIndex(c => c.clientId === message.clientId);
463
- if (clientIndex !== -1) {
464
- console.log(chalk.yellow(`\n${this.clients[clientIndex].name || 'Agent'} has left the session`));
465
- this.clients.splice(clientIndex, 1);
466
- }
467
- this.emit('agent_left', message);
468
- }
469
-
470
- handleChatMessage(message) {
471
- if (message.clientId === this.clientId) return;
472
-
473
- const sender = this.clients.find(c => c.clientId === message.clientId) || { name: 'Unknown' };
474
- console.log(chalk.cyan(`\n[${sender.name}]: ${message.content}`));
475
- this.emit('chat', message);
476
- }
477
-
478
- handleWorkUpdate(message) {
479
- this.workHistory.push({
480
- ...message.work,
481
- timestamp: message.timestamp,
482
- clientId: message.clientId
483
- });
484
-
485
- const client = this.clients.find(c => c.clientId === message.clientId) || { name: 'Unknown' };
486
- console.log(chalk.green(`\n[Work Update] ${client.name}: ${message.work.description}`));
487
- this.emit('work_updated', message);
488
- }
489
-
490
- async handleTaskAssigned(task) {
491
- console.log(chalk.magenta(`\n📋 New Task: ${task.prompt}`));
492
- console.log(chalk.dim(`Type: ${task.taskType} | Assigned by: ${this.getClientName(task.assignedBy)}\n`));
493
-
494
- // Store task in pending tasks
495
- this.pendingTasks.set(task.id, {
496
- ...task,
497
- status: 'pending',
498
- assignedAt: new Date().toISOString()
499
- });
500
-
501
- // Add to task queue if it's assigned to this client or to all
502
- if (!task.assignedTo || task.assignedTo === this.clientId) {
503
- this.taskQueue.push(task);
504
- this.processTaskQueue();
505
- }
506
-
507
- this.emit('task_assigned', task);
508
- }
509
-
510
- async processTaskQueue() {
511
- if (this.currentTask || this.taskQueue.length === 0) return;
512
-
513
- this.currentTask = this.taskQueue.shift();
514
- const taskId = this.currentTask.id;
515
-
516
- try {
517
- // Update task status to in-progress
518
- this.pendingTasks.set(taskId, {
519
- ...this.pendingTasks.get(taskId),
520
- status: 'in_progress',
521
- startedAt: new Date().toISOString()
522
- });
523
-
524
- // Notify others about task progress
525
- this.send({
526
- type: 'task_progress',
527
- sessionId: this.sessionId,
528
- taskId,
529
- status: 'in_progress',
530
- progress: 0,
531
- timestamp: new Date().toISOString()
532
- });
533
-
534
- console.log(chalk.blue(`\n🔄 [${this.role}] Working on: ${this.currentTask.prompt}`));
535
-
536
- // Process the task using the completeTask method
537
- const completedTask = await this.completeTask(taskId);
538
-
539
- if (completedTask) {
540
- // Task completed successfully
541
- this.send({
542
- type: 'task_progress',
543
- sessionId: this.sessionId,
544
- taskId,
545
- status: 'completed',
546
- progress: 100,
547
- timestamp: new Date().toISOString()
548
- });
549
-
550
- console.log(chalk.green(`\n✅ [${this.role}] Completed task: ${this.currentTask.prompt}`));
551
- }
552
- if (this.taskQueue.length > 0) {
553
- this.processTaskQueue();
554
- }
555
- } catch (error) {
556
- console.error('Error processing task:', error);
557
- this.send({
558
- type: 'task_failed',
559
- sessionId: this.sessionId,
560
- taskId,
561
- error: error.message,
562
- timestamp: new Date().toISOString()
563
- });
564
- } finally {
565
- this.currentTask = null;
566
- }
567
- }
568
-
569
- async completeTask(taskId) {
570
- const task = this.pendingTasks.get(taskId);
571
- if (!task) {
572
- console.error('Task not found:', taskId);
573
- return null;
574
- }
575
-
576
- try {
577
- // Process the task using the same logic as the normal mode
578
- const { processQuery } = require('./agi-cli');
579
- const result = await processQuery(task.prompt, [], this.model);
580
-
581
- // Update task status
582
- const completedTask = {
583
- ...task,
584
- status: 'completed',
585
- completedAt: new Date().toISOString(),
586
- result: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
587
- };
588
-
589
- this.pendingTasks.set(taskId, completedTask);
590
- this.taskResults.set(taskId, completedTask.result);
591
-
592
- // Notify server and other clients
593
- this.send({
594
- type: 'task_completed',
595
- sessionId: this.sessionId,
596
- taskId,
597
- result: completedTask.result,
598
- timestamp: new Date().toISOString()
599
- });
600
-
601
- console.log(chalk.green(`\n✅ [${this.role}] Completed: ${task.prompt}`));
602
- this.emit('task_completed', completedTask);
603
-
604
- return completedTask;
605
- } catch (error) {
606
- console.error('Error completing task:', error);
607
-
608
- // Update task with error status
609
- const failedTask = {
610
- ...task,
611
- status: 'failed',
612
- completedAt: new Date().toISOString(),
613
- error: error.message
614
- };
615
-
616
- this.pendingTasks.set(taskId, failedTask);
617
- this.emit('task_failed', { taskId, error: error.message });
618
-
619
- return null;
620
- }
621
- }
622
-
623
- handleTaskCompleted(data) {
624
- const task = this.pendingTasks.get(data.taskId);
625
- if (!task) {
626
- console.error('Task not found for completion:', data.taskId);
627
- return;
628
- }
629
-
630
- console.log(chalk.green(`\n✅ Task completed by all agents!`));
631
- console.log(chalk.blue(`Task: ${task.prompt || 'Unknown task'}`));
632
-
633
- if (data.results && data.results.length > 0) {
634
- console.log(chalk.cyan('Results:'));
635
- data.results.forEach(result => {
636
- console.log(chalk.yellow(`- ${this.getClientName(result.clientId) || 'Unknown'}:`));
637
- console.log(result.result);
638
- });
639
- } else if (data.result) {
640
- console.log(chalk.cyan('Result:'));
641
- console.log(data.result);
642
- }
643
-
644
- // Update the task with the final result
645
- const completedTask = {
646
- ...task,
647
- status: 'completed',
648
- completedAt: new Date().toISOString(),
649
- result: data.result || 'No result provided'
650
- };
651
-
652
- this.pendingTasks.set(data.taskId, completedTask);
653
- this.taskResults.set(data.taskId, completedTask.result);
654
-
655
- this.emit('task_completed', completedTask);
656
- }
657
-
658
- async updateWork(work) {
659
- const workItem = {
660
- id: uuidv4(),
661
- ...work,
662
- clientId: this.clientId,
663
- clientName: this.name,
664
- timestamp: new Date().toISOString(),
665
- status: work.status || 'in_progress'
666
- };
667
-
668
- this.workHistory.push(workItem);
669
-
670
- // Keep only the last 10 work items
671
- if (this.workHistory.length > 10) {
672
- this.workHistory = this.workHistory.slice(-10);
673
- }
674
-
675
- this.send({
676
- type: 'work_update',
677
- sessionId: this.sessionId,
678
- ...workItem
679
- });
680
-
681
- this.emit('work_updated', workItem);
682
- return workItem;
683
- }
684
-
685
- async updateClientInfo(updates) {
686
- // Update local client info
687
- Object.assign(this, updates);
688
-
689
- // Notify server about the update
690
- if (this.connected) {
691
- this.send({
692
- type: 'client_update',
693
- sessionId: this.sessionId,
694
- clientId: this.clientId,
695
- updates: {
696
- name: this.name,
697
- role: this.role,
698
- model: this.model,
699
- updatedAt: new Date().toISOString()
700
- },
701
- timestamp: new Date().toISOString()
702
- });
703
- }
704
- }
705
-
706
- assignTask(task, assigneeClientId) {
707
- this.send({
708
- type: 'task_assigned',
709
- sessionId: this.sessionId,
710
- task: {
711
- ...task,
712
- assignedBy: this.clientId,
713
- assignedAt: new Date().toISOString()
714
- },
715
- assignedTo: assigneeClientId
716
- });
717
- }
718
-
719
- send(message) {
720
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
721
- this.ws.send(JSON.stringify({
722
- ...message,
723
- clientId: this.clientId,
724
- timestamp: new Date().toISOString()
725
- }));
726
- }
727
- }
728
-
729
- disconnect() {
730
- if (this.ws) {
731
- this.ws.close();
732
- this.connected = false;
733
- this.ws = null;
734
- }
735
- if (this.rl) {
736
- this.rl.close();
737
- }
738
- }
739
-
740
- sendChatMessage(message) {
741
- if (!this.connected || !this.ws) {
742
- console.error('Not connected to server');
743
- return false;
744
- }
745
-
746
- try {
747
- const chatMessage = {
748
- type: 'chat_message',
749
- sessionId: this.sessionId,
750
- clientId: this.clientId,
751
- sender: this.name,
752
- message: message,
753
- timestamp: new Date().toISOString()
754
- };
755
-
756
- this.ws.send(JSON.stringify(chatMessage));
757
- return true;
758
- } catch (error) {
759
- console.error('Error sending chat message:', error);
760
- return false;
761
- }
762
- }
763
-
764
- getClientName(clientId) {
765
- if (!clientId) return 'Unknown';
766
- const client = this.clients.find(c => c.clientId === clientId || c.id === clientId);
767
- return client ? (client.name || client.clientInfo?.name || 'Unknown') : 'Unknown';
768
- }
769
-
770
- async createSession(sessionId = null) {
771
- if (!this.connected) {
772
- await this.connect();
773
- }
774
-
775
- this.sessionId = sessionId || uuidv4();
776
- this.isHost = true;
777
-
778
- return new Promise((resolve, reject) => {
779
- if (!this.ws) {
780
- reject(new Error('Not connected to server'));
781
- return;
782
- }
783
-
784
- const onSessionCreated = (data) => {
785
- if (data.sessionId === this.sessionId) {
786
- this.off('session_created', onSessionCreated);
787
- this.off('error', onError);
788
- console.log(`✅ Session ${this.sessionId} created successfully`);
789
- resolve(this.sessionId);
790
- }
791
- };
792
-
793
- const onError = (error) => {
794
- this.off('session_created', onSessionCreated);
795
- this.off('error', onError);
796
- console.error('Error creating session:', error.message);
797
- reject(error);
798
- };
799
-
800
- this.once('session_created', onSessionCreated);
801
- this.once('error', onError);
802
-
803
- console.log(`Creating new session ${this.sessionId}...`);
804
- this.send({
805
- type: 'create_session',
806
- sessionId: this.sessionId,
807
- clientInfo: {
808
- name: this.name,
809
- role: this.role,
810
- model: this.model
811
- }
812
- });
813
- });
814
- }
815
-
816
- async joinSession(sessionId, createIfNotExists = true) {
817
- if (!sessionId) {
818
- throw new Error('Session ID is required');
819
- }
820
- if (!this.connected) {
821
- await this.connect();
822
- }
823
-
824
- return new Promise((resolve, reject) => {
825
- if (!this.ws) {
826
- reject(new Error('Not connected to server'));
827
- return;
828
- }
829
-
830
- const timeout = setTimeout(() => {
831
- this.off('session_joined', onSessionJoined);
832
- this.off('session_created', onSessionCreated);
833
- this.off('error', onError);
834
- reject(new Error('Session join timed out'));
835
- }, 15000); // Increased timeout to 15 seconds
836
-
837
- const onSessionJoined = (data) => {
838
- if (data.sessionId === sessionId) {
839
- clearTimeout(timeout);
840
- this.sessionId = sessionId;
841
- this.isHost = data.isHost || false;
842
- this.emit('session_joined', data);
843
- this.off('session_joined', onSessionJoined);
844
- this.off('session_created', onSessionCreated);
845
- this.off('error', onError);
846
- console.log(`✅ Successfully joined session ${sessionId}`);
847
- resolve(data);
848
- }
849
- };
850
-
851
- const onSessionCreated = (data) => {
852
- if (data.sessionId === sessionId) {
853
- clearTimeout(timeout);
854
- this.sessionId = sessionId;
855
- this.isHost = true;
856
- this.emit('session_joined', { ...data, isHost: true });
857
- this.off('session_joined', onSessionJoined);
858
- this.off('session_created', onSessionCreated);
859
- this.off('error', onError);
860
- console.log(`✅ Created and joined new session ${sessionId}`);
861
- resolve({ ...data, isHost: true });
862
- }
863
- };
864
-
865
- const onError = (error) => {
866
- if (error.message === 'Session not found' && createIfNotExists) {
867
- // If session doesn't exist and we're allowed to create it
868
- this.off('session_joined', onSessionJoined);
869
- this.off('error', onError);
870
- console.log(`Session ${sessionId} not found, creating new session...`);
871
- this.createSession(sessionId)
872
- .then(() => {
873
- this.once('session_created', onSessionCreated);
874
- this.once('error', onError);
875
- })
876
- .catch(reject);
877
- return;
878
- }
879
-
880
- clearTimeout(timeout);
881
- this.off('session_joined', onSessionJoined);
882
- this.off('session_created', onSessionCreated);
883
- this.off('error', onError);
884
- console.error('Error joining session:', error.message);
885
- reject(error);
886
- };
887
-
888
- this.on('session_joined', onSessionJoined);
889
- this.on('error', onError);
890
-
891
- console.log(`Attempting to join session ${sessionId}...`);
892
- this.send({
893
- type: 'join_session',
894
- sessionId: sessionId,
895
- clientId: this.clientId,
896
- name: this.name,
897
- role: this.role
898
- });
899
- });
900
- }
901
- }
902
-
903
- // Export MultiplayerClient and MultiplayerServer as direct properties of module.exports
904
- module.exports.MultiplayerClient = MultiplayerClient;
905
- module.exports.MultiplayerServer = MultiplayerServer;