sam-coder-cli 1.0.13 → 1.0.14
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 +240 -23
- package/package.json +1 -1
|
@@ -1,8 +1,210 @@
|
|
|
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');
|
|
8
|
+
|
|
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
|
|
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');
|
|
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 = {};
|
|
56
|
+
|
|
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
|
+
}
|
|
105
|
+
|
|
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()
|
|
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
|
+
}
|
|
142
|
+
|
|
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);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
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
|
+
});
|
|
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
|
+
}
|
|
6
208
|
|
|
7
209
|
class MultiplayerClient extends EventEmitter {
|
|
8
210
|
constructor(options = {}) {
|
|
@@ -22,29 +224,44 @@ class MultiplayerClient extends EventEmitter {
|
|
|
22
224
|
});
|
|
23
225
|
}
|
|
24
226
|
|
|
25
|
-
connect() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
227
|
+
async connect() {
|
|
228
|
+
try {
|
|
229
|
+
// Ensure server is running and get the correct URL
|
|
230
|
+
const serverPort = new URL(this.serverUrl).port || 8080;
|
|
231
|
+
this.serverUrl = await ensureServerRunning(serverPort);
|
|
232
|
+
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
235
|
+
|
|
236
|
+
this.ws.on('open', () => {
|
|
237
|
+
this.connected = true;
|
|
238
|
+
this.emit('connected');
|
|
239
|
+
resolve();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
this.ws.on('message', (data) => {
|
|
243
|
+
try {
|
|
244
|
+
const message = JSON.parse(data);
|
|
245
|
+
this.handleMessage(message);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('Error parsing message:', error);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
this.ws.on('close', () => {
|
|
252
|
+
this.connected = false;
|
|
253
|
+
this.emit('disconnected');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
this.ws.on('error', (error) => {
|
|
257
|
+
this.connected = false;
|
|
258
|
+
reject(error);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error('Failed to start multiplayer server:', error);
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
48
265
|
}
|
|
49
266
|
|
|
50
267
|
handleMessage(message) {
|