sam-coder-cli 1.0.14 → 1.0.15

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,17 +207,34 @@ 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 = [];
221
238
  this.rl = options.rl || readline.createInterface({
222
239
  input: process.stdin,
223
240
  output: process.stdout
@@ -227,7 +244,8 @@ class MultiplayerClient extends EventEmitter {
227
244
  async connect() {
228
245
  try {
229
246
  // Ensure server is running and get the correct URL
230
- const serverPort = new URL(this.serverUrl).port || 8080;
247
+ const url = new URL(this.serverUrl);
248
+ const serverPort = url.port || 8080;
231
249
  this.serverUrl = await ensureServerRunning(serverPort);
232
250
 
233
251
  return new Promise((resolve, reject) => {
@@ -235,6 +253,15 @@ class MultiplayerClient extends EventEmitter {
235
253
 
236
254
  this.ws.on('open', () => {
237
255
  this.connected = true;
256
+ // Send client info immediately after connection
257
+ this.send({
258
+ type: 'client_info',
259
+ clientInfo: {
260
+ name: this.name || `Agent-${Math.random().toString(36).substr(2, 4)}`,
261
+ role: this.role || 'Assistant',
262
+ model: this.model || 'default'
263
+ }
264
+ });
238
265
  this.emit('connected');
239
266
  resolve();
240
267
  });
@@ -269,6 +296,15 @@ class MultiplayerClient extends EventEmitter {
269
296
  case 'session_joined':
270
297
  this.handleSessionJoined(message);
271
298
  break;
299
+ case 'session_created':
300
+ this.sessionId = message.sessionId;
301
+ this.clientId = message.clientId;
302
+ this.isHost = message.isHost;
303
+ this.emit('session_created', message);
304
+ break;
305
+ case 'client_updated':
306
+ this.handleClientUpdated(message);
307
+ break;
272
308
  case 'agent_joined':
273
309
  this.handleAgentJoined(message);
274
310
  break;
@@ -284,30 +320,45 @@ class MultiplayerClient extends EventEmitter {
284
320
  case 'task_assigned':
285
321
  this.handleTaskAssigned(message);
286
322
  break;
323
+ case 'task_completed':
324
+ this.handleTaskCompleted(message);
325
+ break;
326
+ case 'task_update':
327
+ this.handleTaskUpdate(message);
328
+ break;
287
329
  case 'error':
288
- this.handleError(message);
330
+ console.error('Error:', message.message);
289
331
  break;
290
332
  default:
291
- this.emit('message', message);
333
+ console.log('Unknown message type:', message.type);
292
334
  }
293
335
  }
294
336
 
295
337
  handleSessionJoined(message) {
296
338
  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
-
339
+ this.clients = message.clients || [];
305
340
  this.workHistory = message.workHistory || [];
341
+
342
+ // Process any pending tasks
343
+ if (message.pendingTasks && message.pendingTasks.length > 0) {
344
+ message.pendingTasks.forEach(task => {
345
+ this.emit('task_assigned', task);
346
+ });
347
+ }
348
+
306
349
  this.emit('session_joined', message);
307
350
  }
308
351
 
352
+ handleClientUpdated(data) {
353
+ const clientIndex = this.clients.findIndex(c => c.id === data.clientId);
354
+ if (clientIndex !== -1) {
355
+ this.clients[clientIndex] = { ...this.clients[clientIndex], ...data.clientInfo };
356
+ }
357
+ this.emit('client_updated', data);
358
+ }
359
+
309
360
  handleAgentJoined(message) {
310
- this.clients.set(message.clientId, {
361
+ this.clients.push({
311
362
  ...message.clientInfo,
312
363
  clientId: message.clientId
313
364
  });
@@ -319,10 +370,10 @@ class MultiplayerClient extends EventEmitter {
319
370
  }
320
371
 
321
372
  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);
373
+ const clientIndex = this.clients.findIndex(c => c.clientId === message.clientId);
374
+ if (clientIndex !== -1) {
375
+ console.log(chalk.yellow(`\n${this.clients[clientIndex].name || 'Agent'} has left the session`));
376
+ this.clients.splice(clientIndex, 1);
326
377
  }
327
378
  this.emit('agent_left', message);
328
379
  }
@@ -330,7 +381,7 @@ class MultiplayerClient extends EventEmitter {
330
381
  handleChatMessage(message) {
331
382
  if (message.clientId === this.clientId) return;
332
383
 
333
- const sender = this.clients.get(message.clientId) || { name: 'Unknown' };
384
+ const sender = this.clients.find(c => c.clientId === message.clientId) || { name: 'Unknown' };
334
385
  console.log(chalk.cyan(`\n[${sender.name}]: ${message.content}`));
335
386
  this.emit('chat', message);
336
387
  }
@@ -342,59 +393,79 @@ class MultiplayerClient extends EventEmitter {
342
393
  clientId: message.clientId
343
394
  });
344
395
 
345
- const client = this.clients.get(message.clientId) || { name: 'Unknown' };
396
+ const client = this.clients.find(c => c.clientId === message.clientId) || { name: 'Unknown' };
346
397
  console.log(chalk.green(`\n[Work Update] ${client.name}: ${message.work.description}`));
347
398
  this.emit('work_updated', message);
348
399
  }
349
400
 
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);
354
- }
355
- }
356
-
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
369
- }
370
- });
401
+ handleTaskAssigned(task) {
402
+ console.log(`\n New task assigned: ${task.prompt}`);
403
+ console.log(`Type: ${task.taskType} | Assigned by: ${this.getClientName(task.assignedBy)}\n`);
404
+
405
+ // Add to task queue
406
+ this.taskQueue.push(task);
407
+ this.processTaskQueue();
408
+
409
+ this.emit('task_assigned', task);
371
410
  }
372
411
 
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
412
+ async processTaskQueue() {
413
+ if (this.currentTask || this.taskQueue.length === 0) return;
414
+
415
+ this.currentTask = this.taskQueue.shift();
416
+
417
+ try {
418
+ // Simulate working on the task
419
+ console.log(`\n Working on task: ${this.currentTask.prompt}`);
420
+
421
+ // Here you would integrate with your AI to process the task
422
+ // For now, we'll just simulate work
423
+ await new Promise(resolve => setTimeout(resolve, 2000));
424
+
425
+ const result = {
426
+ taskId: this.currentTask.id,
427
+ result: `Completed task: ${this.currentTask.prompt}`,
428
+ timestamp: new Date().toISOString()
429
+ };
430
+
431
+ // Send result back to server
432
+ this.send({
433
+ type: 'task_result',
434
+ sessionId: this.sessionId,
435
+ taskId: this.currentTask.id,
436
+ result: result.result,
437
+ timestamp: result.timestamp
438
+ });
439
+
440
+ console.log(` Completed task: ${this.currentTask.prompt}`);
441
+
442
+ } catch (error) {
443
+ console.error('Error processing task:', error);
444
+ } finally {
445
+ this.currentTask = null;
446
+ // Process next task if available
447
+ if (this.taskQueue.length > 0) {
448
+ this.processTaskQueue();
381
449
  }
382
- });
450
+ }
383
451
  }
384
452
 
385
- sendChatMessage(content) {
386
- this.send({
387
- type: 'chat',
388
- sessionId: this.sessionId,
389
- content,
390
- timestamp: new Date().toISOString()
453
+ handleTaskCompleted(data) {
454
+ console.log(`\n Task completed by all agents!`);
455
+ console.log(`Task: ${this.tasks.get(data.taskId)?.prompt || 'Unknown task'}`);
456
+ console.log('Results:');
457
+
458
+ data.results.forEach(result => {
459
+ console.log(`- ${this.getClientName(result.clientId)}: ${result.result}`);
391
460
  });
461
+
462
+ this.emit('task_completed', data);
392
463
  }
393
464
 
394
465
  updateWork(work) {
395
- const workUpdate = {
396
- id: uuidv4(),
397
- description: work.description,
466
+ const workItem = {
467
+ ...work,
468
+ clientId: this.clientId,
398
469
  status: work.status || 'in_progress',
399
470
  timestamp: new Date().toISOString()
400
471
  };
@@ -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.15",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {