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.
@@ -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.sessionId = null;
215
- this.agentName = options.agentName || `Agent-${Math.floor(Math.random() * 1000)}`;
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.isHost = false;
218
- this.clients = new Map();
219
- this.workHistory = [];
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 and get the correct URL
230
- const serverPort = new URL(this.serverUrl).port || 8080;
231
- this.serverUrl = await ensureServerRunning(serverPort);
252
+ // Ensure server is running
253
+ this.serverUrl = await ensureServerRunning(this.port);
232
254
 
233
- return new Promise((resolve, reject) => {
234
- this.ws = new WebSocket(this.serverUrl);
235
-
236
- this.ws.on('open', () => {
237
- this.connected = true;
238
- this.emit('connected');
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
- this.ws.on('error', (error) => {
257
- this.connected = false;
258
- reject(error);
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 start multiplayer server:', error);
263
- throw error;
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
- this.handleError(message);
332
+ console.error('Error:', message.message);
289
333
  break;
290
334
  default:
291
- this.emit('message', message);
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.isHost = message.clients.find(c => c.clientId === this.clientId)?.isHost || false;
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.set(message.clientId, {
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 client = this.clients.get(message.clientId);
323
- if (client) {
324
- console.log(chalk.yellow(`\n${client.name || 'Agent'} has left the session`));
325
- this.clients.delete(message.clientId);
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.get(message.clientId) || { name: 'Unknown' };
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.get(message.clientId) || { name: 'Unknown' };
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(message) {
351
- if (message.assignedTo === this.clientId) {
352
- console.log(chalk.magenta(`\n[Task Assigned] ${message.task.description}`));
353
- this.emit('task_assigned', message.task);
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
- handleError(message) {
358
- console.error(chalk.red(`\n[Error] ${message.message}`));
359
- this.emit('error', new Error(message.message));
360
- }
361
-
362
- createSession() {
363
- this.send({
364
- type: 'create_session',
365
- clientInfo: {
366
- name: this.agentName,
367
- model: this.model,
368
- isHost: true
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
- joinSession(sessionId) {
374
- this.sessionId = sessionId;
375
- this.send({
376
- type: 'join_session',
377
- sessionId,
378
- clientInfo: {
379
- name: this.agentName,
380
- model: this.model
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
- sendChatMessage(content) {
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: 'chat',
520
+ type: 'task_completed',
388
521
  sessionId: this.sessionId,
389
- content,
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
- updateWork(work) {
395
- const workUpdate = {
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
- description: work.description,
398
- status: work.status || 'in_progress',
399
- timestamp: new Date().toISOString()
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
- work: workUpdate
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) {
@@ -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
- this.client.connect();
89
+ console.clear();
90
+ this.printHeader();
90
91
 
91
- this.rl.question(chalk.blue('Enter session ID to join or press Enter to create a new session: '), (sessionId) => {
92
- if (sessionId && sessionId.trim()) {
93
- this.client.joinSession(sessionId.trim());
94
- } else {
95
- console.log(chalk.yellow('Creating a new session...'));
96
- this.client.createSession();
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
- prompt() {
102
- if (this.isWorking) return;
103
-
104
- this.rl.question(chalk.blue('> '), async (input) => {
105
- if (!input.trim()) {
106
- this.prompt();
107
- return;
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
- // Handle commands
111
- if (input.startsWith('/')) {
112
- await this.handleCommand(input);
113
- } else {
114
- // Regular chat message
115
- this.client.sendChatMessage(input);
116
- this.prompt();
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(command) {
122
- const [cmd, ...args] = command.slice(1).split(' ');
140
+ async handleCommand(input) {
141
+ const [command, ...args] = input.slice(1).split(' ');
123
142
 
124
- switch (cmd.toLowerCase()) {
125
- case 'help':
126
- this.showHelp();
127
- break;
128
-
129
- case 'name':
130
- if (args.length > 0) {
131
- const newName = args.join(' ');
132
- this.client.agentName = newName;
133
- console.log(chalk.green(`Name changed to: ${newName}`));
134
- // Notify others about the name change
135
- this.client.sendChatMessage(`I am now known as ${newName}`);
136
- } else {
137
- console.log(chalk.yellow('Current name:'), this.client.agentName);
138
- }
139
- break;
140
-
141
- case 'role':
142
- if (args.length > 0) {
143
- this.agentRole = args.join(' ');
144
- console.log(chalk.green(`Role set to: ${this.agentRole}`));
145
- this.client.sendChatMessage(`My role is: ${this.agentRole}`);
146
- } else {
147
- console.log(chalk.yellow('Current role:'), this.agentRole || 'Not set');
148
- }
149
- break;
150
-
151
- case 'work':
152
- if (args.length > 0) {
153
- const workDescription = args.join(' ');
154
- this.client.updateWork({
155
- description: workDescription,
156
- status: 'in_progress'
157
- });
158
- console.log(chalk.green('Work update sent'));
159
- } else {
160
- console.log(chalk.yellow('Please provide a work description'));
161
- }
162
- break;
163
-
164
- case 'task':
165
- if (args.length > 1) {
166
- const [targetName, ...taskParts] = args;
167
- const taskDescription = taskParts.join(' ');
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
- // Find client by name
170
- const targetClient = Array.from(this.client.clients.values())
171
- .find(c => c.name.toLowerCase() === targetName.toLowerCase());
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.red(`No agent found with name: ${targetName}`));
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
- } else {
183
- console.log(chalk.yellow('Usage: /task <agent_name> <task_description>'));
184
- }
185
- break;
186
-
187
- case 'list':
188
- this.listAgents();
189
- break;
190
-
191
- case 'exit':
192
- console.log(chalk.yellow('Disconnecting...'));
193
- this.client.disconnect();
194
- process.exit(0);
195
- break;
196
-
197
- default:
198
- console.log(chalk.red('Unknown command. Type /help for available commands.'));
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(chalk.cyan('\nMultiplayer Mode Commands:'));
206
- console.log(' /help - Show this help message');
207
- console.log(' /name [new_name] - Set or show your agent\'s name');
208
- console.log(' /role [role] - Set or show your agent\'s role');
209
- console.log(' /work [description] - Update your work status');
210
- console.log(' /task [name] [task] - Assign a task to another agent');
211
- console.log(' /list - List all agents in the session');
212
- console.log(' /exit - Leave the session and exit\n');
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(chalk.cyan('\nAgents in session:'));
217
- let index = 1;
218
- for (const [id, client] of this.client.clients) {
219
- const roleInfo = client.role ? ` (${client.role})` : '';
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
- console.log('');
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.server = http.createServer();
10
- this.wss = new WebSocket.Server({ server: this.server });
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.wss.on('connection', (ws) => {
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
- try {
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, clientId, data) {
48
- switch (data.type) {
49
- case 'create_session':
50
- this.handleCreateSession(ws, clientId, data);
51
- break;
52
- case 'join_session':
53
- this.handleJoinSession(ws, clientId, data);
54
- break;
55
- case 'update_work':
56
- case 'chat':
57
- case 'task_update':
58
- this.broadcastToSession(data.sessionId, {
59
- ...data,
60
- clientId,
61
- timestamp: new Date().toISOString()
62
- });
63
- break;
64
- default:
65
- console.log('Unknown message type:', data.type);
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, clientId, data) {
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
- this.handleJoinSession(ws, clientId, {
79
- ...data,
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, clientId, data) {
86
- const { sessionId, clientInfo } = data;
87
-
88
- if (!this.sessions.has(sessionId)) {
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
- // Add client to session
107
- session.clients[clientId] = {
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
- clientInfo: {
111
- ...clientInfo,
112
- joinedAt: new Date().toISOString(),
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
- clients: Object.values(session.clients).map(c => c.clientInfo),
123
- workHistory: session.workHistory
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: session.clients[clientId].clientInfo
131
- }, clientId);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {