mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-29 18:33:03 +02:00
connect main process to mindserver, start/stop/restart controls
This commit is contained in:
parent
b1fd881e65
commit
5278ecb72c
8 changed files with 272 additions and 58 deletions
14
main.js
14
main.js
|
@ -1,8 +1,10 @@
|
|||
import { AgentProcess } from './src/process/agent-process.js';
|
||||
import { AgentProcess } from './src/process/agent_process.js';
|
||||
import settings from './settings.js';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import { createMindServer } from './src/server/mind_server.js';
|
||||
import { mainProxy } from './src/process/main_proxy.js';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
function parseArguments() {
|
||||
return yargs(hideBin(process.argv))
|
||||
|
@ -23,15 +25,19 @@ async function main() {
|
|||
if (settings.host_mindserver) {
|
||||
const mindServer = createMindServer();
|
||||
}
|
||||
|
||||
mainProxy.connect();
|
||||
|
||||
const args = parseArguments();
|
||||
const profiles = getProfiles(args);
|
||||
console.log(profiles);
|
||||
const { load_memory, init_message } = settings;
|
||||
|
||||
for (let i=0; i<profiles.length; i++) {
|
||||
const agent = new AgentProcess();
|
||||
agent.start(profiles[i], load_memory, init_message, i);
|
||||
const agent_process = new AgentProcess();
|
||||
const profile = readFileSync(profiles[i], 'utf8');
|
||||
const agent_json = JSON.parse(profile);
|
||||
mainProxy.registerAgent(agent_json.name, agent_process);
|
||||
agent_process.start(profiles[i], load_memory, init_message, i);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { isOtherAgent, initConversationManager, sendToBot, endAllChats, response
|
|||
import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js';
|
||||
import { addViewer } from './viewer.js';
|
||||
import settings from '../../settings.js';
|
||||
import { serverProxy } from './server_proxy.js';
|
||||
import { serverProxy } from './agent_proxy.js';
|
||||
|
||||
export class Agent {
|
||||
async start(profile_fp, load_mem=false, init_message=null, count_id=0) {
|
||||
|
@ -21,9 +21,6 @@ export class Agent {
|
|||
if (!profile_fp) {
|
||||
throw new Error('No profile filepath provided');
|
||||
}
|
||||
|
||||
// Connect to MindServer via proxy
|
||||
serverProxy.connect();
|
||||
|
||||
console.log('Starting agent initialization with profile:', profile_fp);
|
||||
|
||||
|
@ -47,7 +44,7 @@ export class Agent {
|
|||
console.log('Initializing examples...');
|
||||
await this.prompter.initExamples();
|
||||
|
||||
serverProxy.registerAgent(this.name);
|
||||
serverProxy.connect(this);
|
||||
|
||||
console.log(this.name, 'logging into minecraft...');
|
||||
this.bot = initBot(this.name);
|
||||
|
@ -61,6 +58,8 @@ export class Agent {
|
|||
|
||||
this.bot.on('login', () => {
|
||||
console.log(this.name, 'logged in!');
|
||||
|
||||
serverProxy.login();
|
||||
|
||||
// Set skin for profile, requires Fabric Tailor. (https://modrinth.com/mod/fabrictailor)
|
||||
if (this.prompter.profile.skin)
|
||||
|
@ -113,6 +112,7 @@ export class Agent {
|
|||
|
||||
const respondFunc = async (username, message) => {
|
||||
if (username === this.name) return;
|
||||
if (settings.only_chat_with.length > 0 && !settings.only_chat_with.includes(username)) return;
|
||||
try {
|
||||
if (ignore_messages.some((m) => message.startsWith(m))) return;
|
||||
|
||||
|
@ -159,7 +159,7 @@ export class Agent {
|
|||
}
|
||||
else {
|
||||
const translation = await handleTranslation("Hello world! I am "+this.name);
|
||||
this.bot.chat(translation);
|
||||
this.openChat(translation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,10 +204,10 @@ export class Agent {
|
|||
const user_command_name = containsCommand(message);
|
||||
if (user_command_name) {
|
||||
if (!commandExists(user_command_name)) {
|
||||
this.bot.chat(`Command '${user_command_name}' does not exist.`);
|
||||
this.routeResponse(source, `Command '${user_command_name}' does not exist.`);
|
||||
return false;
|
||||
}
|
||||
this.bot.chat(`*${source} used ${user_command_name.substring(1)}*`);
|
||||
this.routeResponse(source, `*${source} used ${user_command_name.substring(1)}*`);
|
||||
if (user_command_name === '!newAction') {
|
||||
// all user-initiated commands are ignored by the bot except for this one
|
||||
// add the preceding message to the history to give context for newAction
|
||||
|
@ -325,11 +325,22 @@ export class Agent {
|
|||
message = message.replaceAll('\n', ' ');
|
||||
|
||||
if (self_prompt)
|
||||
this.bot.chat(message);
|
||||
this.openChat(message);
|
||||
else
|
||||
this.bot.whisper(to_player, message);
|
||||
}
|
||||
|
||||
openChat(message) {
|
||||
if (settings.only_chat_with.length > 0) {
|
||||
for (let username of settings.only_chat_with) {
|
||||
this.bot.whisper(username, message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.bot.chat(message);
|
||||
}
|
||||
}
|
||||
|
||||
startEvents() {
|
||||
// Custom events
|
||||
this.bot.on('time', () => {
|
||||
|
@ -421,7 +432,7 @@ export class Agent {
|
|||
|
||||
cleanKill(msg='Killing agent process...') {
|
||||
this.history.add('system', msg);
|
||||
this.bot.chat('Restarting.')
|
||||
this.openChat('Restarting.');
|
||||
this.history.save();
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
@ -2,20 +2,22 @@ import { io } from 'socket.io-client';
|
|||
import { recieveFromBot, updateAgents } from './conversation.js';
|
||||
import settings from '../../settings.js';
|
||||
|
||||
class ServerProxy {
|
||||
class AgentServerProxy {
|
||||
constructor() {
|
||||
if (ServerProxy.instance) {
|
||||
return ServerProxy.instance;
|
||||
if (AgentServerProxy.instance) {
|
||||
return AgentServerProxy.instance;
|
||||
}
|
||||
|
||||
this.socket = null;
|
||||
this.connected = false;
|
||||
ServerProxy.instance = this;
|
||||
AgentServerProxy.instance = this;
|
||||
}
|
||||
|
||||
connect() {
|
||||
connect(agent) {
|
||||
if (this.connected) return;
|
||||
|
||||
this.agent = agent;
|
||||
|
||||
this.socket = io(`http://${settings.mindserver_host}:${settings.mindserver_port}`);
|
||||
this.connected = true;
|
||||
|
||||
|
@ -35,14 +37,15 @@ class ServerProxy {
|
|||
this.socket.on('agents-update', (agents) => {
|
||||
updateAgents(agents);
|
||||
});
|
||||
|
||||
this.socket.on('restart-agent', (agentName) => {
|
||||
console.log(`Restarting agent: ${agentName}`);
|
||||
this.agent.cleanKill();
|
||||
});
|
||||
}
|
||||
|
||||
registerAgent(agentName) {
|
||||
if (!this.connected) {
|
||||
console.warn('Cannot register agent: not connected to MindServer');
|
||||
return;
|
||||
}
|
||||
this.socket.emit('register-agent', agentName);
|
||||
login() {
|
||||
this.socket.emit('login-agent', this.agent.name);
|
||||
}
|
||||
|
||||
getSocket() {
|
||||
|
@ -51,7 +54,7 @@ class ServerProxy {
|
|||
}
|
||||
|
||||
// Create and export a singleton instance
|
||||
export const serverProxy = new ServerProxy();
|
||||
export const serverProxy = new AgentServerProxy();
|
||||
|
||||
export function sendBotChatToServer(agentName, json) {
|
||||
serverProxy.getSocket().emit('chat-message', agentName, json);
|
|
@ -1,10 +1,13 @@
|
|||
import { spawn } from 'child_process';
|
||||
import { mainProxy } from './main_proxy.js';
|
||||
|
||||
export class AgentProcess {
|
||||
static runningCount = 0;
|
||||
|
||||
start(profile, load_memory=false, init_message=null, count_id=0) {
|
||||
let args = ['src/process/init-agent.js', this.name];
|
||||
this.profile = profile;
|
||||
this.count_id = count_id;
|
||||
this.running = true;
|
||||
|
||||
let args = ['src/process/init_agent.js', this.name];
|
||||
args.push('-p', profile);
|
||||
args.push('-c', count_id);
|
||||
if (load_memory)
|
||||
|
@ -16,21 +19,17 @@ export class AgentProcess {
|
|||
stdio: 'inherit',
|
||||
stderr: 'inherit',
|
||||
});
|
||||
AgentProcess.runningCount++;
|
||||
|
||||
let last_restart = Date.now();
|
||||
agentProcess.on('exit', (code, signal) => {
|
||||
console.log(`Agent process exited with code ${code} and signal ${signal}`);
|
||||
this.running = false;
|
||||
mainProxy.logoutAgent(this.name);
|
||||
|
||||
if (code !== 0) {
|
||||
if (code !== 0 && signal !== 'SIGINT') {
|
||||
// agent must run for at least 10 seconds before restarting
|
||||
if (Date.now() - last_restart < 10000) {
|
||||
console.error(`Agent process ${profile} exited too quickly and will not be restarted.`);
|
||||
AgentProcess.runningCount--;
|
||||
if (AgentProcess.runningCount <= 0) {
|
||||
console.error('All agent processes have ended. Exiting.');
|
||||
process.exit(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.log('Restarting agent...');
|
||||
|
@ -42,5 +41,18 @@ export class AgentProcess {
|
|||
agentProcess.on('error', (err) => {
|
||||
console.error('Agent process error:', err);
|
||||
});
|
||||
|
||||
this.process = agentProcess;
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.running) return;
|
||||
this.process.kill('SIGINT');
|
||||
}
|
||||
|
||||
continue() {
|
||||
if (!this.running) {
|
||||
this.start(this.profile, true, 'Agent process restarted.', this.count_id);
|
||||
}
|
||||
}
|
||||
}
|
54
src/process/main_proxy.js
Normal file
54
src/process/main_proxy.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { io } from 'socket.io-client';
|
||||
import settings from '../../settings.js';
|
||||
|
||||
// Singleton mindserver proxy for the main process
|
||||
class MainProxy {
|
||||
constructor() {
|
||||
if (MainProxy.instance) {
|
||||
return MainProxy.instance;
|
||||
}
|
||||
|
||||
this.socket = null;
|
||||
this.connected = false;
|
||||
this.agent_processes = {};
|
||||
MainProxy.instance = this;
|
||||
}
|
||||
|
||||
connect() {
|
||||
if (this.connected) return;
|
||||
|
||||
this.socket = io(`http://${settings.mindserver_host}:${settings.mindserver_port}`);
|
||||
this.connected = true;
|
||||
|
||||
this.socket.on('stop-agent', (agentName) => {
|
||||
if (this.agent_processes[agentName]) {
|
||||
this.agent_processes[agentName].stop();
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('start-agent', (agentName) => {
|
||||
if (this.agent_processes[agentName]) {
|
||||
this.agent_processes[agentName].continue();
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('register-agents-success', () => {
|
||||
console.log('Agents registered');
|
||||
});
|
||||
}
|
||||
|
||||
addAgent(agent) {
|
||||
this.agent_processes.push(agent);
|
||||
}
|
||||
|
||||
logoutAgent(agentName) {
|
||||
this.socket.emit('logout-agent', agentName);
|
||||
}
|
||||
|
||||
registerAgent(name, process) {
|
||||
this.socket.emit('register-agents', [name]);
|
||||
this.agent_processes[name] = process;
|
||||
}
|
||||
}
|
||||
|
||||
export const mainProxy = new MainProxy();
|
|
@ -7,7 +7,9 @@ import { fileURLToPath } from 'url';
|
|||
// Module-level variables
|
||||
let io;
|
||||
let server;
|
||||
const connectedAgents = {};
|
||||
const registeredAgents = new Set();
|
||||
const inGameAgents = {};
|
||||
const agentManagers = {}; // socket for main process that registers/controls agents
|
||||
|
||||
// Initialize the server
|
||||
export function createMindServer(port = 8080) {
|
||||
|
@ -24,28 +26,81 @@ export function createMindServer(port = 8080) {
|
|||
let curAgentName = null;
|
||||
console.log('Client connected');
|
||||
|
||||
socket.emit('agents-update', Object.keys(connectedAgents));
|
||||
agentsUpdate(socket);
|
||||
|
||||
socket.on('register-agent', (agentName) => {
|
||||
console.log('Agent registered:', agentName);
|
||||
connectedAgents[agentName] = socket;
|
||||
curAgentName = agentName;
|
||||
io.emit('agents-update', Object.keys(connectedAgents));
|
||||
socket.on('register-agents', (agentNames) => {
|
||||
console.log(`Registering agents: ${agentNames}`);
|
||||
agentNames.forEach(name => registeredAgents.add(name));
|
||||
for (let name of agentNames) {
|
||||
agentManagers[name] = socket;
|
||||
}
|
||||
socket.emit('register-agents-success');
|
||||
agentsUpdate();
|
||||
});
|
||||
|
||||
socket.on('chat-message', (agentName, json) => {
|
||||
console.log(`${curAgentName} received message from ${agentName}: ${json}`);
|
||||
const agentSocket = connectedAgents[agentName];
|
||||
if (agentSocket) {
|
||||
agentSocket.emit('chat-message', curAgentName, json);
|
||||
socket.on('login-agent', (agentName) => {
|
||||
if (curAgentName && curAgentName !== agentName) {
|
||||
console.warn(`Agent ${agentName} already logged in as ${curAgentName}`);
|
||||
return;
|
||||
}
|
||||
if (registeredAgents.has(agentName)) {
|
||||
curAgentName = agentName;
|
||||
inGameAgents[agentName] = socket;
|
||||
agentsUpdate();
|
||||
} else {
|
||||
console.warn(`Agent ${agentName} not registered`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('logout-agent', (agentName) => {
|
||||
if (inGameAgents[agentName]) {
|
||||
delete inGameAgents[agentName];
|
||||
agentsUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('Client disconnected');
|
||||
delete connectedAgents[socket.id];
|
||||
io.emit('agents-update', Object.keys(connectedAgents));
|
||||
if (inGameAgents[curAgentName]) {
|
||||
delete inGameAgents[curAgentName];
|
||||
agentsUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('chat-message', (agentName, json) => {
|
||||
if (!inGameAgents[agentName]) {
|
||||
console.warn(`Agent ${agentName} tried to send a message but is not logged in`);
|
||||
return;
|
||||
}
|
||||
console.log(`${curAgentName} received message from ${agentName}: ${json}`);
|
||||
inGameAgents[agentName].emit('chat-message', curAgentName, json);
|
||||
});
|
||||
|
||||
socket.on('restart-agent', (agentName) => {
|
||||
console.log(`Restarting agent: ${agentName}`);
|
||||
inGameAgents[agentName].emit('restart-agent');
|
||||
});
|
||||
|
||||
socket.on('stop-agent', (agentName) => {
|
||||
let manager = agentManagers[agentName];
|
||||
if (manager) {
|
||||
manager.emit('stop-agent', agentName);
|
||||
}
|
||||
else {
|
||||
console.warn(`Stopping unregisterd agent ${agentName}`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('start-agent', (agentName) => {
|
||||
let manager = agentManagers[agentName];
|
||||
if (manager) {
|
||||
manager.emit('start-agent', agentName);
|
||||
}
|
||||
else {
|
||||
console.warn(`Starting unregisterd agent ${agentName}`);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
server.listen(port, 'localhost', () => {
|
||||
|
@ -54,6 +109,18 @@ export function createMindServer(port = 8080) {
|
|||
|
||||
return server;
|
||||
}
|
||||
|
||||
function agentsUpdate(socket) {
|
||||
if (!socket) {
|
||||
socket = io;
|
||||
}
|
||||
let agents = [];
|
||||
registeredAgents.forEach(name => {
|
||||
agents.push({name, in_game: !!inGameAgents[name]});
|
||||
});
|
||||
socket.emit('agents-update', agents);
|
||||
}
|
||||
|
||||
// Optional: export these if you need access to them from other files
|
||||
export const getIO = () => io;
|
||||
export const getServer = () => server;
|
||||
|
|
|
@ -1,33 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mindcraft Agents</title>
|
||||
<title>Mindcraft</title>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background: #f0f0f0;
|
||||
background: #1a1a1a;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
#agents {
|
||||
background: white;
|
||||
background: #2d2d2d;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
color: #ffffff;
|
||||
}
|
||||
.agent {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: #f8f8f8;
|
||||
background: #363636;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.restart-btn, .start-btn, .stop-btn {
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.restart-btn {
|
||||
background: #4CAF50;
|
||||
}
|
||||
.start-btn {
|
||||
background: #2196F3;
|
||||
}
|
||||
.stop-btn {
|
||||
background: #f44336;
|
||||
}
|
||||
.restart-btn:hover { background: #45a049; }
|
||||
.start-btn:hover { background: #1976D2; }
|
||||
.stop-btn:hover { background: #d32f2f; }
|
||||
.status-icon {
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.status-icon.online {
|
||||
color: #4CAF50;
|
||||
}
|
||||
.status-icon.offline {
|
||||
color: #f44336;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Connected Mindcraft Agents</h1>
|
||||
<h1>Mindcraft</h1>
|
||||
<div id="agents"></div>
|
||||
|
||||
<script>
|
||||
|
@ -36,9 +70,36 @@
|
|||
|
||||
socket.on('agents-update', (agents) => {
|
||||
agentsDiv.innerHTML = agents.length ?
|
||||
agents.map(name => `<div class="agent">${name}</div>`).join('') :
|
||||
agents.map(agent => `
|
||||
<div class="agent">
|
||||
<span>
|
||||
<span class="status-icon ${agent.in_game ? 'online' : 'offline'}">●</span>
|
||||
${agent.name}
|
||||
</span>
|
||||
<div>
|
||||
${agent.in_game ? `
|
||||
<button class="stop-btn" onclick="stopAgent('${agent.name}')">Stop</button>
|
||||
<button class="restart-btn" onclick="restartAgent('${agent.name}')">Restart</button>
|
||||
` : `
|
||||
<button class="start-btn" onclick="startAgent('${agent.name}')">Start</button>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
`).join('') :
|
||||
'<div class="agent">No agents connected</div>';
|
||||
});
|
||||
|
||||
function restartAgent(agentName) {
|
||||
socket.emit('restart-agent', agentName);
|
||||
}
|
||||
|
||||
function startAgent(agentName) {
|
||||
socket.emit('start-agent', agentName);
|
||||
}
|
||||
|
||||
function stopAgent(agentName) {
|
||||
socket.emit('stop-agent', agentName);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue