From 5278ecb72c04d81180c9fe9e9f5c9fd6484b7699 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sun, 1 Dec 2024 22:27:42 -0600 Subject: [PATCH] connect main process to mindserver, start/stop/restart controls --- main.js | 14 ++- src/agent/agent.js | 31 ++++-- src/agent/{server_proxy.js => agent_proxy.js} | 27 +++--- .../{agent-process.js => agent_process.js} | 32 +++++-- src/process/{init-agent.js => init_agent.js} | 0 src/process/main_proxy.js | 54 +++++++++++ src/server/mind_server.js | 95 ++++++++++++++++--- src/server/public/index.html | 77 +++++++++++++-- 8 files changed, 272 insertions(+), 58 deletions(-) rename src/agent/{server_proxy.js => agent_proxy.js} (69%) rename src/process/{agent-process.js => agent_process.js} (67%) rename src/process/{init-agent.js => init_agent.js} (100%) create mode 100644 src/process/main_proxy.js diff --git a/main.js b/main.js index 0e80a19..a344bc8 100644 --- a/main.js +++ b/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 setTimeout(resolve, 1000)); } } diff --git a/src/agent/agent.js b/src/agent/agent.js index 28c53fb..7e82104 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -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); } diff --git a/src/agent/server_proxy.js b/src/agent/agent_proxy.js similarity index 69% rename from src/agent/server_proxy.js rename to src/agent/agent_proxy.js index 8520754..81c5d0f 100644 --- a/src/agent/server_proxy.js +++ b/src/agent/agent_proxy.js @@ -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); diff --git a/src/process/agent-process.js b/src/process/agent_process.js similarity index 67% rename from src/process/agent-process.js rename to src/process/agent_process.js index 5135de1..83af427 100644 --- a/src/process/agent-process.js +++ b/src/process/agent_process.js @@ -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); + } } } \ No newline at end of file diff --git a/src/process/init-agent.js b/src/process/init_agent.js similarity index 100% rename from src/process/init-agent.js rename to src/process/init_agent.js diff --git a/src/process/main_proxy.js b/src/process/main_proxy.js new file mode 100644 index 0000000..44c2733 --- /dev/null +++ b/src/process/main_proxy.js @@ -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(); \ No newline at end of file diff --git a/src/server/mind_server.js b/src/server/mind_server.js index 1035424..9235fcc 100644 --- a/src/server/mind_server.js +++ b/src/server/mind_server.js @@ -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; diff --git a/src/server/public/index.html b/src/server/public/index.html index 52dcd1b..b597ed9 100644 --- a/src/server/public/index.html +++ b/src/server/public/index.html @@ -1,33 +1,67 @@ - Mindcraft Agents + Mindcraft -

Connected Mindcraft Agents

+

Mindcraft

\ No newline at end of file