sam-coder-cli 1.0.13 → 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.
@@ -1,57 +1,310 @@
1
1
  const WebSocket = require('ws');
2
+ const http = require('http');
2
3
  const EventEmitter = require('events');
3
4
  const readline = require('readline');
4
5
  const chalk = require('chalk');
5
6
  const { v4: uuidv4 } = require('uuid');
7
+ const { spawn, execSync } = require('child_process');
6
8
 
7
- class MultiplayerClient extends EventEmitter {
8
- constructor(options = {}) {
9
- super();
10
- this.serverUrl = options.serverUrl || 'ws://localhost:8080';
11
- this.clientId = uuidv4();
12
- this.sessionId = null;
13
- this.agentName = options.agentName || `Agent-${Math.floor(Math.random() * 1000)}`;
14
- this.model = options.model || 'default';
15
- this.isHost = false;
16
- this.clients = new Map();
17
- this.workHistory = [];
18
- this.ws = null;
19
- this.rl = options.rl || readline.createInterface({
20
- input: process.stdin,
21
- output: process.stdout
9
+ async function isPortInUse(port) {
10
+ return new Promise((resolve) => {
11
+ const net = require('net');
12
+ const server = net.createServer()
13
+ .once('error', () => resolve(true))
14
+ .once('listening', () => {
15
+ server.close();
16
+ resolve(false);
17
+ })
18
+ .listen(port);
19
+ });
20
+ }
21
+
22
+ async function ensureServerRunning(port = 8080) {
23
+ const portInUse = await isPortInUse(port);
24
+ if (!portInUse) {
25
+ console.log('Starting local multiplayer server...');
26
+ // Start server in a detached process
27
+ const serverProcess = spawn('node', [__filename, '--server', '--port', port.toString()], {
28
+ detached: true,
29
+ stdio: 'ignore',
30
+ windowsHide: true
22
31
  });
32
+
33
+ serverProcess.unref();
34
+
35
+ // Wait for server to start
36
+ await new Promise(resolve => setTimeout(resolve, 1000));
37
+ console.log('Local multiplayer server started');
23
38
  }
39
+ return `ws://localhost:${port}`;
40
+ }
41
+
42
+ class MultiplayerServer {
43
+ constructor(port = 8080, isChildProcess = false) {
44
+ this.port = port;
45
+ this.sessions = new Map();
46
+ this.server = http.createServer();
47
+ this.wss = new WebSocket.Server({ server: this.server });
48
+ this.setupEventHandlers();
49
+ }
50
+
51
+ setupEventHandlers() {
52
+ this.wss.on('connection', (ws) => {
53
+ let clientId = uuidv4();
54
+ let sessionId = null;
55
+ let clientInfo = {};
24
56
 
25
- connect() {
26
- this.ws = new WebSocket(this.serverUrl);
57
+ ws.on('message', (message) => {
58
+ try {
59
+ const data = JSON.parse(message);
60
+ this.handleMessage(ws, clientId, data);
61
+ } catch (error) {
62
+ console.error('Error parsing message:', error);
63
+ }
64
+ });
65
+
66
+ ws.on('close', () => {
67
+ if (sessionId && this.sessions.has(sessionId)) {
68
+ const session = this.sessions.get(sessionId);
69
+ delete session.clients[clientId];
70
+ this.broadcastToSession(sessionId, {
71
+ type: 'agent_left',
72
+ clientId,
73
+ clientInfo
74
+ });
75
+
76
+ if (Object.keys(session.clients).length === 0) {
77
+ this.sessions.delete(sessionId);
78
+ }
79
+ }
80
+ });
81
+ });
82
+ }
83
+
84
+ handleMessage(ws, clientId, data) {
85
+ switch (data.type) {
86
+ case 'create_session':
87
+ this.handleCreateSession(ws, clientId, data);
88
+ break;
89
+ case 'join_session':
90
+ this.handleJoinSession(ws, clientId, data);
91
+ break;
92
+ case 'update_work':
93
+ case 'chat':
94
+ case 'task_update':
95
+ this.broadcastToSession(data.sessionId, {
96
+ ...data,
97
+ clientId,
98
+ timestamp: new Date().toISOString()
99
+ });
100
+ break;
101
+ default:
102
+ console.log('Unknown message type:', data.type);
103
+ }
104
+ }
27
105
 
28
- this.ws.on('open', () => {
29
- this.emit('connected');
106
+ handleCreateSession(ws, clientId, data) {
107
+ const sessionId = uuidv4();
108
+ this.sessions.set(sessionId, {
109
+ id: sessionId,
110
+ clients: {},
111
+ workHistory: [],
112
+ createdAt: new Date().toISOString()
30
113
  });
114
+
115
+ this.handleJoinSession(ws, clientId, {
116
+ ...data,
117
+ sessionId,
118
+ isHost: true
119
+ });
120
+ }
121
+
122
+ handleJoinSession(ws, clientId, data) {
123
+ const { sessionId, clientInfo } = data;
124
+
125
+ if (!this.sessions.has(sessionId)) {
126
+ ws.send(JSON.stringify({
127
+ type: 'error',
128
+ message: 'Session not found'
129
+ }));
130
+ return;
131
+ }
132
+
133
+ const session = this.sessions.get(sessionId);
134
+
135
+ if (Object.keys(session.clients).length >= 4) {
136
+ ws.send(JSON.stringify({
137
+ type: 'error',
138
+ message: 'Session is full (max 4 agents)'
139
+ }));
140
+ return;
141
+ }
31
142
 
32
- this.ws.on('message', (data) => {
33
- try {
34
- const message = JSON.parse(data);
35
- this.handleMessage(message);
36
- } catch (error) {
37
- console.error('Error parsing message:', error);
143
+ // Add client to session
144
+ session.clients[clientId] = {
145
+ ws,
146
+ clientId,
147
+ clientInfo: {
148
+ ...clientInfo,
149
+ joinedAt: new Date().toISOString(),
150
+ isHost: data.isHost || false
151
+ }
152
+ };
153
+
154
+ // Send welcome message with session info
155
+ ws.send(JSON.stringify({
156
+ type: 'session_joined',
157
+ sessionId,
158
+ clientId,
159
+ clients: Object.values(session.clients).map(c => c.clientInfo),
160
+ workHistory: session.workHistory
161
+ }));
162
+
163
+ // Notify other clients
164
+ this.broadcastToSession(sessionId, {
165
+ type: 'agent_joined',
166
+ clientId,
167
+ clientInfo: session.clients[clientId].clientInfo
168
+ }, clientId);
169
+ }
170
+
171
+ broadcastToSession(sessionId, message, excludeClientId = null) {
172
+ if (!this.sessions.has(sessionId)) return;
173
+
174
+ const session = this.sessions.get(sessionId);
175
+ const messageStr = JSON.stringify(message);
176
+
177
+ Object.entries(session.clients).forEach(([id, client]) => {
178
+ if (id !== excludeClientId && client.ws.readyState === WebSocket.OPEN) {
179
+ client.ws.send(messageStr);
38
180
  }
39
181
  });
182
+ }
40
183
 
41
- this.ws.on('close', () => {
42
- this.emit('disconnected');
184
+ start() {
185
+ return new Promise((resolve) => {
186
+ this.server.listen(this.port, () => {
187
+ if (!this.isChildProcess) {
188
+ console.log(`Multiplayer server running on ws://localhost:${this.port}`);
189
+ }
190
+ resolve();
191
+ });
43
192
  });
193
+ }
194
+
195
+ static async startAsChildProcess(port = 8080) {
196
+ const server = new MultiplayerServer(port, true);
197
+ await server.start();
198
+ // Keep process alive
199
+ process.on('SIGINT', () => process.exit());
200
+ }
201
+ }
202
+
203
+ // Start server if this file is run directly
204
+ if (require.main === module && process.argv.includes('--server')) {
205
+ const port = parseInt(process.argv[process.argv.indexOf('--port') + 1]) || 8080;
206
+ MultiplayerServer.startAsChildProcess(port);
207
+ }
44
208
 
45
- this.ws.on('error', (error) => {
46
- this.emit('error', error);
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
+ };
223
+ constructor(options = {}) {
224
+ super();
225
+ this.serverUrl = options.serverUrl || 'ws://localhost:8080';
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;
229
+ this.model = options.model || 'default';
230
+ this.sessionId = options.sessionId || null;
231
+ this.isHost = options.isHost || false;
232
+ this.connected = false;
233
+ this.ws = null;
234
+ this.workHistory = [];
235
+ this.clients = [];
236
+ this.currentTask = null;
237
+ this.taskQueue = [];
238
+ this.rl = options.rl || readline.createInterface({
239
+ input: process.stdin,
240
+ output: process.stdout
47
241
  });
48
242
  }
49
243
 
244
+ async connect() {
245
+ try {
246
+ // Ensure server is running and get the correct URL
247
+ const url = new URL(this.serverUrl);
248
+ const serverPort = url.port || 8080;
249
+ this.serverUrl = await ensureServerRunning(serverPort);
250
+
251
+ return new Promise((resolve, reject) => {
252
+ this.ws = new WebSocket(this.serverUrl);
253
+
254
+ this.ws.on('open', () => {
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
+ });
265
+ this.emit('connected');
266
+ resolve();
267
+ });
268
+
269
+ this.ws.on('message', (data) => {
270
+ try {
271
+ const message = JSON.parse(data);
272
+ this.handleMessage(message);
273
+ } catch (error) {
274
+ console.error('Error parsing message:', error);
275
+ }
276
+ });
277
+
278
+ this.ws.on('close', () => {
279
+ this.connected = false;
280
+ this.emit('disconnected');
281
+ });
282
+
283
+ this.ws.on('error', (error) => {
284
+ this.connected = false;
285
+ reject(error);
286
+ });
287
+ });
288
+ } catch (error) {
289
+ console.error('Failed to start multiplayer server:', error);
290
+ throw error;
291
+ }
292
+ }
293
+
50
294
  handleMessage(message) {
51
295
  switch (message.type) {
52
296
  case 'session_joined':
53
297
  this.handleSessionJoined(message);
54
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;
55
308
  case 'agent_joined':
56
309
  this.handleAgentJoined(message);
57
310
  break;
@@ -67,30 +320,45 @@ class MultiplayerClient extends EventEmitter {
67
320
  case 'task_assigned':
68
321
  this.handleTaskAssigned(message);
69
322
  break;
323
+ case 'task_completed':
324
+ this.handleTaskCompleted(message);
325
+ break;
326
+ case 'task_update':
327
+ this.handleTaskUpdate(message);
328
+ break;
70
329
  case 'error':
71
- this.handleError(message);
330
+ console.error('Error:', message.message);
72
331
  break;
73
332
  default:
74
- this.emit('message', message);
333
+ console.log('Unknown message type:', message.type);
75
334
  }
76
335
  }
77
336
 
78
337
  handleSessionJoined(message) {
79
338
  this.sessionId = message.sessionId;
80
- this.isHost = message.clients.find(c => c.clientId === this.clientId)?.isHost || false;
81
-
82
- // Update local client list
83
- this.clients.clear();
84
- message.clients.forEach(client => {
85
- this.clients.set(client.clientId, client);
86
- });
87
-
339
+ this.clients = message.clients || [];
88
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
+
89
349
  this.emit('session_joined', message);
90
350
  }
91
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
+
92
360
  handleAgentJoined(message) {
93
- this.clients.set(message.clientId, {
361
+ this.clients.push({
94
362
  ...message.clientInfo,
95
363
  clientId: message.clientId
96
364
  });
@@ -102,10 +370,10 @@ class MultiplayerClient extends EventEmitter {
102
370
  }
103
371
 
104
372
  handleAgentLeft(message) {
105
- const client = this.clients.get(message.clientId);
106
- if (client) {
107
- console.log(chalk.yellow(`\n${client.name || 'Agent'} has left the session`));
108
- 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);
109
377
  }
110
378
  this.emit('agent_left', message);
111
379
  }
@@ -113,7 +381,7 @@ class MultiplayerClient extends EventEmitter {
113
381
  handleChatMessage(message) {
114
382
  if (message.clientId === this.clientId) return;
115
383
 
116
- const sender = this.clients.get(message.clientId) || { name: 'Unknown' };
384
+ const sender = this.clients.find(c => c.clientId === message.clientId) || { name: 'Unknown' };
117
385
  console.log(chalk.cyan(`\n[${sender.name}]: ${message.content}`));
118
386
  this.emit('chat', message);
119
387
  }
@@ -125,59 +393,79 @@ class MultiplayerClient extends EventEmitter {
125
393
  clientId: message.clientId
126
394
  });
127
395
 
128
- const client = this.clients.get(message.clientId) || { name: 'Unknown' };
396
+ const client = this.clients.find(c => c.clientId === message.clientId) || { name: 'Unknown' };
129
397
  console.log(chalk.green(`\n[Work Update] ${client.name}: ${message.work.description}`));
130
398
  this.emit('work_updated', message);
131
399
  }
132
400
 
133
- handleTaskAssigned(message) {
134
- if (message.assignedTo === this.clientId) {
135
- console.log(chalk.magenta(`\n[Task Assigned] ${message.task.description}`));
136
- this.emit('task_assigned', message.task);
137
- }
138
- }
139
-
140
- handleError(message) {
141
- console.error(chalk.red(`\n[Error] ${message.message}`));
142
- this.emit('error', new Error(message.message));
143
- }
144
-
145
- createSession() {
146
- this.send({
147
- type: 'create_session',
148
- clientInfo: {
149
- name: this.agentName,
150
- model: this.model,
151
- isHost: true
152
- }
153
- });
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);
154
410
  }
155
411
 
156
- joinSession(sessionId) {
157
- this.sessionId = sessionId;
158
- this.send({
159
- type: 'join_session',
160
- sessionId,
161
- clientInfo: {
162
- name: this.agentName,
163
- 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();
164
449
  }
165
- });
450
+ }
166
451
  }
167
452
 
168
- sendChatMessage(content) {
169
- this.send({
170
- type: 'chat',
171
- sessionId: this.sessionId,
172
- content,
173
- 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}`);
174
460
  });
461
+
462
+ this.emit('task_completed', data);
175
463
  }
176
464
 
177
465
  updateWork(work) {
178
- const workUpdate = {
179
- id: uuidv4(),
180
- description: work.description,
466
+ const workItem = {
467
+ ...work,
468
+ clientId: this.clientId,
181
469
  status: work.status || 'in_progress',
182
470
  timestamp: new Date().toISOString()
183
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.13",
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": {