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.
- package/bin/multiplayer-client.js +373 -85
- package/bin/multiplayer-mode.js +217 -114
- package/bin/multiplayer-server.js +155 -56
- package/package.json +1 -1
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
330
|
+
console.error('Error:', message.message);
|
|
72
331
|
break;
|
|
73
332
|
default:
|
|
74
|
-
|
|
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.
|
|
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.
|
|
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
|
|
106
|
-
if (
|
|
107
|
-
console.log(chalk.yellow(`\n${
|
|
108
|
-
this.clients.
|
|
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.
|
|
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.
|
|
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(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
157
|
-
this.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
466
|
+
const workItem = {
|
|
467
|
+
...work,
|
|
468
|
+
clientId: this.clientId,
|
|
181
469
|
status: work.status || 'in_progress',
|
|
182
470
|
timestamp: new Date().toISOString()
|
|
183
471
|
};
|
package/bin/multiplayer-mode.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const MultiplayerClient = require('./multiplayer-client');
|
|
2
|
-
const chalk = require('chalk');
|
|
3
2
|
const readline = require('readline');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
4
|
const { v4: uuidv4 } = require('uuid');
|
|
5
5
|
|
|
6
6
|
class MultiplayerMode {
|
|
@@ -86,142 +86,245 @@ class MultiplayerMode {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
async start() {
|
|
89
|
-
|
|
89
|
+
console.clear();
|
|
90
|
+
this.printHeader();
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
// Set agent name
|
|
93
|
+
this.client.name = await this.askQuestion('Enter your name (or press Enter for a random one): ') ||
|
|
94
|
+
`Agent-${Math.random().toString(36).substr(2, 4)}`;
|
|
95
|
+
|
|
96
|
+
// Set agent role
|
|
97
|
+
console.log('\nAvailable roles:');
|
|
98
|
+
Object.entries(MultiplayerClient.AGENT_ROLES).forEach(([key, value]) => {
|
|
99
|
+
console.log(`- ${key}: ${value}`);
|
|
98
100
|
});
|
|
101
|
+
this.client.role = await this.askQuestion('\nChoose your role (or press Enter for Developer): ') ||
|
|
102
|
+
MultiplayerClient.AGENT_ROLES.DEVELOPER;
|
|
103
|
+
|
|
104
|
+
// Get session ID from user
|
|
105
|
+
const sessionId = await this.askQuestion('\nEnter session ID to join or press Enter to create a new session: ');
|
|
106
|
+
|
|
107
|
+
if (sessionId) {
|
|
108
|
+
await this.joinSession(sessionId);
|
|
109
|
+
} else {
|
|
110
|
+
await this.createSession();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.setupCommandHandlers();
|
|
114
|
+
this.prompt();
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
117
|
+
async askQuestion(question) {
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
this.rl.question(question, (answer) => {
|
|
120
|
+
resolve(answer.trim());
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
109
124
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
async joinSession(sessionId) {
|
|
126
|
+
this.client.joinSession(sessionId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async createSession() {
|
|
130
|
+
console.log(chalk.yellow('Creating a new session...'));
|
|
131
|
+
this.client.createSession();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setupCommandHandlers() {
|
|
135
|
+
this.client.on('command', async (command) => {
|
|
136
|
+
await this.handleCommand(command);
|
|
118
137
|
});
|
|
119
138
|
}
|
|
120
139
|
|
|
121
|
-
async handleCommand(
|
|
122
|
-
const [
|
|
140
|
+
async handleCommand(input) {
|
|
141
|
+
const [command, ...args] = input.slice(1).split(' ');
|
|
123
142
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
143
|
+
try {
|
|
144
|
+
switch (command.toLowerCase()) {
|
|
145
|
+
case 'name':
|
|
146
|
+
if (args.length > 0) {
|
|
147
|
+
const newName = args.join(' ');
|
|
148
|
+
this.client.name = newName;
|
|
149
|
+
await this.client.updateClientInfo({ name: newName });
|
|
150
|
+
console.log(chalk.green(`✅ Name updated to: ${newName}`));
|
|
151
|
+
} else {
|
|
152
|
+
console.log(chalk.yellow(`Current name: ${this.client.name}`));
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
case 'role':
|
|
157
|
+
if (args.length > 0) {
|
|
158
|
+
const newRole = args.join(' ');
|
|
159
|
+
this.client.role = newRole;
|
|
160
|
+
await this.client.updateClientInfo({ role: newRole });
|
|
161
|
+
console.log(chalk.green(`✅ Role updated to: ${newRole}`));
|
|
162
|
+
} else {
|
|
163
|
+
console.log(chalk.yellow(`Current role: ${this.client.role}`));
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case 'model':
|
|
168
|
+
if (args.length > 0) {
|
|
169
|
+
this.client.model = args[0];
|
|
170
|
+
await this.client.updateClientInfo({ model: this.client.model });
|
|
171
|
+
console.log(chalk.green(`✅ Model updated to: ${this.client.model}`));
|
|
172
|
+
} else {
|
|
173
|
+
console.log(chalk.yellow(`Current model: ${this.client.model}`));
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case 'work':
|
|
178
|
+
if (args.length > 0) {
|
|
179
|
+
const workDesc = args.join(' ');
|
|
180
|
+
this.client.updateWork({
|
|
181
|
+
description: workDesc,
|
|
182
|
+
status: 'in_progress',
|
|
183
|
+
timestamp: new Date().toISOString()
|
|
184
|
+
});
|
|
185
|
+
console.log(chalk.green(`✅ Work updated: ${workDesc}`));
|
|
186
|
+
} else {
|
|
187
|
+
console.log(chalk.yellow('Please provide a work description. Example: /work Fixing bugs'));
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
168
190
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
191
|
+
case 'collab':
|
|
192
|
+
case 'collaborate':
|
|
193
|
+
if (args.length > 0) {
|
|
194
|
+
if (!this.client.isHost) {
|
|
195
|
+
console.log(chalk.yellow('Only the session host can initiate collaboration'));
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
const taskType = args[0].toLowerCase();
|
|
199
|
+
const prompt = args.slice(1).join(' ');
|
|
200
|
+
|
|
201
|
+
if (!Object.values(MultiplayerClient.TASK_TYPES).includes(taskType)) {
|
|
202
|
+
console.log(chalk.yellow(`Invalid task type. Available types: ${Object.values(MultiplayerClient.TASK_TYPES).join(', ')}`));
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!prompt) {
|
|
207
|
+
console.log(chalk.yellow('Please provide a task prompt. Example: /collab research How does quantum computing work?'));
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(chalk.blue(`\n🤝 Starting collaborative ${taskType} task: ${prompt}`));
|
|
212
|
+
await this.client.collaborate(prompt, taskType);
|
|
213
|
+
|
|
214
|
+
} else {
|
|
215
|
+
console.log(chalk.yellow('Usage: /collab <type> <prompt>'));
|
|
216
|
+
console.log(chalk.yellow('Types: ' + Object.values(MultiplayerClient.TASK_TYPES).join(', ')));
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'task':
|
|
221
|
+
if (args.length > 1) {
|
|
222
|
+
const [target, ...taskArgs] = args;
|
|
223
|
+
const taskDesc = taskArgs.join(' ');
|
|
224
|
+
|
|
225
|
+
// Find the target client
|
|
226
|
+
const targetClient = this.client.clients.find(c =>
|
|
227
|
+
c.name.toLowerCase() === target.toLowerCase() ||
|
|
228
|
+
c.clientId === target
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
if (!targetClient) {
|
|
232
|
+
console.log(chalk.red(`❌ Could not find agent: ${target}`));
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// In a real implementation, you would send this to the server
|
|
237
|
+
// and the server would route it to the appropriate client
|
|
238
|
+
console.log(chalk.blue(`\n📝 Task assigned to ${targetClient.name}: ${taskDesc}`));
|
|
172
239
|
|
|
173
|
-
if (targetClient) {
|
|
174
|
-
this.client.assignTask({
|
|
175
|
-
description: taskDescription,
|
|
176
|
-
status: 'pending'
|
|
177
|
-
}, targetClient.clientId);
|
|
178
|
-
console.log(chalk.green(`Task assigned to ${targetClient.name}`));
|
|
179
240
|
} else {
|
|
180
|
-
console.log(chalk.
|
|
241
|
+
console.log(chalk.yellow('Usage: /task <agent> <task description>'));
|
|
242
|
+
console.log(chalk.yellow('Example: /task alice Please review the latest changes'));
|
|
181
243
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
244
|
+
break;
|
|
245
|
+
|
|
246
|
+
case 'list':
|
|
247
|
+
this.listAgents();
|
|
248
|
+
break;
|
|
249
|
+
|
|
250
|
+
case 'exit':
|
|
251
|
+
console.log(chalk.yellow('\n👋 Disconnecting...'));
|
|
252
|
+
process.exit(0);
|
|
253
|
+
break;
|
|
254
|
+
|
|
255
|
+
case 'help':
|
|
256
|
+
this.showHelp();
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
default:
|
|
260
|
+
console.log(chalk.red(`❌ Unknown command: ${command}. Type /help for available commands.`));
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}`));
|
|
199
264
|
}
|
|
200
265
|
|
|
201
266
|
this.prompt();
|
|
202
267
|
}
|
|
203
268
|
|
|
204
269
|
showHelp() {
|
|
205
|
-
console.log(
|
|
206
|
-
console.log('
|
|
207
|
-
console.log(' /name [new_name]
|
|
208
|
-
console.log(' /role [role]
|
|
209
|
-
console.log(' /
|
|
210
|
-
|
|
211
|
-
console.log('
|
|
212
|
-
console.log(' /
|
|
270
|
+
console.log('\n=== 🆘 Available Commands ===');
|
|
271
|
+
console.log('\n👤 Agent Management:');
|
|
272
|
+
console.log(' /name [new_name] - Set or show your name');
|
|
273
|
+
console.log(' /role [role] - Set or show your role');
|
|
274
|
+
console.log(' /model [model_name] - Set or show your AI model');
|
|
275
|
+
|
|
276
|
+
console.log('\n🤝 Collaboration:');
|
|
277
|
+
console.log(' /work [description] - Update your work status');
|
|
278
|
+
console.log(' /collab <type> <prompt> - Start a collaborative task');
|
|
279
|
+
console.log(' Types: ' + Object.values(MultiplayerClient.TASK_TYPES).join(', '));
|
|
280
|
+
console.log(' /task [agent] [task] - Assign a task to another agent');
|
|
281
|
+
|
|
282
|
+
console.log('\n📋 Session:');
|
|
283
|
+
console.log(' /list - List all agents in the session');
|
|
284
|
+
console.log(' /exit - Leave the session');
|
|
285
|
+
console.log(' /help - Show this help message');
|
|
286
|
+
|
|
287
|
+
console.log('\n💡 Example:');
|
|
288
|
+
console.log(' /collab research How does quantum computing work?');
|
|
289
|
+
console.log(' /work Implementing user authentication');
|
|
290
|
+
console.log(' /task bob Please review my latest changes');
|
|
213
291
|
}
|
|
214
292
|
|
|
215
293
|
listAgents() {
|
|
216
|
-
console.log(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const hostInfo = client.isHost ? ' [HOST]' : '';
|
|
221
|
-
console.log(` ${index}. ${chalk.green(client.name)}${roleInfo}${hostInfo}`);
|
|
222
|
-
index++;
|
|
294
|
+
console.log('\n=== 🧑💻 Agents in Session ===');
|
|
295
|
+
if (this.client.clients.length === 0) {
|
|
296
|
+
console.log('No other agents in the session');
|
|
297
|
+
return;
|
|
223
298
|
}
|
|
224
|
-
|
|
299
|
+
|
|
300
|
+
this.client.clients.forEach(client => {
|
|
301
|
+
const role = client.role ? ` (${client.role})` : '';
|
|
302
|
+
const status = client.isHost ? '👑 ' : '👤 ';
|
|
303
|
+
console.log(`${status}${client.name}${role} [${client.model}]`);
|
|
304
|
+
if (client.work) {
|
|
305
|
+
console.log(` ${client.work.status === 'completed' ? '✅' : '🔄'} ${client.work.description}`);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
prompt() {
|
|
311
|
+
if (this.isWorking) return;
|
|
312
|
+
|
|
313
|
+
this.rl.question(chalk.blue('> '), async (input) => {
|
|
314
|
+
if (!input.trim()) {
|
|
315
|
+
this.prompt();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Handle commands
|
|
320
|
+
if (input.startsWith('/')) {
|
|
321
|
+
await this.handleCommand(input);
|
|
322
|
+
} else {
|
|
323
|
+
// Regular chat message
|
|
324
|
+
this.client.sendChatMessage(input);
|
|
325
|
+
this.prompt();
|
|
326
|
+
}
|
|
327
|
+
});
|
|
225
328
|
}
|
|
226
329
|
}
|
|
227
330
|
|
|
@@ -6,24 +6,20 @@ class MultiplayerServer {
|
|
|
6
6
|
constructor(port = 8080) {
|
|
7
7
|
this.port = port;
|
|
8
8
|
this.sessions = new Map();
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
9
|
+
this.clients = new Map();
|
|
10
|
+
this.tasks = new Map();
|
|
11
|
+
this.server = new WebSocket.Server({ port });
|
|
11
12
|
this.setupEventHandlers();
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
setupEventHandlers() {
|
|
15
|
-
this.
|
|
16
|
+
this.server.on('connection', (ws) => {
|
|
16
17
|
let clientId = uuid.v4();
|
|
17
18
|
let sessionId = null;
|
|
18
19
|
let clientInfo = {};
|
|
19
20
|
|
|
20
21
|
ws.on('message', (message) => {
|
|
21
|
-
|
|
22
|
-
const data = JSON.parse(message);
|
|
23
|
-
this.handleMessage(ws, clientId, data);
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.error('Error parsing message:', error);
|
|
26
|
-
}
|
|
22
|
+
this.handleMessage(ws, message);
|
|
27
23
|
});
|
|
28
24
|
|
|
29
25
|
ws.on('close', () => {
|
|
@@ -35,7 +31,7 @@ class MultiplayerServer {
|
|
|
35
31
|
clientId,
|
|
36
32
|
clientInfo
|
|
37
33
|
});
|
|
38
|
-
|
|
34
|
+
|
|
39
35
|
if (Object.keys(session.clients).length === 0) {
|
|
40
36
|
this.sessions.delete(sessionId);
|
|
41
37
|
}
|
|
@@ -44,48 +40,129 @@ class MultiplayerServer {
|
|
|
44
40
|
});
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
handleMessage(ws,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
43
|
+
handleMessage(ws, message) {
|
|
44
|
+
try {
|
|
45
|
+
const data = typeof message === 'string' ? JSON.parse(message) : message;
|
|
46
|
+
const client = this.clients.get(ws);
|
|
47
|
+
|
|
48
|
+
switch (data.type) {
|
|
49
|
+
case 'client_info':
|
|
50
|
+
if (client) {
|
|
51
|
+
Object.assign(client, data.clientInfo);
|
|
52
|
+
this.broadcastToSession(client.sessionId, {
|
|
53
|
+
type: 'client_updated',
|
|
54
|
+
clientId: client.id,
|
|
55
|
+
clientInfo: data.clientInfo
|
|
56
|
+
}, ws);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case 'create_session':
|
|
61
|
+
this.handleCreateSession(ws, data);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'join_session':
|
|
65
|
+
this.handleJoinSession(ws, data);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'collaborate':
|
|
69
|
+
if (client && client.sessionId) {
|
|
70
|
+
const taskId = uuid.v4();
|
|
71
|
+
this.tasks.set(taskId, {
|
|
72
|
+
id: taskId,
|
|
73
|
+
sessionId: client.sessionId,
|
|
74
|
+
prompt: data.prompt,
|
|
75
|
+
status: 'in_progress',
|
|
76
|
+
createdBy: client.id,
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
results: []
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.broadcastToSession(client.sessionId, {
|
|
82
|
+
type: 'task_assigned',
|
|
83
|
+
taskId,
|
|
84
|
+
prompt: data.prompt,
|
|
85
|
+
assignedBy: client.id
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'task_result':
|
|
91
|
+
if (client && client.sessionId && data.taskId) {
|
|
92
|
+
const task = this.tasks.get(data.taskId);
|
|
93
|
+
if (task) {
|
|
94
|
+
task.results.push({
|
|
95
|
+
clientId: client.id,
|
|
96
|
+
result: data.result,
|
|
97
|
+
timestamp: new Date().toISOString()
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Check if all clients have responded
|
|
101
|
+
const session = this.sessions.get(client.sessionId);
|
|
102
|
+
if (session && task.results.length >= Object.keys(session.clients).length) {
|
|
103
|
+
task.status = 'completed';
|
|
104
|
+
this.broadcastToSession(client.sessionId, {
|
|
105
|
+
type: 'task_completed',
|
|
106
|
+
taskId: task.id,
|
|
107
|
+
results: task.results
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'chat':
|
|
115
|
+
case 'work_update':
|
|
116
|
+
case 'task_update':
|
|
117
|
+
this.broadcastToSession(data.sessionId, data);
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
default:
|
|
121
|
+
console.log('Unknown message type:', data.type);
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('Error handling message:', error);
|
|
66
125
|
}
|
|
67
126
|
}
|
|
68
127
|
|
|
69
|
-
handleCreateSession(ws,
|
|
128
|
+
handleCreateSession(ws, data) {
|
|
70
129
|
const sessionId = uuid.v4();
|
|
130
|
+
const clientId = uuid.v4();
|
|
131
|
+
|
|
132
|
+
// Store client info
|
|
133
|
+
this.clients.set(ws, {
|
|
134
|
+
id: clientId,
|
|
135
|
+
sessionId,
|
|
136
|
+
ws,
|
|
137
|
+
isHost: true,
|
|
138
|
+
name: data.name || `Agent-${Math.random().toString(36).substr(2, 4)}`,
|
|
139
|
+
role: data.role || 'Assistant',
|
|
140
|
+
model: data.model || 'default'
|
|
141
|
+
});
|
|
142
|
+
|
|
71
143
|
this.sessions.set(sessionId, {
|
|
72
144
|
id: sessionId,
|
|
73
|
-
clients: {},
|
|
145
|
+
clients: { [clientId]: true },
|
|
74
146
|
workHistory: [],
|
|
75
|
-
createdAt: new Date().toISOString()
|
|
147
|
+
createdAt: new Date().toISOString(),
|
|
148
|
+
hostId: clientId
|
|
76
149
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
150
|
+
|
|
151
|
+
// Send session created confirmation
|
|
152
|
+
ws.send(JSON.stringify({
|
|
153
|
+
type: 'session_created',
|
|
80
154
|
sessionId,
|
|
155
|
+
clientId,
|
|
81
156
|
isHost: true
|
|
82
|
-
});
|
|
157
|
+
}));
|
|
83
158
|
}
|
|
84
159
|
|
|
85
|
-
handleJoinSession(ws,
|
|
86
|
-
const { sessionId
|
|
87
|
-
|
|
88
|
-
|
|
160
|
+
handleJoinSession(ws, data) {
|
|
161
|
+
const { sessionId } = data;
|
|
162
|
+
const clientId = data.clientId || uuid.v4();
|
|
163
|
+
const session = this.sessions.get(sessionId);
|
|
164
|
+
|
|
165
|
+
if (!session) {
|
|
89
166
|
ws.send(JSON.stringify({
|
|
90
167
|
type: 'error',
|
|
91
168
|
message: 'Session not found'
|
|
@@ -93,8 +170,6 @@ class MultiplayerServer {
|
|
|
93
170
|
return;
|
|
94
171
|
}
|
|
95
172
|
|
|
96
|
-
const session = this.sessions.get(sessionId);
|
|
97
|
-
|
|
98
173
|
if (Object.keys(session.clients).length >= 4) {
|
|
99
174
|
ws.send(JSON.stringify({
|
|
100
175
|
type: 'error',
|
|
@@ -103,40 +178,64 @@ class MultiplayerServer {
|
|
|
103
178
|
return;
|
|
104
179
|
}
|
|
105
180
|
|
|
106
|
-
//
|
|
107
|
-
|
|
181
|
+
// Store/update client info
|
|
182
|
+
const existingClient = Array.from(this.clients.entries())
|
|
183
|
+
.find(([_, c]) => c.id === clientId)?.[1];
|
|
184
|
+
|
|
185
|
+
const clientInfo = {
|
|
186
|
+
id: clientId,
|
|
187
|
+
sessionId,
|
|
108
188
|
ws,
|
|
109
|
-
clientId,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
isHost: data.isHost || false
|
|
114
|
-
}
|
|
189
|
+
isHost: session.hostId === clientId,
|
|
190
|
+
name: data.name || existingClient?.name || `Agent-${clientId.substr(0, 4)}`,
|
|
191
|
+
role: data.role || existingClient?.role || 'Assistant',
|
|
192
|
+
model: data.model || existingClient?.model || 'default'
|
|
115
193
|
};
|
|
116
194
|
|
|
195
|
+
this.clients.set(ws, clientInfo);
|
|
196
|
+
session.clients[clientId] = true;
|
|
197
|
+
|
|
198
|
+
// Get all client infos for the session
|
|
199
|
+
const sessionClients = Array.from(this.clients.values())
|
|
200
|
+
.filter(c => c.sessionId === sessionId)
|
|
201
|
+
.map(({ id, name, role, isHost }) => ({
|
|
202
|
+
id,
|
|
203
|
+
name,
|
|
204
|
+
role,
|
|
205
|
+
isHost
|
|
206
|
+
}));
|
|
207
|
+
|
|
117
208
|
// Send welcome message with session info
|
|
118
209
|
ws.send(JSON.stringify({
|
|
119
210
|
type: 'session_joined',
|
|
120
211
|
sessionId,
|
|
121
212
|
clientId,
|
|
122
|
-
|
|
123
|
-
|
|
213
|
+
isHost: clientInfo.isHost,
|
|
214
|
+
clients: sessionClients,
|
|
215
|
+
workHistory: session.workHistory,
|
|
216
|
+
pendingTasks: Array.from(this.tasks.values())
|
|
217
|
+
.filter(t => t.sessionId === sessionId && t.status === 'in_progress')
|
|
124
218
|
}));
|
|
125
219
|
|
|
126
220
|
// Notify other clients
|
|
127
221
|
this.broadcastToSession(sessionId, {
|
|
128
222
|
type: 'agent_joined',
|
|
129
223
|
clientId,
|
|
130
|
-
clientInfo:
|
|
131
|
-
|
|
224
|
+
clientInfo: {
|
|
225
|
+
id: clientId,
|
|
226
|
+
name: clientInfo.name,
|
|
227
|
+
role: clientInfo.role,
|
|
228
|
+
isHost: clientInfo.isHost
|
|
229
|
+
}
|
|
230
|
+
}, ws);
|
|
132
231
|
}
|
|
133
232
|
|
|
134
233
|
broadcastToSession(sessionId, message, excludeClientId = null) {
|
|
135
234
|
if (!this.sessions.has(sessionId)) return;
|
|
136
|
-
|
|
235
|
+
|
|
137
236
|
const session = this.sessions.get(sessionId);
|
|
138
237
|
const messageStr = JSON.stringify(message);
|
|
139
|
-
|
|
238
|
+
|
|
140
239
|
Object.entries(session.clients).forEach(([id, client]) => {
|
|
141
240
|
if (id !== excludeClientId && client.ws.readyState === WebSocket.OPEN) {
|
|
142
241
|
client.ws.send(messageStr);
|