From 28c2143e73f26b1e774d2b9c37841c32f55a3e1d Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Tue, 26 Aug 2025 12:53:56 -0500 Subject: [PATCH 1/8] add comment for port scanning --- settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.js b/settings.js index 13c6cf7..f1454c9 100644 --- a/settings.js +++ b/settings.js @@ -1,7 +1,7 @@ const settings = { "minecraft_version": "auto", // or specific version like "1.21.1" "host": "127.0.0.1", // or "localhost", "your.ip.address.here" - "port": 55916, + "port": 55916, // set to -1 to automatically scan for open ports "auth": "offline", // or "microsoft" // the mindserver manages all agents and hosts the UI From 70c34c79f90f64af389db03bc6be19d19c79d43e Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Tue, 26 Aug 2025 14:21:24 -0500 Subject: [PATCH 2/8] add full listeners for UI, auto open browser --- main.js | 2 +- package.json | 1 + settings.js | 1 + src/agent/library/full_state.js | 89 +++++++++ src/agent/mindserver_proxy.js | 11 ++ src/mindcraft/mindcraft.js | 13 +- src/mindcraft/mindserver.js | 46 ++++- src/mindcraft/public/index.html | 240 +++++++++++++++++++----- src/mindcraft/public/settings_spec.json | 15 +- 9 files changed, 366 insertions(+), 52 deletions(-) create mode 100644 src/agent/library/full_state.js diff --git a/main.js b/main.js index 590348c..cabf295 100644 --- a/main.js +++ b/main.js @@ -63,7 +63,7 @@ if (process.env.LOG_ALL) { settings.log_all_prompts = process.env.LOG_ALL; } -Mindcraft.init(false, settings.mindserver_port); +Mindcraft.init(false, settings.mindserver_port, settings.auto_open_browser); for (let profile of settings.profiles) { const profile_json = JSON.parse(readFileSync(profile, 'utf8')); diff --git a/package.json b/package.json index 106a75c..bf0a7c4 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "mineflayer-pathfinder": "^2.4.5", "mineflayer-pvp": "^1.3.2", "node-canvas-webgl": "PrismarineJS/node-canvas-webgl", + "open": "^10.2.0", "openai": "^4.4.0", "patch-package": "^8.0.0", "prismarine-item": "^1.15.0", diff --git a/settings.js b/settings.js index f1454c9..f5487e3 100644 --- a/settings.js +++ b/settings.js @@ -6,6 +6,7 @@ const settings = { // the mindserver manages all agents and hosts the UI "mindserver_port": 8080, + "auto_open_browser": true, "base_profile": "assistant", // survival, assistant, creative, or god_mode "profiles": [ diff --git a/src/agent/library/full_state.js b/src/agent/library/full_state.js new file mode 100644 index 0000000..c5db85f --- /dev/null +++ b/src/agent/library/full_state.js @@ -0,0 +1,89 @@ +import { + getPosition, + getBiomeName, + getNearbyPlayerNames, + getInventoryCounts, + getNearbyEntityTypes, + getNearbyBlockTypes, + getBlockAtPosition, + getFirstBlockAboveHead +} from "./world.js"; +import convoManager from '../conversation.js'; + +export function getFullState(agent) { + const bot = agent.bot; + + const pos = getPosition(bot); + const position = { + x: Number(pos.x.toFixed(2)), + y: Number(pos.y.toFixed(2)), + z: Number(pos.z.toFixed(2)) + }; + + let weather = 'Clear'; + if (bot.thunderState > 0) weather = 'Thunderstorm'; + else if (bot.rainState > 0) weather = 'Rain'; + + let timeLabel = 'Night'; + if (bot.time.timeOfDay < 6000) timeLabel = 'Morning'; + else if (bot.time.timeOfDay < 12000) timeLabel = 'Afternoon'; + + const below = getBlockAtPosition(bot, 0, -1, 0).name; + const legs = getBlockAtPosition(bot, 0, 0, 0).name; + const head = getBlockAtPosition(bot, 0, 1, 0).name; + + let players = getNearbyPlayerNames(bot); + let bots = convoManager.getInGameAgents().filter(b => b !== agent.name); + players = players.filter(p => !bots.includes(p)); + + const helmet = bot.inventory.slots[5]; + const chestplate = bot.inventory.slots[6]; + const leggings = bot.inventory.slots[7]; + const boots = bot.inventory.slots[8]; + + const state = { + name: agent.name, + gameplay: { + position, + dimension: bot.game.dimension, + gamemode: bot.game.gameMode, + health: Math.round(bot.health), + hunger: Math.round(bot.food), + biome: getBiomeName(bot), + weather, + timeOfDay: bot.time.timeOfDay, + timeLabel + }, + action: { + current: agent.isIdle() ? 'Idle' : agent.actions.currentActionLabel, + isIdle: agent.isIdle() + }, + surroundings: { + below, + legs, + head, + firstBlockAboveHead: getFirstBlockAboveHead(bot, null, 32) + }, + inventory: { + counts: getInventoryCounts(bot), + equipment: { + helmet: helmet ? helmet.name : null, + chestplate: chestplate ? chestplate.name : null, + leggings: leggings ? leggings.name : null, + boots: boots ? boots.name : null, + mainHand: bot.heldItem ? bot.heldItem.name : null + } + }, + nearby: { + humanPlayers: players, + botPlayers: bots, + entityTypes: getNearbyEntityTypes(bot).filter(t => t !== 'player' && t !== 'item'), + blockTypes: getNearbyBlockTypes(bot) + }, + modes: { + summary: bot.modes.getMiniDocs() + } + }; + + return state; +} \ No newline at end of file diff --git a/src/agent/mindserver_proxy.js b/src/agent/mindserver_proxy.js index 1426098..2db78e3 100644 --- a/src/agent/mindserver_proxy.js +++ b/src/agent/mindserver_proxy.js @@ -1,6 +1,7 @@ import { io } from 'socket.io-client'; import convoManager from './conversation.js'; import { setSettings } from './settings.js'; +import { getFullState } from './library/full_state.js'; // agent's individual connection to the mindserver // always connect to localhost @@ -65,6 +66,16 @@ class MindServerProxy { } }); + this.socket.on('get-full-state', (callback) => { + try { + const state = getFullState(this.agent); + callback(state); + } catch (error) { + console.error('Error getting full state:', error); + callback(null); + } + }); + // Request settings and wait for response await new Promise((resolve, reject) => { const timeout = setTimeout(() => { diff --git a/src/mindcraft/mindcraft.js b/src/mindcraft/mindcraft.js index 57c4dfe..640576f 100644 --- a/src/mindcraft/mindcraft.js +++ b/src/mindcraft/mindcraft.js @@ -1,6 +1,7 @@ -import { createMindServer, registerAgent } from './mindserver.js'; +import { createMindServer, registerAgent, numStateListeners } from './mindserver.js'; import { AgentProcess } from '../process/agent_process.js'; import { getServer } from './mcserver.js'; +import open from 'open'; let mindserver; let connected = false; @@ -8,7 +9,7 @@ let agent_processes = {}; let agent_count = 0; let port = 8080; -export async function init(host_public=false, port=8080) { +export async function init(host_public=false, port=8080, auto_open_browser=true) { if (connected) { console.error('Already initiliazed!'); return; @@ -16,6 +17,14 @@ export async function init(host_public=false, port=8080) { mindserver = createMindServer(host_public, port); port = port; connected = true; + if (auto_open_browser) { + setTimeout(() => { + // check if browser listener is already open + if (numStateListeners() === 0) { + open('http://localhost:'+port); + } + }, 2000); + } } export async function createAgent(settings) { diff --git a/src/mindcraft/mindserver.js b/src/mindcraft/mindserver.js index acd0775..7f1748a 100644 --- a/src/mindcraft/mindserver.js +++ b/src/mindcraft/mindserver.js @@ -15,6 +15,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); let io; let server; const agent_connections = {}; +const agent_listeners = []; const settings_spec = JSON.parse(readFileSync(path.join(__dirname, 'public/settings_spec.json'), 'utf8')); @@ -23,8 +24,8 @@ class AgentConnection { this.socket = null; this.settings = settings; this.in_game = false; + this.full_state = null; } - } export function registerAgent(settings) { @@ -114,6 +115,9 @@ export function createMindServer(host_public = false, port = 8080) { agent_connections[curAgentName].in_game = false; agentsUpdate(); } + if (agent_listeners.includes(socket)) { + removeListener(socket); + } }); socket.on('chat-message', (agentName, json) => { @@ -174,6 +178,10 @@ export function createMindServer(host_public = false, port = 8080) { socket.on('bot-output', (agentName, message) => { io.emit('bot-output', agentName, message); }); + + socket.on('listen-to-agents', () => { + addListener(socket); + }); }); let host = host_public ? '0.0.0.0' : 'localhost'; @@ -195,6 +203,42 @@ function agentsUpdate(socket) { socket.emit('agents-update', agents); } + +let listenerInterval = null; +function addListener(listener_socket) { + agent_listeners.push(listener_socket); + if (agent_listeners.length === 1) { + listenerInterval = setInterval(async () => { + const states = {}; + for (let agentName in agent_connections) { + let agent = agent_connections[agentName]; + if (agent.in_game) { + try { + const state = await new Promise((resolve) => { + agent.socket.emit('get-full-state', (s) => resolve(s)); + }); + states[agentName] = state; + } catch (e) { + states[agentName] = { error: String(e) }; + } + } + } + for (let listener of agent_listeners) { + listener.emit('state-update', states); + } + }, 1000); + } +} + +function removeListener(listener_socket) { + agent_listeners.splice(agent_listeners.indexOf(listener_socket), 1); + if (agent_listeners.length === 0) { + clearInterval(listenerInterval); + listenerInterval = null; + } +} + // Optional: export these if you need access to them from other files export const getIO = () => io; export const getServer = () => server; +export const numStateListeners = () => agent_listeners.length; \ No newline at end of file diff --git a/src/mindcraft/public/index.html b/src/mindcraft/public/index.html index d9690a2..3cf7ba4 100644 --- a/src/mindcraft/public/index.html +++ b/src/mindcraft/public/index.html @@ -118,22 +118,115 @@ display: flex; justify-content: flex-start; } + .status-badge { + font-size: 0.75em; + margin-left: 8px; + padding: 2px 6px; + border-radius: 4px; + background: #3a3a3a; + color: #cccccc; + text-transform: lowercase; + } + .status-badge.online { color: #4CAF50; } + .status-badge.offline { color: #f44336; } + .title-row { + display: flex; + align-items: center; + gap: 10px; + } + .title-left { + display: flex; + align-items: center; + gap: 8px; + } + .title-spacer { flex: 1; } + /* Modal styles */ + .modal-backdrop { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0,0,0,0.5); + display: none; + align-items: center; + justify-content: center; + z-index: 1000; + } + .modal { + background: #2d2d2d; + border-radius: 8px; + width: 80vw; + height: 80vh; + display: flex; + flex-direction: column; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0,0,0,0.4); + } + .modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid #3a3a3a; + } + .modal-close-btn { + background: #f44336; + color: #fff; + border: none; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + } + .modal-body { + flex: 1 1 auto; + overflow: auto; + padding: 12px 16px; + } + .modal-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-top: 1px solid #3a3a3a; + } + .footer-left { color: #cccccc; font-style: italic; } -

Mindcraft

+
+
+

Mindcraft

+ mindserver offline +
+
+
-
-

Create Agent

-
-
Profile: Not uploaded
-
- - - +
+ +
+ + + From b7697723bcb825217d80e8bb8afc9042c1ca5f7d Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Wed, 27 Aug 2025 12:19:36 -0500 Subject: [PATCH 5/8] remove agent from ui, smarter viewer logic --- main.js | 2 +- settings.js | 2 +- src/agent/mindserver_proxy.js | 2 +- src/mindcraft/mindcraft.js | 14 ++- src/mindcraft/mindserver.js | 34 +++++-- src/mindcraft/public/index.html | 166 +++++++++++++++++++++++++------- 6 files changed, 167 insertions(+), 53 deletions(-) diff --git a/main.js b/main.js index cabf295..4402cb9 100644 --- a/main.js +++ b/main.js @@ -63,7 +63,7 @@ if (process.env.LOG_ALL) { settings.log_all_prompts = process.env.LOG_ALL; } -Mindcraft.init(false, settings.mindserver_port, settings.auto_open_browser); +Mindcraft.init(false, settings.mindserver_port, settings.auto_open_ui); for (let profile of settings.profiles) { const profile_json = JSON.parse(readFileSync(profile, 'utf8')); diff --git a/settings.js b/settings.js index f5487e3..26c0347 100644 --- a/settings.js +++ b/settings.js @@ -6,7 +6,7 @@ const settings = { // the mindserver manages all agents and hosts the UI "mindserver_port": 8080, - "auto_open_browser": true, + "auto_open_ui": true, "base_profile": "assistant", // survival, assistant, creative, or god_mode "profiles": [ diff --git a/src/agent/mindserver_proxy.js b/src/agent/mindserver_proxy.js index b1043b1..68024a1 100644 --- a/src/agent/mindserver_proxy.js +++ b/src/agent/mindserver_proxy.js @@ -44,7 +44,7 @@ class MindServerProxy { convoManager.receiveFromBot(agentName, json); }); - this.socket.on('agents-update', (agents) => { + this.socket.on('agents-status', (agents) => { this.agents = agents; convoManager.updateAgents(agents); if (this.agent?.task) { diff --git a/src/mindcraft/mindcraft.js b/src/mindcraft/mindcraft.js index 640576f..b797a67 100644 --- a/src/mindcraft/mindcraft.js +++ b/src/mindcraft/mindcraft.js @@ -9,7 +9,7 @@ let agent_processes = {}; let agent_count = 0; let port = 8080; -export async function init(host_public=false, port=8080, auto_open_browser=true) { +export async function init(host_public=false, port=8080, auto_open_ui=true) { if (connected) { console.error('Already initiliazed!'); return; @@ -17,7 +17,7 @@ export async function init(host_public=false, port=8080, auto_open_browser=true) mindserver = createMindServer(host_public, port); port = port; connected = true; - if (auto_open_browser) { + if (auto_open_ui) { setTimeout(() => { // check if browser listener is already open if (numStateListeners() === 0) { @@ -34,7 +34,8 @@ export async function createAgent(settings) { } settings = JSON.parse(JSON.stringify(settings)); let agent_name = settings.profile.name; - registerAgent(settings); + const viewer_port = 3000 + agent_count; + registerAgent(settings, viewer_port); let load_memory = settings.load_memory || false; let init_message = settings.init_message || null; @@ -68,6 +69,13 @@ export function stopAgent(agentName) { } } +export function destroyAgent(agentName) { + if (agent_processes[agentName]) { + agent_processes[agentName].stop(); + delete agent_processes[agentName]; + } +} + export function shutdown() { console.log('Shutting down'); for (let agentName in agent_processes) { diff --git a/src/mindcraft/mindserver.js b/src/mindcraft/mindserver.js index c06e0b5..64af008 100644 --- a/src/mindcraft/mindserver.js +++ b/src/mindcraft/mindserver.js @@ -20,26 +20,27 @@ const agent_listeners = []; const settings_spec = JSON.parse(readFileSync(path.join(__dirname, 'public/settings_spec.json'), 'utf8')); class AgentConnection { - constructor(settings) { + constructor(settings, viewer_port) { this.socket = null; this.settings = settings; this.in_game = false; this.full_state = null; + this.viewer_port = viewer_port; } setSettings(settings) { this.settings = settings; } } -export function registerAgent(settings) { - let agentConnection = new AgentConnection(settings); +export function registerAgent(settings, viewer_port) { + let agentConnection = new AgentConnection(settings, viewer_port); agent_connections[settings.profile.name] = agentConnection; } export function logoutAgent(agentName) { if (agent_connections[agentName]) { agent_connections[agentName].in_game = false; - agentsUpdate(); + agentsStatusUpdate(); } } @@ -58,7 +59,7 @@ export function createMindServer(host_public = false, port = 8080) { let curAgentName = null; console.log('Client connected'); - agentsUpdate(socket); + agentsStatusUpdate(socket); socket.on('create-agent', (settings, callback) => { console.log('API create agent...'); @@ -105,7 +106,7 @@ export function createMindServer(host_public = false, port = 8080) { agent_connections[agentName].socket = socket; agent_connections[agentName].in_game = true; curAgentName = agentName; - agentsUpdate(); + agentsStatusUpdate(); } else { console.warn(`Unregistered agent ${agentName} tried to login`); @@ -116,7 +117,7 @@ export function createMindServer(host_public = false, port = 8080) { if (agent_connections[curAgentName]) { console.log(`Agent ${curAgentName} disconnected`); agent_connections[curAgentName].in_game = false; - agentsUpdate(); + agentsStatusUpdate(); } if (agent_listeners.includes(socket)) { removeListener(socket); @@ -153,6 +154,14 @@ export function createMindServer(host_public = false, port = 8080) { mindcraft.startAgent(agentName); }); + socket.on('destroy-agent', (agentName) => { + if (agent_connections[agentName]) { + mindcraft.destroyAgent(agentName); + delete agent_connections[agentName]; + } + agentsStatusUpdate(); + }); + socket.on('stop-all-agents', () => { console.log('Killing all agents'); for (let agentName in agent_connections) { @@ -202,15 +211,20 @@ export function createMindServer(host_public = false, port = 8080) { return server; } -function agentsUpdate(socket) { +function agentsStatusUpdate(socket) { if (!socket) { socket = io; } let agents = []; for (let agentName in agent_connections) { - agents.push({name: agentName, in_game: agent_connections[agentName].in_game}); + const conn = agent_connections[agentName]; + agents.push({ + name: agentName, + in_game: conn.in_game, + viewerPort: conn.viewer_port + }); }; - socket.emit('agents-update', agents); + socket.emit('agents-status', agents); } diff --git a/src/mindcraft/public/index.html b/src/mindcraft/public/index.html index 6e81b32..a56f498 100644 --- a/src/mindcraft/public/index.html +++ b/src/mindcraft/public/index.html @@ -174,7 +174,7 @@ .controls-row { margin-top: 8px; display: grid; - grid-template-columns: auto 1fr auto auto auto auto; + grid-template-columns: auto minmax(100px, 1fr) repeat(5, auto); gap: 8px; align-items: center; } @@ -383,6 +383,7 @@ let profileData = null; const agentSettings = {}; const agentLastMessage = {}; + let currentAgents = []; const statusEl = document.getElementById('msStatus'); function updateStatus(connected) { @@ -405,6 +406,8 @@ socket.on('connect', () => { updateStatus(true); subscribeToState(); + // Clear all cached settings on reconnect + Object.keys(agentSettings).forEach(name => delete agentSettings[name]); }); socket.on('disconnect', () => { updateStatus(false); @@ -574,6 +577,16 @@ const e = st.inventory.equipment; equippedEl.textContent = `equipped: ${e.mainHand || 'none'}`; } + const armorEl = document.getElementById(`armor-${name}`); + if (armorEl && st.inventory?.equipment) { + const e = st.inventory.equipment; + const armor = []; + if (e.helmet) armor.push(`head: ${e.helmet}`); + if (e.chestplate) armor.push(`chest: ${e.chestplate}`); + if (e.leggings) armor.push(`legs: ${e.leggings}`); + if (e.boots) armor.push(`feet: ${e.boots}`); + armorEl.textContent = `armor: ${armor.length ? armor.join(', ') : 'none'}`; + } if (actionEl && st.action) { actionEl.textContent = `${st.action.current || 'Idle'}`; } @@ -713,7 +726,7 @@ const viewerContainer = agentEl.querySelector('.agent-view-container'); if (!viewerContainer) return; - const agentState = agents.find(a => a.name === name); + const agentState = currentAgents.find(a => a.name === name); const shouldShow = agentState?.in_game && settings?.render_bot_view === true; viewerContainer.parentElement.style.display = shouldShow ? '' : 'none'; } @@ -735,25 +748,14 @@ closeAgentSettingsBtn.addEventListener('click', closeAgentSettings); - async function renderAgents(agents) { - // fetch settings for any new agents - await Promise.all(agents.map(a => fetchAgentSettings(a.name))); - // Update all agent viewers after render - const updateViewers = () => { - agents.forEach(a => { - if (a.in_game) updateAgentViewer(a.name); - }); - }; - - agentsDiv.innerHTML = agents.length ? - // Set timeout to run after DOM update - agents.map((agent, idx) => { - const cfg = agentSettings[agent.name] || {}; - const showViewer = agent.in_game && cfg.render_bot_view === true; - const viewerHTML = showViewer ? `
` : ''; - const lastMessage = agentLastMessage[agent.name] || ''; - return ` + function renderAgentCard(agent) { + const cfg = agentSettings[agent.name] || {}; + const showViewer = agent.in_game && cfg.render_bot_view === true; + const viewerPort = agent.viewerPort; + const viewerHTML = showViewer ? `
` : ''; + const lastMessage = agentLastMessage[agent.name] || ''; + return `
@@ -773,6 +775,7 @@
equipped: -
Last Message: ${lastMessage}
@@ -783,30 +786,118 @@ oninput="onMsgInputChange('${agent.name}')" onkeydown="if(event.key === 'Enter') document.getElementById('sendBtn-${agent.name}').click()" ${!agent.in_game ? 'disabled' : ''}> - - - + + +
`; - }).join('') : - '
No agents connected
'; - - // Update viewers after DOM has updated - setTimeout(updateViewers, 0); } - socket.on('agents-update', async (agents) => { - // Fetch settings for any newly connected agents - const newlyConnected = agents.filter(a => a.in_game && (!agentSettings[a.name] || !agentSettings[a.name].fetched)); - await Promise.all(newlyConnected.map(async (a) => { - const settings = await fetchAgentSettings(a.name); - if (settings) { - agentSettings[a.name] = { ...settings, fetched: true }; + async function renderAgents(agents) { + if (!agents.length) { + agentsDiv.innerHTML = '
No agents connected
'; + return; + } + + // If agentsDiv is empty, do a full render + if (!agentsDiv.children.length) { + agentsDiv.innerHTML = agents.map(agent => renderAgentCard(agent)).join(''); + // Update all viewers after initial render + setTimeout(() => { + agents.forEach(a => { + if (a.in_game) updateAgentViewer(a.name); + }); + }, 0); + return; + } + + // Compare with current agents to find changes + const prevAgents = currentAgents.reduce((acc, a) => ({ ...acc, [a.name]: a }), {}); + const changedAgents = agents.filter(a => { + const prev = prevAgents[a.name]; + return !prev || prev.in_game !== a.in_game || prev.viewerPort !== a.viewerPort; + }); + + // Update only changed agents + changedAgents.forEach(agent => { + const el = document.getElementById(`agent-${agent.name}`); + if (el) { + // Update existing card + el.outerHTML = renderAgentCard(agent); + if (agent.in_game) updateAgentViewer(agent.name); + } else { + // Add new card + agentsDiv.insertAdjacentHTML('beforeend', renderAgentCard(agent)); + if (agent.in_game) updateAgentViewer(agent.name); } - })); - renderAgents(agents); + }); + + // Remove cards for agents that no longer exist + Array.from(agentsDiv.children).forEach(el => { + const name = el.id.replace('agent-', ''); + if (!agents.find(a => a.name === name)) { + el.remove(); + } + }); + } + + socket.on('agents-status', async (agents) => { + // Fetch settings for all agents that don't have current settings + const needSettings = agents.filter(a => !agentSettings[a.name]); + if (needSettings.length > 0) { + await Promise.all(needSettings.map(async (a) => { + const settings = await fetchAgentSettings(a.name); + if (settings) { + agentSettings[a.name] = settings; + } + })); + } + + // Compare with current agents to find changes + const prevAgents = currentAgents.reduce((acc, a) => ({ ...acc, [a.name]: a }), {}); + const changedAgents = agents.filter(a => { + const prev = prevAgents[a.name]; + return !prev || prev.in_game !== a.in_game || prev.viewerPort !== a.viewerPort; + }); + + // Update current agents list + currentAgents = agents; + + // If agentsDiv is empty, do a full render + if (!agentsDiv.children.length) { + agentsDiv.innerHTML = agents.map(agent => renderAgentCard(agent)).join(''); + // Update all viewers after initial render + setTimeout(() => { + agents.forEach(a => { + if (a.in_game) updateAgentViewer(a.name); + }); + }, 0); + return; + } + + // Update only changed agents + changedAgents.forEach(agent => { + const el = document.getElementById(`agent-${agent.name}`); + if (el) { + // Update existing card + el.outerHTML = renderAgentCard(agent); + if (agent.in_game) updateAgentViewer(agent.name); + } else { + // Add new card + agentsDiv.insertAdjacentHTML('beforeend', renderAgentCard(agent)); + if (agent.in_game) updateAgentViewer(agent.name); + } + }); + + // Remove cards for agents that no longer exist + Array.from(agentsDiv.children).forEach(el => { + const name = el.id.replace('agent-', ''); + if (!agents.find(a => a.name === name)) { + el.remove(); + } + }); }); function restartAgent(n) { socket.emit('restart-agent', n); } @@ -820,6 +911,7 @@ socket.emit('start-agent', n); } function stopAgent(n) { socket.emit('stop-agent', n); } + function destroyAgent(n) { socket.emit('destroy-agent', n); } function disconnectAllAgents() { socket.emit('stop-all-agents'); } From 7e3d9c0c85f36e33ee39d2f260d6d5cc10fddc75 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 28 Aug 2025 16:02:05 -0500 Subject: [PATCH 6/8] better connection logic for ui --- settings.js | 2 +- src/agent/mindserver_proxy.js | 1 + src/mindcraft/mindserver.js | 11 ++++++++++- src/mindcraft/public/index.html | 18 ++++++++++++++---- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/settings.js b/settings.js index 26c0347..d09291e 100644 --- a/settings.js +++ b/settings.js @@ -6,7 +6,7 @@ const settings = { // the mindserver manages all agents and hosts the UI "mindserver_port": 8080, - "auto_open_ui": true, + "auto_open_ui": true, // opens UI in browser on startup "base_profile": "assistant", // survival, assistant, creative, or god_mode "profiles": [ diff --git a/src/agent/mindserver_proxy.js b/src/agent/mindserver_proxy.js index 68024a1..a64fe94 100644 --- a/src/agent/mindserver_proxy.js +++ b/src/agent/mindserver_proxy.js @@ -88,6 +88,7 @@ class MindServerProxy { return reject(new Error(response.error)); } setSettings(response.settings); + this.socket.emit('connect-agent-process', name); resolve(); }); }); diff --git a/src/mindcraft/mindserver.js b/src/mindcraft/mindserver.js index 64af008..0ac976a 100644 --- a/src/mindcraft/mindserver.js +++ b/src/mindcraft/mindserver.js @@ -101,6 +101,13 @@ export function createMindServer(host_public = false, port = 8080) { } }); + socket.on('connect-agent-process', (agentName) => { + if (agent_connections[agentName]) { + agent_connections[agentName].socket = socket; + agentsStatusUpdate(); + } + }); + socket.on('login-agent', (agentName) => { if (agent_connections[agentName]) { agent_connections[agentName].socket = socket; @@ -117,6 +124,7 @@ export function createMindServer(host_public = false, port = 8080) { if (agent_connections[curAgentName]) { console.log(`Agent ${curAgentName} disconnected`); agent_connections[curAgentName].in_game = false; + agent_connections[curAgentName].socket = null; agentsStatusUpdate(); } if (agent_listeners.includes(socket)) { @@ -221,7 +229,8 @@ function agentsStatusUpdate(socket) { agents.push({ name: agentName, in_game: conn.in_game, - viewerPort: conn.viewer_port + viewerPort: conn.viewer_port, + socket_connected: !!conn.socket }); }; socket.emit('agents-status', agents); diff --git a/src/mindcraft/public/index.html b/src/mindcraft/public/index.html index a56f498..8a29c89 100644 --- a/src/mindcraft/public/index.html +++ b/src/mindcraft/public/index.html @@ -759,7 +759,7 @@
- ${agent.name} + ${agent.name}${agent.socket_connected && !agent.in_game ? 'joining...' : ''} @@ -789,7 +789,7 @@ - +
`; @@ -817,7 +817,7 @@ const prevAgents = currentAgents.reduce((acc, a) => ({ ...acc, [a.name]: a }), {}); const changedAgents = agents.filter(a => { const prev = prevAgents[a.name]; - return !prev || prev.in_game !== a.in_game || prev.viewerPort !== a.viewerPort; + return !prev || prev.in_game !== a.in_game || prev.viewerPort !== a.viewerPort || prev.socket_connected !== a.socket_connected; }); // Update only changed agents @@ -859,7 +859,7 @@ const prevAgents = currentAgents.reduce((acc, a) => ({ ...acc, [a.name]: a }), {}); const changedAgents = agents.filter(a => { const prev = prevAgents[a.name]; - return !prev || prev.in_game !== a.in_game || prev.viewerPort !== a.viewerPort; + return !prev || prev.in_game !== a.in_game || prev.viewerPort !== a.viewerPort || prev.socket_connected !== a.socket_connected; }); // Update current agents list @@ -907,6 +907,16 @@ if (btn) { btn.textContent = 'Connecting...'; btn.disabled = true; + // Re-enable after 10s if still disabled (agent failed to connect) + setTimeout(() => { + const retryBtn = document.querySelector(`button[onclick=\\"startAgent('${n}')\\"]`); + const agentState = (window.currentAgents || []).find(a => a.name === n); + const stillWaiting = agentState ? (!agentState.in_game && !agentState.socket_connected) : true; + if (retryBtn && stillWaiting) { + retryBtn.disabled = false; + retryBtn.textContent = 'Connect'; + } + }, 10000); } socket.emit('start-agent', n); } From a83c5bb23652fb4c4000e6182bc244e9ec3057db Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Fri, 29 Aug 2025 11:30:38 -0500 Subject: [PATCH 7/8] UI shows error for create-agent --- src/mindcraft/mindcraft.js | 36 ++++++++++++++++++++++++++---------- src/mindcraft/mindserver.js | 12 +++++++++--- src/models/_model_map.js | 2 +- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/mindcraft/mindcraft.js b/src/mindcraft/mindcraft.js index b797a67..a860b87 100644 --- a/src/mindcraft/mindcraft.js +++ b/src/mindcraft/mindcraft.js @@ -23,14 +23,17 @@ export async function init(host_public=false, port=8080, auto_open_ui=true) { if (numStateListeners() === 0) { open('http://localhost:'+port); } - }, 2000); + }, 3000); } } export async function createAgent(settings) { if (!settings.profile.name) { console.error('Agent name is required in profile'); - return; + return { + success: false, + error: 'Agent name is required in profile' + }; } settings = JSON.parse(JSON.stringify(settings)); let agent_name = settings.profile.name; @@ -39,15 +42,28 @@ export async function createAgent(settings) { let load_memory = settings.load_memory || false; let init_message = settings.init_message || null; - const server = await getServer(settings.host, settings.port, settings.minecraft_version); - settings.host = server.host; - settings.port = server.port; - settings.minecraft_version = server.version; + try { + const server = await getServer(settings.host, settings.port, settings.minecraft_version); + settings.host = server.host; + settings.port = server.port; + settings.minecraft_version = server.version; - const agentProcess = new AgentProcess(agent_name, port); - agentProcess.start(load_memory, init_message, agent_count); - agent_count++; - agent_processes[settings.profile.name] = agentProcess; + const agentProcess = new AgentProcess(agent_name, port); + agentProcess.start(load_memory, init_message, agent_count); + agent_count++; + agent_processes[settings.profile.name] = agentProcess; + } catch (error) { + console.error(`Error creating agent ${agent_name}:`, error); + destroyAgent(agent_name); + return { + success: false, + error: error.message + }; + } + return { + success: true, + error: null + }; } export function getAgentProcess(agentName) { diff --git a/src/mindcraft/mindserver.js b/src/mindcraft/mindserver.js index 0ac976a..1397553 100644 --- a/src/mindcraft/mindserver.js +++ b/src/mindcraft/mindserver.js @@ -61,7 +61,7 @@ export function createMindServer(host_public = false, port = 8080) { agentsStatusUpdate(socket); - socket.on('create-agent', (settings, callback) => { + socket.on('create-agent', async (settings, callback) => { console.log('API create agent...'); for (let key in settings_spec) { if (!(key in settings)) { @@ -84,8 +84,14 @@ export function createMindServer(host_public = false, port = 8080) { callback({ success: false, error: 'Agent already exists' }); return; } - mindcraft.createAgent(settings); - callback({ success: true }); + let returned = await mindcraft.createAgent(settings); + callback({ success: returned.success, error: returned.error }); + let name = settings.profile.name; + if (!returned.success && agent_connections[name]) { + mindcraft.destroyAgent(name); + delete agent_connections[name]; + } + agentsStatusUpdate(); } else { console.error('Agent name is required in profile'); diff --git a/src/models/_model_map.js b/src/models/_model_map.js index be43893..c119f07 100644 --- a/src/models/_model_map.js +++ b/src/models/_model_map.js @@ -55,7 +55,7 @@ export function selectAPI(profile) { else if (profile.model.includes('gemini')) profile.api = "google"; else if (profile.model.includes('grok')) - profile.api = 'grok'; + profile.api = 'xai'; else if (profile.model.includes('mistral')) profile.api = 'mistral'; else if (profile.model.includes('deepseek')) From af7bad0c6008f7d0a2e913415e50f60efd124bc5 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Fri, 29 Aug 2025 11:35:31 -0500 Subject: [PATCH 8/8] remove getNearbyBlockTypes --- src/agent/library/full_state.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/agent/library/full_state.js b/src/agent/library/full_state.js index 7323457..45a1fbe 100644 --- a/src/agent/library/full_state.js +++ b/src/agent/library/full_state.js @@ -4,7 +4,6 @@ import { getNearbyPlayerNames, getInventoryCounts, getNearbyEntityTypes, - getNearbyBlockTypes, getBlockAtPosition, getFirstBlockAboveHead } from "./world.js";