sam-coder-cli 1.0.14 ā 1.0.16
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.
- package/bin/multiplayer-client.js +276 -92
- package/bin/multiplayer-mode.js +217 -114
- package/bin/multiplayer-server.js +155 -56
- package/package.json +1 -1
|
@@ -207,60 +207,89 @@ if (require.main === module && process.argv.includes('--server')) {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
class MultiplayerClient extends EventEmitter {
|
|
210
|
+
static TASK_TYPES = {
|
|
211
|
+
RESEARCH: 'research',
|
|
212
|
+
CODE: 'code',
|
|
213
|
+
DEBUG: 'debug',
|
|
214
|
+
DOCUMENT: 'document'
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
static AGENT_ROLES = {
|
|
218
|
+
LEAD: 'Lead',
|
|
219
|
+
DEVELOPER: 'Developer',
|
|
220
|
+
RESEARCHER: 'Researcher',
|
|
221
|
+
REVIEWER: 'Reviewer'
|
|
222
|
+
};
|
|
210
223
|
constructor(options = {}) {
|
|
211
224
|
super();
|
|
212
225
|
this.serverUrl = options.serverUrl || 'ws://localhost:8080';
|
|
213
|
-
this.clientId = uuidv4();
|
|
214
|
-
this.
|
|
215
|
-
this.
|
|
226
|
+
this.clientId = options.clientId || uuidv4();
|
|
227
|
+
this.name = options.name || `Agent-${this.clientId.substr(0, 4)}`;
|
|
228
|
+
this.role = options.role || MultiplayerClient.AGENT_ROLES.DEVELOPER;
|
|
216
229
|
this.model = options.model || 'default';
|
|
217
|
-
this.
|
|
218
|
-
this.
|
|
219
|
-
this.
|
|
230
|
+
this.sessionId = options.sessionId || null;
|
|
231
|
+
this.isHost = options.isHost || false;
|
|
232
|
+
this.connected = false;
|
|
220
233
|
this.ws = null;
|
|
234
|
+
this.workHistory = [];
|
|
235
|
+
this.clients = [];
|
|
236
|
+
this.currentTask = null;
|
|
237
|
+
this.taskQueue = [];
|
|
238
|
+
this.pendingTasks = new Map();
|
|
239
|
+
this.taskResults = new Map();
|
|
221
240
|
this.rl = options.rl || readline.createInterface({
|
|
222
241
|
input: process.stdin,
|
|
223
242
|
output: process.stdout
|
|
224
243
|
});
|
|
244
|
+
|
|
245
|
+
// Bind methods
|
|
246
|
+
this.processTaskQueue = this.processTaskQueue.bind(this);
|
|
247
|
+
this.completeTask = this.completeTask.bind(this);
|
|
225
248
|
}
|
|
226
249
|
|
|
227
250
|
async connect() {
|
|
228
251
|
try {
|
|
229
|
-
// Ensure server is running
|
|
230
|
-
|
|
231
|
-
this.serverUrl = await ensureServerRunning(serverPort);
|
|
252
|
+
// Ensure server is running
|
|
253
|
+
this.serverUrl = await ensureServerRunning(this.port);
|
|
232
254
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
resolve();
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
this.ws.on('message', (data) => {
|
|
243
|
-
try {
|
|
244
|
-
const message = JSON.parse(data);
|
|
245
|
-
this.handleMessage(message);
|
|
246
|
-
} catch (error) {
|
|
247
|
-
console.error('Error parsing message:', error);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
this.ws.on('close', () => {
|
|
252
|
-
this.connected = false;
|
|
253
|
-
this.emit('disconnected');
|
|
254
|
-
});
|
|
255
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
256
|
+
|
|
257
|
+
this.ws.on('open', () => {
|
|
258
|
+
this.connected = true;
|
|
259
|
+
this.emit('connected');
|
|
260
|
+
console.log(chalk.green('ā
Connected to multiplayer server'));
|
|
255
261
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
262
|
+
// Send client info immediately after connection
|
|
263
|
+
this.updateClientInfo({
|
|
264
|
+
name: this.name,
|
|
265
|
+
role: this.role,
|
|
266
|
+
model: this.model
|
|
259
267
|
});
|
|
260
268
|
});
|
|
269
|
+
|
|
270
|
+
this.ws.on('message', (data) => {
|
|
271
|
+
try {
|
|
272
|
+
const message = JSON.parse(data);
|
|
273
|
+
this.handleMessage(message);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error('Error parsing message:', error);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
this.ws.on('close', () => {
|
|
280
|
+
this.connected = false;
|
|
281
|
+
this.emit('disconnected');
|
|
282
|
+
console.log(chalk.yellow('\nš Disconnected from multiplayer server'));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
this.ws.on('error', (error) => {
|
|
286
|
+
console.error('WebSocket error:', error);
|
|
287
|
+
this.emit('error', error);
|
|
288
|
+
});
|
|
289
|
+
|
|
261
290
|
} catch (error) {
|
|
262
|
-
console.error('Failed to
|
|
263
|
-
|
|
291
|
+
console.error('Failed to connect to server:', error);
|
|
292
|
+
this.emit('error', error);
|
|
264
293
|
}
|
|
265
294
|
}
|
|
266
295
|
|
|
@@ -269,6 +298,15 @@ class MultiplayerClient extends EventEmitter {
|
|
|
269
298
|
case 'session_joined':
|
|
270
299
|
this.handleSessionJoined(message);
|
|
271
300
|
break;
|
|
301
|
+
case 'session_created':
|
|
302
|
+
this.sessionId = message.sessionId;
|
|
303
|
+
this.clientId = message.clientId;
|
|
304
|
+
this.isHost = message.isHost;
|
|
305
|
+
this.emit('session_created', message);
|
|
306
|
+
break;
|
|
307
|
+
case 'client_updated':
|
|
308
|
+
this.handleClientUpdated(message);
|
|
309
|
+
break;
|
|
272
310
|
case 'agent_joined':
|
|
273
311
|
this.handleAgentJoined(message);
|
|
274
312
|
break;
|
|
@@ -284,30 +322,45 @@ class MultiplayerClient extends EventEmitter {
|
|
|
284
322
|
case 'task_assigned':
|
|
285
323
|
this.handleTaskAssigned(message);
|
|
286
324
|
break;
|
|
325
|
+
case 'task_completed':
|
|
326
|
+
this.handleTaskCompleted(message);
|
|
327
|
+
break;
|
|
328
|
+
case 'task_update':
|
|
329
|
+
this.handleTaskUpdate(message);
|
|
330
|
+
break;
|
|
287
331
|
case 'error':
|
|
288
|
-
|
|
332
|
+
console.error('Error:', message.message);
|
|
289
333
|
break;
|
|
290
334
|
default:
|
|
291
|
-
|
|
335
|
+
console.log('Unknown message type:', message.type);
|
|
292
336
|
}
|
|
293
337
|
}
|
|
294
338
|
|
|
295
339
|
handleSessionJoined(message) {
|
|
296
340
|
this.sessionId = message.sessionId;
|
|
297
|
-
this.
|
|
298
|
-
|
|
299
|
-
// Update local client list
|
|
300
|
-
this.clients.clear();
|
|
301
|
-
message.clients.forEach(client => {
|
|
302
|
-
this.clients.set(client.clientId, client);
|
|
303
|
-
});
|
|
304
|
-
|
|
341
|
+
this.clients = message.clients || [];
|
|
305
342
|
this.workHistory = message.workHistory || [];
|
|
343
|
+
|
|
344
|
+
// Process any pending tasks
|
|
345
|
+
if (message.pendingTasks && message.pendingTasks.length > 0) {
|
|
346
|
+
message.pendingTasks.forEach(task => {
|
|
347
|
+
this.emit('task_assigned', task);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
306
351
|
this.emit('session_joined', message);
|
|
307
352
|
}
|
|
308
353
|
|
|
354
|
+
handleClientUpdated(data) {
|
|
355
|
+
const clientIndex = this.clients.findIndex(c => c.id === data.clientId);
|
|
356
|
+
if (clientIndex !== -1) {
|
|
357
|
+
this.clients[clientIndex] = { ...this.clients[clientIndex], ...data.clientInfo };
|
|
358
|
+
}
|
|
359
|
+
this.emit('client_updated', data);
|
|
360
|
+
}
|
|
361
|
+
|
|
309
362
|
handleAgentJoined(message) {
|
|
310
|
-
this.clients.
|
|
363
|
+
this.clients.push({
|
|
311
364
|
...message.clientInfo,
|
|
312
365
|
clientId: message.clientId
|
|
313
366
|
});
|
|
@@ -319,10 +372,10 @@ class MultiplayerClient extends EventEmitter {
|
|
|
319
372
|
}
|
|
320
373
|
|
|
321
374
|
handleAgentLeft(message) {
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
324
|
-
console.log(chalk.yellow(`\n${
|
|
325
|
-
this.clients.
|
|
375
|
+
const clientIndex = this.clients.findIndex(c => c.clientId === message.clientId);
|
|
376
|
+
if (clientIndex !== -1) {
|
|
377
|
+
console.log(chalk.yellow(`\n${this.clients[clientIndex].name || 'Agent'} has left the session`));
|
|
378
|
+
this.clients.splice(clientIndex, 1);
|
|
326
379
|
}
|
|
327
380
|
this.emit('agent_left', message);
|
|
328
381
|
}
|
|
@@ -330,7 +383,7 @@ class MultiplayerClient extends EventEmitter {
|
|
|
330
383
|
handleChatMessage(message) {
|
|
331
384
|
if (message.clientId === this.clientId) return;
|
|
332
385
|
|
|
333
|
-
const sender = this.clients.
|
|
386
|
+
const sender = this.clients.find(c => c.clientId === message.clientId) || { name: 'Unknown' };
|
|
334
387
|
console.log(chalk.cyan(`\n[${sender.name}]: ${message.content}`));
|
|
335
388
|
this.emit('chat', message);
|
|
336
389
|
}
|
|
@@ -342,68 +395,199 @@ class MultiplayerClient extends EventEmitter {
|
|
|
342
395
|
clientId: message.clientId
|
|
343
396
|
});
|
|
344
397
|
|
|
345
|
-
const client = this.clients.
|
|
398
|
+
const client = this.clients.find(c => c.clientId === message.clientId) || { name: 'Unknown' };
|
|
346
399
|
console.log(chalk.green(`\n[Work Update] ${client.name}: ${message.work.description}`));
|
|
347
400
|
this.emit('work_updated', message);
|
|
348
401
|
}
|
|
349
402
|
|
|
350
|
-
handleTaskAssigned(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
403
|
+
async handleTaskAssigned(task) {
|
|
404
|
+
console.log(chalk.magenta(`\nš New Task: ${task.prompt}`));
|
|
405
|
+
console.log(chalk.dim(`Type: ${task.taskType} | Assigned by: ${this.getClientName(task.assignedBy)}\n`));
|
|
406
|
+
|
|
407
|
+
// Store task in pending tasks
|
|
408
|
+
this.pendingTasks.set(task.id, {
|
|
409
|
+
...task,
|
|
410
|
+
status: 'pending',
|
|
411
|
+
assignedAt: new Date().toISOString()
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Add to task queue if it's assigned to this client or to all
|
|
415
|
+
if (!task.assignedTo || task.assignedTo === this.clientId) {
|
|
416
|
+
this.taskQueue.push(task);
|
|
417
|
+
this.processTaskQueue();
|
|
354
418
|
}
|
|
419
|
+
|
|
420
|
+
this.emit('task_assigned', task);
|
|
355
421
|
}
|
|
356
422
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
423
|
+
async processTaskQueue() {
|
|
424
|
+
if (this.currentTask || this.taskQueue.length === 0) return;
|
|
425
|
+
|
|
426
|
+
this.currentTask = this.taskQueue.shift();
|
|
427
|
+
const taskId = this.currentTask.id;
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
// Update task status to in-progress
|
|
431
|
+
this.pendingTasks.set(taskId, {
|
|
432
|
+
...this.pendingTasks.get(taskId),
|
|
433
|
+
status: 'in_progress',
|
|
434
|
+
startedAt: new Date().toISOString()
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Notify others about task progress
|
|
438
|
+
this.send({
|
|
439
|
+
type: 'task_progress',
|
|
440
|
+
sessionId: this.sessionId,
|
|
441
|
+
taskId,
|
|
442
|
+
status: 'in_progress',
|
|
443
|
+
progress: 0,
|
|
444
|
+
timestamp: new Date().toISOString()
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
console.log(chalk.blue(`\nš [${this.role}] Working on: ${this.currentTask.prompt}`));
|
|
448
|
+
|
|
449
|
+
// Simulate work with progress updates
|
|
450
|
+
const totalSteps = 5;
|
|
451
|
+
for (let i = 0; i < totalSteps; i++) {
|
|
452
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
453
|
+
const progress = Math.floor(((i + 1) / totalSteps) * 100);
|
|
454
|
+
|
|
455
|
+
// Update progress
|
|
456
|
+
this.send({
|
|
457
|
+
type: 'task_progress',
|
|
458
|
+
sessionId: this.sessionId,
|
|
459
|
+
taskId,
|
|
460
|
+
status: 'in_progress',
|
|
461
|
+
progress,
|
|
462
|
+
timestamp: new Date().toISOString()
|
|
463
|
+
});
|
|
369
464
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
465
|
+
|
|
466
|
+
// Complete the task
|
|
467
|
+
await this.completeTask(taskId);
|
|
468
|
+
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error('Error processing task:', error);
|
|
471
|
+
this.send({
|
|
472
|
+
type: 'task_failed',
|
|
473
|
+
sessionId: this.sessionId,
|
|
474
|
+
taskId,
|
|
475
|
+
error: error.message,
|
|
476
|
+
timestamp: new Date().toISOString()
|
|
477
|
+
});
|
|
478
|
+
} finally {
|
|
479
|
+
this.currentTask = null;
|
|
480
|
+
// Process next task if available
|
|
481
|
+
if (this.taskQueue.length > 0) {
|
|
482
|
+
this.processTaskQueue();
|
|
381
483
|
}
|
|
382
|
-
}
|
|
484
|
+
}
|
|
383
485
|
}
|
|
384
|
-
|
|
385
|
-
|
|
486
|
+
|
|
487
|
+
async completeTask(taskId) {
|
|
488
|
+
const task = this.pendingTasks.get(taskId);
|
|
489
|
+
if (!task) return null;
|
|
490
|
+
|
|
491
|
+
// Generate task result based on task type
|
|
492
|
+
let result;
|
|
493
|
+
switch (task.taskType) {
|
|
494
|
+
case 'research':
|
|
495
|
+
result = `Research completed on: ${task.prompt}\nSummary: This is a simulated research result.`;
|
|
496
|
+
break;
|
|
497
|
+
case 'code':
|
|
498
|
+
result = `Code implementation for: ${task.prompt}\n// Simulated code implementation\nfunction ${task.prompt.split(' ')[0].toLowerCase()}() {\n // TODO: Implement\n}`;
|
|
499
|
+
break;
|
|
500
|
+
case 'debug':
|
|
501
|
+
result = `Debugging analysis for: ${task.prompt}\n- Issue identified: Simulated issue\n- Solution: Simulated fix`;
|
|
502
|
+
break;
|
|
503
|
+
default:
|
|
504
|
+
result = `Completed: ${task.prompt}`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Update task status
|
|
508
|
+
const completedTask = {
|
|
509
|
+
...task,
|
|
510
|
+
status: 'completed',
|
|
511
|
+
completedAt: new Date().toISOString(),
|
|
512
|
+
result
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
this.pendingTasks.set(taskId, completedTask);
|
|
516
|
+
this.taskResults.set(taskId, result);
|
|
517
|
+
|
|
518
|
+
// Notify server and other clients
|
|
386
519
|
this.send({
|
|
387
|
-
type: '
|
|
520
|
+
type: 'task_completed',
|
|
388
521
|
sessionId: this.sessionId,
|
|
389
|
-
|
|
522
|
+
taskId,
|
|
523
|
+
result,
|
|
390
524
|
timestamp: new Date().toISOString()
|
|
391
525
|
});
|
|
526
|
+
|
|
527
|
+
console.log(chalk.green(`\nā
[${this.role}] Completed: ${task.prompt}`));
|
|
528
|
+
this.emit('task_completed', completedTask);
|
|
529
|
+
|
|
530
|
+
return completedTask;
|
|
392
531
|
}
|
|
393
532
|
|
|
394
|
-
|
|
395
|
-
|
|
533
|
+
handleTaskCompleted(data) {
|
|
534
|
+
console.log(`\n Task completed by all agents!`);
|
|
535
|
+
console.log(`Task: ${this.tasks.get(data.taskId)?.prompt || 'Unknown task'}`);
|
|
536
|
+
console.log('Results:');
|
|
537
|
+
|
|
538
|
+
data.results.forEach(result => {
|
|
539
|
+
console.log(`- ${this.getClientName(result.clientId)}: ${result.result}`);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
this.emit('task_completed', data);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async updateWork(work) {
|
|
546
|
+
const workItem = {
|
|
396
547
|
id: uuidv4(),
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
548
|
+
...work,
|
|
549
|
+
clientId: this.clientId,
|
|
550
|
+
clientName: this.name,
|
|
551
|
+
timestamp: new Date().toISOString(),
|
|
552
|
+
status: work.status || 'in_progress'
|
|
400
553
|
};
|
|
401
|
-
|
|
554
|
+
|
|
555
|
+
this.workHistory.push(workItem);
|
|
556
|
+
|
|
557
|
+
// Keep only the last 10 work items
|
|
558
|
+
if (this.workHistory.length > 10) {
|
|
559
|
+
this.workHistory = this.workHistory.slice(-10);
|
|
560
|
+
}
|
|
561
|
+
|
|
402
562
|
this.send({
|
|
403
563
|
type: 'work_update',
|
|
404
564
|
sessionId: this.sessionId,
|
|
405
|
-
|
|
565
|
+
...workItem
|
|
406
566
|
});
|
|
567
|
+
|
|
568
|
+
this.emit('work_updated', workItem);
|
|
569
|
+
return workItem;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async updateClientInfo(updates) {
|
|
573
|
+
// Update local client info
|
|
574
|
+
Object.assign(this, updates);
|
|
575
|
+
|
|
576
|
+
// Notify server about the update
|
|
577
|
+
if (this.connected) {
|
|
578
|
+
this.send({
|
|
579
|
+
type: 'client_update',
|
|
580
|
+
sessionId: this.sessionId,
|
|
581
|
+
clientId: this.clientId,
|
|
582
|
+
updates: {
|
|
583
|
+
name: this.name,
|
|
584
|
+
role: this.role,
|
|
585
|
+
model: this.model,
|
|
586
|
+
updatedAt: new Date().toISOString()
|
|
587
|
+
},
|
|
588
|
+
timestamp: new Date().toISOString()
|
|
589
|
+
});
|
|
590
|
+
}
|
|
407
591
|
}
|
|
408
592
|
|
|
409
593
|
assignTask(task, assigneeClientId) {
|
package/bin/multiplayer-mode.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const MultiplayerClient = require('./multiplayer-client');
|
|
2
|
-
const chalk = require('chalk');
|
|
3
2
|
const readline = require('readline');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
4
|
const { v4: uuidv4 } = require('uuid');
|
|
5
5
|
|
|
6
6
|
class MultiplayerMode {
|
|
@@ -86,142 +86,245 @@ class MultiplayerMode {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
async start() {
|
|
89
|
-
|
|
89
|
+
console.clear();
|
|
90
|
+
this.printHeader();
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
// Set agent name
|
|
93
|
+
this.client.name = await this.askQuestion('Enter your name (or press Enter for a random one): ') ||
|
|
94
|
+
`Agent-${Math.random().toString(36).substr(2, 4)}`;
|
|
95
|
+
|
|
96
|
+
// Set agent role
|
|
97
|
+
console.log('\nAvailable roles:');
|
|
98
|
+
Object.entries(MultiplayerClient.AGENT_ROLES).forEach(([key, value]) => {
|
|
99
|
+
console.log(`- ${key}: ${value}`);
|
|
98
100
|
});
|
|
101
|
+
this.client.role = await this.askQuestion('\nChoose your role (or press Enter for Developer): ') ||
|
|
102
|
+
MultiplayerClient.AGENT_ROLES.DEVELOPER;
|
|
103
|
+
|
|
104
|
+
// Get session ID from user
|
|
105
|
+
const sessionId = await this.askQuestion('\nEnter session ID to join or press Enter to create a new session: ');
|
|
106
|
+
|
|
107
|
+
if (sessionId) {
|
|
108
|
+
await this.joinSession(sessionId);
|
|
109
|
+
} else {
|
|
110
|
+
await this.createSession();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.setupCommandHandlers();
|
|
114
|
+
this.prompt();
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
117
|
+
async askQuestion(question) {
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
this.rl.question(question, (answer) => {
|
|
120
|
+
resolve(answer.trim());
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
109
124
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
async joinSession(sessionId) {
|
|
126
|
+
this.client.joinSession(sessionId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async createSession() {
|
|
130
|
+
console.log(chalk.yellow('Creating a new session...'));
|
|
131
|
+
this.client.createSession();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setupCommandHandlers() {
|
|
135
|
+
this.client.on('command', async (command) => {
|
|
136
|
+
await this.handleCommand(command);
|
|
118
137
|
});
|
|
119
138
|
}
|
|
120
139
|
|
|
121
|
-
async handleCommand(
|
|
122
|
-
const [
|
|
140
|
+
async handleCommand(input) {
|
|
141
|
+
const [command, ...args] = input.slice(1).split(' ');
|
|
123
142
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
143
|
+
try {
|
|
144
|
+
switch (command.toLowerCase()) {
|
|
145
|
+
case 'name':
|
|
146
|
+
if (args.length > 0) {
|
|
147
|
+
const newName = args.join(' ');
|
|
148
|
+
this.client.name = newName;
|
|
149
|
+
await this.client.updateClientInfo({ name: newName });
|
|
150
|
+
console.log(chalk.green(`ā
Name updated to: ${newName}`));
|
|
151
|
+
} else {
|
|
152
|
+
console.log(chalk.yellow(`Current name: ${this.client.name}`));
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
case 'role':
|
|
157
|
+
if (args.length > 0) {
|
|
158
|
+
const newRole = args.join(' ');
|
|
159
|
+
this.client.role = newRole;
|
|
160
|
+
await this.client.updateClientInfo({ role: newRole });
|
|
161
|
+
console.log(chalk.green(`ā
Role updated to: ${newRole}`));
|
|
162
|
+
} else {
|
|
163
|
+
console.log(chalk.yellow(`Current role: ${this.client.role}`));
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case 'model':
|
|
168
|
+
if (args.length > 0) {
|
|
169
|
+
this.client.model = args[0];
|
|
170
|
+
await this.client.updateClientInfo({ model: this.client.model });
|
|
171
|
+
console.log(chalk.green(`ā
Model updated to: ${this.client.model}`));
|
|
172
|
+
} else {
|
|
173
|
+
console.log(chalk.yellow(`Current model: ${this.client.model}`));
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case 'work':
|
|
178
|
+
if (args.length > 0) {
|
|
179
|
+
const workDesc = args.join(' ');
|
|
180
|
+
this.client.updateWork({
|
|
181
|
+
description: workDesc,
|
|
182
|
+
status: 'in_progress',
|
|
183
|
+
timestamp: new Date().toISOString()
|
|
184
|
+
});
|
|
185
|
+
console.log(chalk.green(`ā
Work updated: ${workDesc}`));
|
|
186
|
+
} else {
|
|
187
|
+
console.log(chalk.yellow('Please provide a work description. Example: /work Fixing bugs'));
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
168
190
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
191
|
+
case 'collab':
|
|
192
|
+
case 'collaborate':
|
|
193
|
+
if (args.length > 0) {
|
|
194
|
+
if (!this.client.isHost) {
|
|
195
|
+
console.log(chalk.yellow('Only the session host can initiate collaboration'));
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
const taskType = args[0].toLowerCase();
|
|
199
|
+
const prompt = args.slice(1).join(' ');
|
|
200
|
+
|
|
201
|
+
if (!Object.values(MultiplayerClient.TASK_TYPES).includes(taskType)) {
|
|
202
|
+
console.log(chalk.yellow(`Invalid task type. Available types: ${Object.values(MultiplayerClient.TASK_TYPES).join(', ')}`));
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!prompt) {
|
|
207
|
+
console.log(chalk.yellow('Please provide a task prompt. Example: /collab research How does quantum computing work?'));
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(chalk.blue(`\nš¤ Starting collaborative ${taskType} task: ${prompt}`));
|
|
212
|
+
await this.client.collaborate(prompt, taskType);
|
|
213
|
+
|
|
214
|
+
} else {
|
|
215
|
+
console.log(chalk.yellow('Usage: /collab <type> <prompt>'));
|
|
216
|
+
console.log(chalk.yellow('Types: ' + Object.values(MultiplayerClient.TASK_TYPES).join(', ')));
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'task':
|
|
221
|
+
if (args.length > 1) {
|
|
222
|
+
const [target, ...taskArgs] = args;
|
|
223
|
+
const taskDesc = taskArgs.join(' ');
|
|
224
|
+
|
|
225
|
+
// Find the target client
|
|
226
|
+
const targetClient = this.client.clients.find(c =>
|
|
227
|
+
c.name.toLowerCase() === target.toLowerCase() ||
|
|
228
|
+
c.clientId === target
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
if (!targetClient) {
|
|
232
|
+
console.log(chalk.red(`ā Could not find agent: ${target}`));
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// In a real implementation, you would send this to the server
|
|
237
|
+
// and the server would route it to the appropriate client
|
|
238
|
+
console.log(chalk.blue(`\nš Task assigned to ${targetClient.name}: ${taskDesc}`));
|
|
172
239
|
|
|
173
|
-
if (targetClient) {
|
|
174
|
-
this.client.assignTask({
|
|
175
|
-
description: taskDescription,
|
|
176
|
-
status: 'pending'
|
|
177
|
-
}, targetClient.clientId);
|
|
178
|
-
console.log(chalk.green(`Task assigned to ${targetClient.name}`));
|
|
179
240
|
} else {
|
|
180
|
-
console.log(chalk.
|
|
241
|
+
console.log(chalk.yellow('Usage: /task <agent> <task description>'));
|
|
242
|
+
console.log(chalk.yellow('Example: /task alice Please review the latest changes'));
|
|
181
243
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
244
|
+
break;
|
|
245
|
+
|
|
246
|
+
case 'list':
|
|
247
|
+
this.listAgents();
|
|
248
|
+
break;
|
|
249
|
+
|
|
250
|
+
case 'exit':
|
|
251
|
+
console.log(chalk.yellow('\nš Disconnecting...'));
|
|
252
|
+
process.exit(0);
|
|
253
|
+
break;
|
|
254
|
+
|
|
255
|
+
case 'help':
|
|
256
|
+
this.showHelp();
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
default:
|
|
260
|
+
console.log(chalk.red(`ā Unknown command: ${command}. Type /help for available commands.`));
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
199
264
|
}
|
|
200
265
|
|
|
201
266
|
this.prompt();
|
|
202
267
|
}
|
|
203
268
|
|
|
204
269
|
showHelp() {
|
|
205
|
-
console.log(
|
|
206
|
-
console.log('
|
|
207
|
-
console.log(' /name [new_name]
|
|
208
|
-
console.log(' /role [role]
|
|
209
|
-
console.log(' /
|
|
210
|
-
|
|
211
|
-
console.log('
|
|
212
|
-
console.log(' /
|
|
270
|
+
console.log('\n=== š Available Commands ===');
|
|
271
|
+
console.log('\nš¤ Agent Management:');
|
|
272
|
+
console.log(' /name [new_name] - Set or show your name');
|
|
273
|
+
console.log(' /role [role] - Set or show your role');
|
|
274
|
+
console.log(' /model [model_name] - Set or show your AI model');
|
|
275
|
+
|
|
276
|
+
console.log('\nš¤ Collaboration:');
|
|
277
|
+
console.log(' /work [description] - Update your work status');
|
|
278
|
+
console.log(' /collab <type> <prompt> - Start a collaborative task');
|
|
279
|
+
console.log(' Types: ' + Object.values(MultiplayerClient.TASK_TYPES).join(', '));
|
|
280
|
+
console.log(' /task [agent] [task] - Assign a task to another agent');
|
|
281
|
+
|
|
282
|
+
console.log('\nš Session:');
|
|
283
|
+
console.log(' /list - List all agents in the session');
|
|
284
|
+
console.log(' /exit - Leave the session');
|
|
285
|
+
console.log(' /help - Show this help message');
|
|
286
|
+
|
|
287
|
+
console.log('\nš” Example:');
|
|
288
|
+
console.log(' /collab research How does quantum computing work?');
|
|
289
|
+
console.log(' /work Implementing user authentication');
|
|
290
|
+
console.log(' /task bob Please review my latest changes');
|
|
213
291
|
}
|
|
214
292
|
|
|
215
293
|
listAgents() {
|
|
216
|
-
console.log(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const hostInfo = client.isHost ? ' [HOST]' : '';
|
|
221
|
-
console.log(` ${index}. ${chalk.green(client.name)}${roleInfo}${hostInfo}`);
|
|
222
|
-
index++;
|
|
294
|
+
console.log('\n=== š§āš» Agents in Session ===');
|
|
295
|
+
if (this.client.clients.length === 0) {
|
|
296
|
+
console.log('No other agents in the session');
|
|
297
|
+
return;
|
|
223
298
|
}
|
|
224
|
-
|
|
299
|
+
|
|
300
|
+
this.client.clients.forEach(client => {
|
|
301
|
+
const role = client.role ? ` (${client.role})` : '';
|
|
302
|
+
const status = client.isHost ? 'š ' : 'š¤ ';
|
|
303
|
+
console.log(`${status}${client.name}${role} [${client.model}]`);
|
|
304
|
+
if (client.work) {
|
|
305
|
+
console.log(` ${client.work.status === 'completed' ? 'ā
' : 'š'} ${client.work.description}`);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
prompt() {
|
|
311
|
+
if (this.isWorking) return;
|
|
312
|
+
|
|
313
|
+
this.rl.question(chalk.blue('> '), async (input) => {
|
|
314
|
+
if (!input.trim()) {
|
|
315
|
+
this.prompt();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Handle commands
|
|
320
|
+
if (input.startsWith('/')) {
|
|
321
|
+
await this.handleCommand(input);
|
|
322
|
+
} else {
|
|
323
|
+
// Regular chat message
|
|
324
|
+
this.client.sendChatMessage(input);
|
|
325
|
+
this.prompt();
|
|
326
|
+
}
|
|
327
|
+
});
|
|
225
328
|
}
|
|
226
329
|
}
|
|
227
330
|
|
|
@@ -6,24 +6,20 @@ class MultiplayerServer {
|
|
|
6
6
|
constructor(port = 8080) {
|
|
7
7
|
this.port = port;
|
|
8
8
|
this.sessions = new Map();
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
9
|
+
this.clients = new Map();
|
|
10
|
+
this.tasks = new Map();
|
|
11
|
+
this.server = new WebSocket.Server({ port });
|
|
11
12
|
this.setupEventHandlers();
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
setupEventHandlers() {
|
|
15
|
-
this.
|
|
16
|
+
this.server.on('connection', (ws) => {
|
|
16
17
|
let clientId = uuid.v4();
|
|
17
18
|
let sessionId = null;
|
|
18
19
|
let clientInfo = {};
|
|
19
20
|
|
|
20
21
|
ws.on('message', (message) => {
|
|
21
|
-
|
|
22
|
-
const data = JSON.parse(message);
|
|
23
|
-
this.handleMessage(ws, clientId, data);
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.error('Error parsing message:', error);
|
|
26
|
-
}
|
|
22
|
+
this.handleMessage(ws, message);
|
|
27
23
|
});
|
|
28
24
|
|
|
29
25
|
ws.on('close', () => {
|
|
@@ -35,7 +31,7 @@ class MultiplayerServer {
|
|
|
35
31
|
clientId,
|
|
36
32
|
clientInfo
|
|
37
33
|
});
|
|
38
|
-
|
|
34
|
+
|
|
39
35
|
if (Object.keys(session.clients).length === 0) {
|
|
40
36
|
this.sessions.delete(sessionId);
|
|
41
37
|
}
|
|
@@ -44,48 +40,129 @@ class MultiplayerServer {
|
|
|
44
40
|
});
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
handleMessage(ws,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
43
|
+
handleMessage(ws, message) {
|
|
44
|
+
try {
|
|
45
|
+
const data = typeof message === 'string' ? JSON.parse(message) : message;
|
|
46
|
+
const client = this.clients.get(ws);
|
|
47
|
+
|
|
48
|
+
switch (data.type) {
|
|
49
|
+
case 'client_info':
|
|
50
|
+
if (client) {
|
|
51
|
+
Object.assign(client, data.clientInfo);
|
|
52
|
+
this.broadcastToSession(client.sessionId, {
|
|
53
|
+
type: 'client_updated',
|
|
54
|
+
clientId: client.id,
|
|
55
|
+
clientInfo: data.clientInfo
|
|
56
|
+
}, ws);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case 'create_session':
|
|
61
|
+
this.handleCreateSession(ws, data);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'join_session':
|
|
65
|
+
this.handleJoinSession(ws, data);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'collaborate':
|
|
69
|
+
if (client && client.sessionId) {
|
|
70
|
+
const taskId = uuid.v4();
|
|
71
|
+
this.tasks.set(taskId, {
|
|
72
|
+
id: taskId,
|
|
73
|
+
sessionId: client.sessionId,
|
|
74
|
+
prompt: data.prompt,
|
|
75
|
+
status: 'in_progress',
|
|
76
|
+
createdBy: client.id,
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
results: []
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.broadcastToSession(client.sessionId, {
|
|
82
|
+
type: 'task_assigned',
|
|
83
|
+
taskId,
|
|
84
|
+
prompt: data.prompt,
|
|
85
|
+
assignedBy: client.id
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'task_result':
|
|
91
|
+
if (client && client.sessionId && data.taskId) {
|
|
92
|
+
const task = this.tasks.get(data.taskId);
|
|
93
|
+
if (task) {
|
|
94
|
+
task.results.push({
|
|
95
|
+
clientId: client.id,
|
|
96
|
+
result: data.result,
|
|
97
|
+
timestamp: new Date().toISOString()
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Check if all clients have responded
|
|
101
|
+
const session = this.sessions.get(client.sessionId);
|
|
102
|
+
if (session && task.results.length >= Object.keys(session.clients).length) {
|
|
103
|
+
task.status = 'completed';
|
|
104
|
+
this.broadcastToSession(client.sessionId, {
|
|
105
|
+
type: 'task_completed',
|
|
106
|
+
taskId: task.id,
|
|
107
|
+
results: task.results
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'chat':
|
|
115
|
+
case 'work_update':
|
|
116
|
+
case 'task_update':
|
|
117
|
+
this.broadcastToSession(data.sessionId, data);
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
default:
|
|
121
|
+
console.log('Unknown message type:', data.type);
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('Error handling message:', error);
|
|
66
125
|
}
|
|
67
126
|
}
|
|
68
127
|
|
|
69
|
-
handleCreateSession(ws,
|
|
128
|
+
handleCreateSession(ws, data) {
|
|
70
129
|
const sessionId = uuid.v4();
|
|
130
|
+
const clientId = uuid.v4();
|
|
131
|
+
|
|
132
|
+
// Store client info
|
|
133
|
+
this.clients.set(ws, {
|
|
134
|
+
id: clientId,
|
|
135
|
+
sessionId,
|
|
136
|
+
ws,
|
|
137
|
+
isHost: true,
|
|
138
|
+
name: data.name || `Agent-${Math.random().toString(36).substr(2, 4)}`,
|
|
139
|
+
role: data.role || 'Assistant',
|
|
140
|
+
model: data.model || 'default'
|
|
141
|
+
});
|
|
142
|
+
|
|
71
143
|
this.sessions.set(sessionId, {
|
|
72
144
|
id: sessionId,
|
|
73
|
-
clients: {},
|
|
145
|
+
clients: { [clientId]: true },
|
|
74
146
|
workHistory: [],
|
|
75
|
-
createdAt: new Date().toISOString()
|
|
147
|
+
createdAt: new Date().toISOString(),
|
|
148
|
+
hostId: clientId
|
|
76
149
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
150
|
+
|
|
151
|
+
// Send session created confirmation
|
|
152
|
+
ws.send(JSON.stringify({
|
|
153
|
+
type: 'session_created',
|
|
80
154
|
sessionId,
|
|
155
|
+
clientId,
|
|
81
156
|
isHost: true
|
|
82
|
-
});
|
|
157
|
+
}));
|
|
83
158
|
}
|
|
84
159
|
|
|
85
|
-
handleJoinSession(ws,
|
|
86
|
-
const { sessionId
|
|
87
|
-
|
|
88
|
-
|
|
160
|
+
handleJoinSession(ws, data) {
|
|
161
|
+
const { sessionId } = data;
|
|
162
|
+
const clientId = data.clientId || uuid.v4();
|
|
163
|
+
const session = this.sessions.get(sessionId);
|
|
164
|
+
|
|
165
|
+
if (!session) {
|
|
89
166
|
ws.send(JSON.stringify({
|
|
90
167
|
type: 'error',
|
|
91
168
|
message: 'Session not found'
|
|
@@ -93,8 +170,6 @@ class MultiplayerServer {
|
|
|
93
170
|
return;
|
|
94
171
|
}
|
|
95
172
|
|
|
96
|
-
const session = this.sessions.get(sessionId);
|
|
97
|
-
|
|
98
173
|
if (Object.keys(session.clients).length >= 4) {
|
|
99
174
|
ws.send(JSON.stringify({
|
|
100
175
|
type: 'error',
|
|
@@ -103,40 +178,64 @@ class MultiplayerServer {
|
|
|
103
178
|
return;
|
|
104
179
|
}
|
|
105
180
|
|
|
106
|
-
//
|
|
107
|
-
|
|
181
|
+
// Store/update client info
|
|
182
|
+
const existingClient = Array.from(this.clients.entries())
|
|
183
|
+
.find(([_, c]) => c.id === clientId)?.[1];
|
|
184
|
+
|
|
185
|
+
const clientInfo = {
|
|
186
|
+
id: clientId,
|
|
187
|
+
sessionId,
|
|
108
188
|
ws,
|
|
109
|
-
clientId,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
isHost: data.isHost || false
|
|
114
|
-
}
|
|
189
|
+
isHost: session.hostId === clientId,
|
|
190
|
+
name: data.name || existingClient?.name || `Agent-${clientId.substr(0, 4)}`,
|
|
191
|
+
role: data.role || existingClient?.role || 'Assistant',
|
|
192
|
+
model: data.model || existingClient?.model || 'default'
|
|
115
193
|
};
|
|
116
194
|
|
|
195
|
+
this.clients.set(ws, clientInfo);
|
|
196
|
+
session.clients[clientId] = true;
|
|
197
|
+
|
|
198
|
+
// Get all client infos for the session
|
|
199
|
+
const sessionClients = Array.from(this.clients.values())
|
|
200
|
+
.filter(c => c.sessionId === sessionId)
|
|
201
|
+
.map(({ id, name, role, isHost }) => ({
|
|
202
|
+
id,
|
|
203
|
+
name,
|
|
204
|
+
role,
|
|
205
|
+
isHost
|
|
206
|
+
}));
|
|
207
|
+
|
|
117
208
|
// Send welcome message with session info
|
|
118
209
|
ws.send(JSON.stringify({
|
|
119
210
|
type: 'session_joined',
|
|
120
211
|
sessionId,
|
|
121
212
|
clientId,
|
|
122
|
-
|
|
123
|
-
|
|
213
|
+
isHost: clientInfo.isHost,
|
|
214
|
+
clients: sessionClients,
|
|
215
|
+
workHistory: session.workHistory,
|
|
216
|
+
pendingTasks: Array.from(this.tasks.values())
|
|
217
|
+
.filter(t => t.sessionId === sessionId && t.status === 'in_progress')
|
|
124
218
|
}));
|
|
125
219
|
|
|
126
220
|
// Notify other clients
|
|
127
221
|
this.broadcastToSession(sessionId, {
|
|
128
222
|
type: 'agent_joined',
|
|
129
223
|
clientId,
|
|
130
|
-
clientInfo:
|
|
131
|
-
|
|
224
|
+
clientInfo: {
|
|
225
|
+
id: clientId,
|
|
226
|
+
name: clientInfo.name,
|
|
227
|
+
role: clientInfo.role,
|
|
228
|
+
isHost: clientInfo.isHost
|
|
229
|
+
}
|
|
230
|
+
}, ws);
|
|
132
231
|
}
|
|
133
232
|
|
|
134
233
|
broadcastToSession(sessionId, message, excludeClientId = null) {
|
|
135
234
|
if (!this.sessions.has(sessionId)) return;
|
|
136
|
-
|
|
235
|
+
|
|
137
236
|
const session = this.sessions.get(sessionId);
|
|
138
237
|
const messageStr = JSON.stringify(message);
|
|
139
|
-
|
|
238
|
+
|
|
140
239
|
Object.entries(session.clients).forEach(([id, client]) => {
|
|
141
240
|
if (id !== excludeClientId && client.ws.readyState === WebSocket.OPEN) {
|
|
142
241
|
client.ws.send(messageStr);
|