sam-coder-cli 1.0.10 → 1.0.12
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/MULTIPLAYER.md +112 -0
- package/bin/agi-cli.js +59 -12
- package/bin/multiplayer-client.js +222 -0
- package/bin/multiplayer-mode.js +228 -0
- package/bin/multiplayer-server.js +160 -0
- package/package.json +5 -2
package/MULTIPLAYER.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Multiplayer Mode for SAM-CODER
|
|
2
|
+
|
|
3
|
+
This guide explains how to use the Multiplayer Mode to collaborate with multiple AI agents in real-time.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Host or join multiplayer sessions
|
|
8
|
+
- Connect 2-4 AI agents in a single session
|
|
9
|
+
- Each agent can have a unique name and role
|
|
10
|
+
- Real-time chat and task coordination
|
|
11
|
+
- Shared work history and task assignment
|
|
12
|
+
- Support for different models/endpoints per agent
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
1. First, install the required dependencies:
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Start the multiplayer server in a separate terminal:
|
|
22
|
+
```bash
|
|
23
|
+
node bin/multiplayer-server.js
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. In a new terminal, start the first agent (host):
|
|
27
|
+
```bash
|
|
28
|
+
node bin/agi-cli.js --multiplayer
|
|
29
|
+
```
|
|
30
|
+
- Press Enter to create a new session
|
|
31
|
+
- Note the session ID shown in the console
|
|
32
|
+
|
|
33
|
+
4. In another terminal, start additional agents (up to 3 more):
|
|
34
|
+
```bash
|
|
35
|
+
node bin/agi-cli.js --multiplayer
|
|
36
|
+
```
|
|
37
|
+
- Enter the session ID from step 3 to join
|
|
38
|
+
|
|
39
|
+
## Multiplayer Commands
|
|
40
|
+
|
|
41
|
+
Once in a multiplayer session, you can use these commands:
|
|
42
|
+
|
|
43
|
+
- `/name [new_name]` - Set or show your agent's name
|
|
44
|
+
- `/role [role]` - Set or show your agent's role
|
|
45
|
+
- `/work [description]` - Update your work status
|
|
46
|
+
- `/task [agent_name] [task]` - Assign a task to another agent
|
|
47
|
+
- `/list` - List all agents in the session
|
|
48
|
+
- `/help` - Show available commands
|
|
49
|
+
- `/exit` - Leave the session
|
|
50
|
+
|
|
51
|
+
## Agent Coordination
|
|
52
|
+
|
|
53
|
+
### Work History
|
|
54
|
+
- All agents can see updates to the shared work history
|
|
55
|
+
- Use `/work` to update what you're working on
|
|
56
|
+
- The work history helps agents coordinate and avoid duplicate work
|
|
57
|
+
|
|
58
|
+
### Task Assignment
|
|
59
|
+
- Use `/task` to assign specific tasks to other agents
|
|
60
|
+
- Example: `/task Coder1 Implement the login function`
|
|
61
|
+
- Assigned tasks appear in the target agent's console
|
|
62
|
+
|
|
63
|
+
### Chat
|
|
64
|
+
- Type any message to send it to all agents in the session
|
|
65
|
+
- Use this to coordinate, ask questions, or share information
|
|
66
|
+
|
|
67
|
+
## Advanced Usage
|
|
68
|
+
|
|
69
|
+
### Custom Server URL
|
|
70
|
+
By default, the client connects to `ws://localhost:8080`. To use a different server:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
MP_SERVER_URL=ws://your-server-address:port node bin/agi-cli.js --multiplayer
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Different Models per Agent
|
|
77
|
+
Each agent can use a different model by setting the `MODEL` environment variable:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
MODEL=anthropic/claude-2 node bin/agi-cli.js --multiplayer
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Troubleshooting
|
|
84
|
+
|
|
85
|
+
### Connection Issues
|
|
86
|
+
- Ensure the multiplayer server is running
|
|
87
|
+
- Check that the server URL is correct
|
|
88
|
+
- Verify that your firewall allows WebSocket connections on the specified port
|
|
89
|
+
|
|
90
|
+
### Session Full
|
|
91
|
+
- Each session supports up to 4 agents
|
|
92
|
+
- Create a new session if you need more agents
|
|
93
|
+
|
|
94
|
+
### Agent Names
|
|
95
|
+
- Agent names must be unique within a session
|
|
96
|
+
- Use `/name` to change your agent's name if needed
|
|
97
|
+
|
|
98
|
+
## Example Workflow
|
|
99
|
+
|
|
100
|
+
1. Agent 1 creates a session and becomes the host
|
|
101
|
+
2. Agent 2 joins the session with `/join <session-id>`
|
|
102
|
+
3. Agents assign roles:
|
|
103
|
+
- Agent 1: `/role Backend Developer`
|
|
104
|
+
- Agent 2: `/role Frontend Developer`
|
|
105
|
+
4. They coordinate work:
|
|
106
|
+
- Agent 1: `/work Implementing user authentication API`
|
|
107
|
+
- Agent 2: `/work Creating login form UI`
|
|
108
|
+
5. They can assign tasks to each other:
|
|
109
|
+
- Agent 1: `/task Frontend Add form validation`
|
|
110
|
+
- Agent 2: `/task Backend Add rate limiting to auth endpoint`
|
|
111
|
+
|
|
112
|
+
Enjoy collaborating with multiple AI agents in real-time!
|
package/bin/agi-cli.js
CHANGED
|
@@ -8,6 +8,7 @@ const fs = require('fs').promises;
|
|
|
8
8
|
const { exec } = require('child_process');
|
|
9
9
|
const util = require('util');
|
|
10
10
|
const execAsync = util.promisify(exec);
|
|
11
|
+
const MultiplayerMode = require('./multiplayer-mode');
|
|
11
12
|
|
|
12
13
|
// Configuration
|
|
13
14
|
const CONFIG_PATH = path.join(os.homedir(), '.sam-coder-config.json');
|
|
@@ -382,6 +383,22 @@ function extractJsonFromMarkdown(text) {
|
|
|
382
383
|
// Call OpenRouter API with tool calling
|
|
383
384
|
async function callOpenRouter(messages, currentModel, useJson = false) {
|
|
384
385
|
const apiKey = OPENROUTER_API_KEY;
|
|
386
|
+
const isCustomEndpoint = API_BASE_URL !== 'https://openrouter.ai/api/v1';
|
|
387
|
+
|
|
388
|
+
let body = {
|
|
389
|
+
model: currentModel,
|
|
390
|
+
messages: messages,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// For standard OpenRouter calls that are not legacy, add tool parameters.
|
|
394
|
+
if (!isCustomEndpoint && !useJson) {
|
|
395
|
+
body.tools = tools;
|
|
396
|
+
body.tool_choice = 'auto';
|
|
397
|
+
}
|
|
398
|
+
// For custom endpoints (like vllm), ensure no tool-related parameters are sent.
|
|
399
|
+
else if (isCustomEndpoint) {
|
|
400
|
+
// The body is already clean for vLLM, containing only model and messages.
|
|
401
|
+
}
|
|
385
402
|
|
|
386
403
|
try {
|
|
387
404
|
const response = await fetch(`${API_BASE_URL}/chat/completions`, {
|
|
@@ -390,12 +407,7 @@ async function callOpenRouter(messages, currentModel, useJson = false) {
|
|
|
390
407
|
'Authorization': `Bearer ${apiKey}`,
|
|
391
408
|
'Content-Type': 'application/json'
|
|
392
409
|
},
|
|
393
|
-
body: JSON.stringify(
|
|
394
|
-
model: currentModel,
|
|
395
|
-
messages: messages,
|
|
396
|
-
tools: useJson ? undefined : tools,
|
|
397
|
-
tool_choice: useJson ? undefined : 'auto'
|
|
398
|
-
})
|
|
410
|
+
body: JSON.stringify(body)
|
|
399
411
|
});
|
|
400
412
|
|
|
401
413
|
if (!response.ok) {
|
|
@@ -661,10 +673,20 @@ async function chat(rl, useToolCalling, initialModel) {
|
|
|
661
673
|
});
|
|
662
674
|
}
|
|
663
675
|
|
|
664
|
-
function askForMode(rl) {
|
|
676
|
+
function askForMode(rl, includeMultiplayer = false) {
|
|
665
677
|
return new Promise((resolve) => {
|
|
666
|
-
|
|
667
|
-
|
|
678
|
+
const prompt = includeMultiplayer
|
|
679
|
+
? 'Select mode (1 for tool calling, 2 for function calling, 3 for multiplayer): '
|
|
680
|
+
: 'Select mode (1 for tool calling, 2 for function calling): ';
|
|
681
|
+
|
|
682
|
+
rl.question(prompt, (answer) => {
|
|
683
|
+
const choice = answer.trim();
|
|
684
|
+
if (includeMultiplayer) {
|
|
685
|
+
if (choice === '3') resolve('multiplayer');
|
|
686
|
+
else resolve(choice === '1' ? 'tool' : 'function');
|
|
687
|
+
} else {
|
|
688
|
+
resolve(choice === '1');
|
|
689
|
+
}
|
|
668
690
|
});
|
|
669
691
|
});
|
|
670
692
|
}
|
|
@@ -737,20 +759,45 @@ async function start() {
|
|
|
737
759
|
MODEL = config.MODEL || 'deepseek/deepseek-chat-v3-0324:free';
|
|
738
760
|
|
|
739
761
|
OPENROUTER_API_KEY = config.OPENROUTER_API_KEY;
|
|
762
|
+
|
|
740
763
|
if (config.isPro && config.customApiBase) {
|
|
741
764
|
API_BASE_URL = config.customApiBase;
|
|
742
765
|
console.log(`🚀 Using Pro Plan custom endpoint: ${API_BASE_URL}`);
|
|
743
766
|
}
|
|
744
767
|
|
|
745
768
|
ui.showHeader();
|
|
769
|
+
|
|
770
|
+
// Check for multiplayer mode flag
|
|
771
|
+
if (process.argv.includes('--multiplayer')) {
|
|
772
|
+
const multiplayerMode = new MultiplayerMode({
|
|
773
|
+
rl,
|
|
774
|
+
model: MODEL,
|
|
775
|
+
serverUrl: process.env.MP_SERVER_URL || 'ws://localhost:8080'
|
|
776
|
+
});
|
|
777
|
+
await multiplayerMode.start();
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Standard single-agent mode
|
|
746
782
|
console.log('Select Mode:');
|
|
747
783
|
console.log('1. Tool Calling (for models that support it)');
|
|
748
784
|
console.log('2. Function Calling (legacy)');
|
|
785
|
+
console.log('3. Multiplayer Mode (collaborate with other agents)');
|
|
749
786
|
|
|
750
|
-
const
|
|
751
|
-
ui.showResponse(`\nStarting in ${useToolCalling ? 'Tool Calling' : 'Function Calling'} mode...\n`);
|
|
787
|
+
const mode = await askForMode(rl, true);
|
|
752
788
|
|
|
753
|
-
|
|
789
|
+
if (mode === 'multiplayer') {
|
|
790
|
+
const multiplayerMode = new MultiplayerMode({
|
|
791
|
+
rl,
|
|
792
|
+
model: MODEL,
|
|
793
|
+
serverUrl: process.env.MP_SERVER_URL || 'ws://localhost:8080'
|
|
794
|
+
});
|
|
795
|
+
await multiplayerMode.start();
|
|
796
|
+
} else {
|
|
797
|
+
const useToolCalling = mode === 'tool';
|
|
798
|
+
ui.showResponse(`\nStarting in ${useToolCalling ? 'Tool Calling' : 'Function Calling'} mode...\n`);
|
|
799
|
+
await chat(rl, useToolCalling, MODEL);
|
|
800
|
+
}
|
|
754
801
|
} catch (error) {
|
|
755
802
|
ui.showError(error);
|
|
756
803
|
rl.close();
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const WebSocket = require('ws');
|
|
2
|
+
const EventEmitter = require('events');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { v4: uuidv4 } = require('uuid');
|
|
6
|
+
|
|
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
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
connect() {
|
|
26
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
27
|
+
|
|
28
|
+
this.ws.on('open', () => {
|
|
29
|
+
this.emit('connected');
|
|
30
|
+
});
|
|
31
|
+
|
|
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);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.ws.on('close', () => {
|
|
42
|
+
this.emit('disconnected');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.ws.on('error', (error) => {
|
|
46
|
+
this.emit('error', error);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
handleMessage(message) {
|
|
51
|
+
switch (message.type) {
|
|
52
|
+
case 'session_joined':
|
|
53
|
+
this.handleSessionJoined(message);
|
|
54
|
+
break;
|
|
55
|
+
case 'agent_joined':
|
|
56
|
+
this.handleAgentJoined(message);
|
|
57
|
+
break;
|
|
58
|
+
case 'agent_left':
|
|
59
|
+
this.handleAgentLeft(message);
|
|
60
|
+
break;
|
|
61
|
+
case 'chat':
|
|
62
|
+
this.handleChatMessage(message);
|
|
63
|
+
break;
|
|
64
|
+
case 'work_update':
|
|
65
|
+
this.handleWorkUpdate(message);
|
|
66
|
+
break;
|
|
67
|
+
case 'task_assigned':
|
|
68
|
+
this.handleTaskAssigned(message);
|
|
69
|
+
break;
|
|
70
|
+
case 'error':
|
|
71
|
+
this.handleError(message);
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
this.emit('message', message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
handleSessionJoined(message) {
|
|
79
|
+
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
|
+
|
|
88
|
+
this.workHistory = message.workHistory || [];
|
|
89
|
+
this.emit('session_joined', message);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
handleAgentJoined(message) {
|
|
93
|
+
this.clients.set(message.clientId, {
|
|
94
|
+
...message.clientInfo,
|
|
95
|
+
clientId: message.clientId
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (message.clientId !== this.clientId) {
|
|
99
|
+
console.log(chalk.blue(`\n${message.clientInfo.name || 'Agent'} has joined the session`));
|
|
100
|
+
}
|
|
101
|
+
this.emit('agent_joined', message);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
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);
|
|
109
|
+
}
|
|
110
|
+
this.emit('agent_left', message);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
handleChatMessage(message) {
|
|
114
|
+
if (message.clientId === this.clientId) return;
|
|
115
|
+
|
|
116
|
+
const sender = this.clients.get(message.clientId) || { name: 'Unknown' };
|
|
117
|
+
console.log(chalk.cyan(`\n[${sender.name}]: ${message.content}`));
|
|
118
|
+
this.emit('chat', message);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
handleWorkUpdate(message) {
|
|
122
|
+
this.workHistory.push({
|
|
123
|
+
...message.work,
|
|
124
|
+
timestamp: message.timestamp,
|
|
125
|
+
clientId: message.clientId
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const client = this.clients.get(message.clientId) || { name: 'Unknown' };
|
|
129
|
+
console.log(chalk.green(`\n[Work Update] ${client.name}: ${message.work.description}`));
|
|
130
|
+
this.emit('work_updated', message);
|
|
131
|
+
}
|
|
132
|
+
|
|
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
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
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
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
sendChatMessage(content) {
|
|
169
|
+
this.send({
|
|
170
|
+
type: 'chat',
|
|
171
|
+
sessionId: this.sessionId,
|
|
172
|
+
content,
|
|
173
|
+
timestamp: new Date().toISOString()
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
updateWork(work) {
|
|
178
|
+
const workUpdate = {
|
|
179
|
+
id: uuidv4(),
|
|
180
|
+
description: work.description,
|
|
181
|
+
status: work.status || 'in_progress',
|
|
182
|
+
timestamp: new Date().toISOString()
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.send({
|
|
186
|
+
type: 'work_update',
|
|
187
|
+
sessionId: this.sessionId,
|
|
188
|
+
work: workUpdate
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
assignTask(task, assigneeClientId) {
|
|
193
|
+
this.send({
|
|
194
|
+
type: 'task_assigned',
|
|
195
|
+
sessionId: this.sessionId,
|
|
196
|
+
task: {
|
|
197
|
+
...task,
|
|
198
|
+
assignedBy: this.clientId,
|
|
199
|
+
assignedAt: new Date().toISOString()
|
|
200
|
+
},
|
|
201
|
+
assignedTo: assigneeClientId
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
send(message) {
|
|
206
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
207
|
+
this.ws.send(JSON.stringify({
|
|
208
|
+
...message,
|
|
209
|
+
clientId: this.clientId,
|
|
210
|
+
timestamp: new Date().toISOString()
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
disconnect() {
|
|
216
|
+
if (this.ws) {
|
|
217
|
+
this.ws.close();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = MultiplayerClient;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const MultiplayerClient = require('./multiplayer-client');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const { v4: uuidv4 } = require('uuid');
|
|
5
|
+
|
|
6
|
+
class MultiplayerMode {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.rl = options.rl || readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
this.client = new MultiplayerClient({
|
|
14
|
+
rl: this.rl,
|
|
15
|
+
agentName: options.agentName || this.generateAgentName(),
|
|
16
|
+
model: options.model || 'default',
|
|
17
|
+
serverUrl: options.serverUrl
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
this.setupEventListeners();
|
|
21
|
+
this.agentRole = '';
|
|
22
|
+
this.isWorking = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
generateAgentName() {
|
|
26
|
+
const adjectives = ['Swift', 'Clever', 'Wise', 'Agile', 'Smart', 'Quick', 'Bright', 'Sharp'];
|
|
27
|
+
const nouns = ['Coder', 'Thinker', 'Solver', 'Creator', 'Builder', 'Planner', 'Strategist'];
|
|
28
|
+
const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
|
|
29
|
+
const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
|
|
30
|
+
return `${randomAdjective}${randomNoun}${Math.floor(100 + Math.random() * 900)}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setupEventListeners() {
|
|
34
|
+
this.client.on('connected', () => {
|
|
35
|
+
console.log(chalk.green('Connected to multiplayer server'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this.client.on('disconnected', () => {
|
|
39
|
+
console.log(chalk.yellow('Disconnected from multiplayer server'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.client.on('session_joined', (data) => {
|
|
43
|
+
console.log(chalk.green(`\nJoined session: ${data.sessionId}`));
|
|
44
|
+
console.log(chalk.blue(`You are: ${this.client.agentName}`));
|
|
45
|
+
console.log(chalk.blue(`Model: ${this.client.model}`));
|
|
46
|
+
console.log(chalk.blue(`Clients in session: ${Array.from(this.client.clients.values()).map(c => c.name).join(', ')}`));
|
|
47
|
+
|
|
48
|
+
if (this.client.isHost) {
|
|
49
|
+
console.log(chalk.yellow('\nYou are the host. Type /help for available commands.'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.prompt();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
this.client.on('agent_joined', (data) => {
|
|
56
|
+
console.log(chalk.blue(`\n${data.clientInfo.name} joined the session`));
|
|
57
|
+
this.prompt();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.client.on('agent_left', (data) => {
|
|
61
|
+
const client = this.client.clients.get(data.clientId);
|
|
62
|
+
if (client) {
|
|
63
|
+
console.log(chalk.yellow(`\n${client.name} left the session`));
|
|
64
|
+
this.prompt();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.client.on('chat', (message) => {
|
|
69
|
+
// Chat messages are already handled by the client
|
|
70
|
+
this.prompt();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
this.client.on('work_updated', (data) => {
|
|
74
|
+
this.prompt();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.client.on('task_assigned', (task) => {
|
|
78
|
+
console.log(chalk.magenta(`\n[New Task] ${task.description}`));
|
|
79
|
+
this.prompt();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.client.on('error', (error) => {
|
|
83
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
84
|
+
this.prompt();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async start() {
|
|
89
|
+
this.client.connect();
|
|
90
|
+
|
|
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
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
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
|
+
}
|
|
109
|
+
|
|
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
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async handleCommand(command) {
|
|
122
|
+
const [cmd, ...args] = command.slice(1).split(' ');
|
|
123
|
+
|
|
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(' ');
|
|
168
|
+
|
|
169
|
+
// Find client by name
|
|
170
|
+
const targetClient = Array.from(this.client.clients.values())
|
|
171
|
+
.find(c => c.name.toLowerCase() === targetName.toLowerCase());
|
|
172
|
+
|
|
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
|
+
} else {
|
|
180
|
+
console.log(chalk.red(`No agent found with name: ${targetName}`));
|
|
181
|
+
}
|
|
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.'));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.prompt();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
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');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
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++;
|
|
223
|
+
}
|
|
224
|
+
console.log('');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = MultiplayerMode;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const WebSocket = require('ws');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const uuid = require('uuid');
|
|
4
|
+
|
|
5
|
+
class MultiplayerServer {
|
|
6
|
+
constructor(port = 8080) {
|
|
7
|
+
this.port = port;
|
|
8
|
+
this.sessions = new Map();
|
|
9
|
+
this.server = http.createServer();
|
|
10
|
+
this.wss = new WebSocket.Server({ server: this.server });
|
|
11
|
+
this.setupEventHandlers();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setupEventHandlers() {
|
|
15
|
+
this.wss.on('connection', (ws) => {
|
|
16
|
+
let clientId = uuid.v4();
|
|
17
|
+
let sessionId = null;
|
|
18
|
+
let clientInfo = {};
|
|
19
|
+
|
|
20
|
+
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
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
ws.on('close', () => {
|
|
30
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
31
|
+
const session = this.sessions.get(sessionId);
|
|
32
|
+
delete session.clients[clientId];
|
|
33
|
+
this.broadcastToSession(sessionId, {
|
|
34
|
+
type: 'agent_left',
|
|
35
|
+
clientId,
|
|
36
|
+
clientInfo
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (Object.keys(session.clients).length === 0) {
|
|
40
|
+
this.sessions.delete(sessionId);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
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);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
handleCreateSession(ws, clientId, data) {
|
|
70
|
+
const sessionId = uuid.v4();
|
|
71
|
+
this.sessions.set(sessionId, {
|
|
72
|
+
id: sessionId,
|
|
73
|
+
clients: {},
|
|
74
|
+
workHistory: [],
|
|
75
|
+
createdAt: new Date().toISOString()
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.handleJoinSession(ws, clientId, {
|
|
79
|
+
...data,
|
|
80
|
+
sessionId,
|
|
81
|
+
isHost: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
handleJoinSession(ws, clientId, data) {
|
|
86
|
+
const { sessionId, clientInfo } = data;
|
|
87
|
+
|
|
88
|
+
if (!this.sessions.has(sessionId)) {
|
|
89
|
+
ws.send(JSON.stringify({
|
|
90
|
+
type: 'error',
|
|
91
|
+
message: 'Session not found'
|
|
92
|
+
}));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const session = this.sessions.get(sessionId);
|
|
97
|
+
|
|
98
|
+
if (Object.keys(session.clients).length >= 4) {
|
|
99
|
+
ws.send(JSON.stringify({
|
|
100
|
+
type: 'error',
|
|
101
|
+
message: 'Session is full (max 4 agents)'
|
|
102
|
+
}));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add client to session
|
|
107
|
+
session.clients[clientId] = {
|
|
108
|
+
ws,
|
|
109
|
+
clientId,
|
|
110
|
+
clientInfo: {
|
|
111
|
+
...clientInfo,
|
|
112
|
+
joinedAt: new Date().toISOString(),
|
|
113
|
+
isHost: data.isHost || false
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Send welcome message with session info
|
|
118
|
+
ws.send(JSON.stringify({
|
|
119
|
+
type: 'session_joined',
|
|
120
|
+
sessionId,
|
|
121
|
+
clientId,
|
|
122
|
+
clients: Object.values(session.clients).map(c => c.clientInfo),
|
|
123
|
+
workHistory: session.workHistory
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
// Notify other clients
|
|
127
|
+
this.broadcastToSession(sessionId, {
|
|
128
|
+
type: 'agent_joined',
|
|
129
|
+
clientId,
|
|
130
|
+
clientInfo: session.clients[clientId].clientInfo
|
|
131
|
+
}, clientId);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
broadcastToSession(sessionId, message, excludeClientId = null) {
|
|
135
|
+
if (!this.sessions.has(sessionId)) return;
|
|
136
|
+
|
|
137
|
+
const session = this.sessions.get(sessionId);
|
|
138
|
+
const messageStr = JSON.stringify(message);
|
|
139
|
+
|
|
140
|
+
Object.entries(session.clients).forEach(([id, client]) => {
|
|
141
|
+
if (id !== excludeClientId && client.ws.readyState === WebSocket.OPEN) {
|
|
142
|
+
client.ws.send(messageStr);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
start() {
|
|
148
|
+
this.server.listen(this.port, () => {
|
|
149
|
+
console.log(`Multiplayer server running on ws://localhost:${this.port}`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Start server if run directly
|
|
155
|
+
if (require.main === module) {
|
|
156
|
+
const server = new MultiplayerServer();
|
|
157
|
+
server.start();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = MultiplayerServer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sam-coder-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
|
|
5
5
|
"main": "bin/agi-cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"chalk": "^4.1.2",
|
|
23
|
-
"
|
|
23
|
+
"node-fetch": "^2.6.7",
|
|
24
|
+
"ora": "^5.4.1",
|
|
25
|
+
"uuid": "^9.0.0",
|
|
26
|
+
"ws": "^8.18.3"
|
|
24
27
|
},
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"eslint": "^7.27.0"
|