remove agent from ui, smarter viewer logic

This commit is contained in:
MaxRobinsonTheGreat 2025-08-27 12:19:36 -05:00
parent 69642d15fd
commit b7697723bc
6 changed files with 167 additions and 53 deletions

View file

@ -63,7 +63,7 @@ if (process.env.LOG_ALL) {
settings.log_all_prompts = 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) { for (let profile of settings.profiles) {
const profile_json = JSON.parse(readFileSync(profile, 'utf8')); const profile_json = JSON.parse(readFileSync(profile, 'utf8'));

View file

@ -6,7 +6,7 @@ const settings = {
// the mindserver manages all agents and hosts the UI // the mindserver manages all agents and hosts the UI
"mindserver_port": 8080, "mindserver_port": 8080,
"auto_open_browser": true, "auto_open_ui": true,
"base_profile": "assistant", // survival, assistant, creative, or god_mode "base_profile": "assistant", // survival, assistant, creative, or god_mode
"profiles": [ "profiles": [

View file

@ -44,7 +44,7 @@ class MindServerProxy {
convoManager.receiveFromBot(agentName, json); convoManager.receiveFromBot(agentName, json);
}); });
this.socket.on('agents-update', (agents) => { this.socket.on('agents-status', (agents) => {
this.agents = agents; this.agents = agents;
convoManager.updateAgents(agents); convoManager.updateAgents(agents);
if (this.agent?.task) { if (this.agent?.task) {

View file

@ -9,7 +9,7 @@ let agent_processes = {};
let agent_count = 0; let agent_count = 0;
let port = 8080; 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) { if (connected) {
console.error('Already initiliazed!'); console.error('Already initiliazed!');
return; return;
@ -17,7 +17,7 @@ export async function init(host_public=false, port=8080, auto_open_browser=true)
mindserver = createMindServer(host_public, port); mindserver = createMindServer(host_public, port);
port = port; port = port;
connected = true; connected = true;
if (auto_open_browser) { if (auto_open_ui) {
setTimeout(() => { setTimeout(() => {
// check if browser listener is already open // check if browser listener is already open
if (numStateListeners() === 0) { if (numStateListeners() === 0) {
@ -34,7 +34,8 @@ export async function createAgent(settings) {
} }
settings = JSON.parse(JSON.stringify(settings)); settings = JSON.parse(JSON.stringify(settings));
let agent_name = settings.profile.name; 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 load_memory = settings.load_memory || false;
let init_message = settings.init_message || null; 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() { export function shutdown() {
console.log('Shutting down'); console.log('Shutting down');
for (let agentName in agent_processes) { for (let agentName in agent_processes) {

View file

@ -20,26 +20,27 @@ const agent_listeners = [];
const settings_spec = JSON.parse(readFileSync(path.join(__dirname, 'public/settings_spec.json'), 'utf8')); const settings_spec = JSON.parse(readFileSync(path.join(__dirname, 'public/settings_spec.json'), 'utf8'));
class AgentConnection { class AgentConnection {
constructor(settings) { constructor(settings, viewer_port) {
this.socket = null; this.socket = null;
this.settings = settings; this.settings = settings;
this.in_game = false; this.in_game = false;
this.full_state = null; this.full_state = null;
this.viewer_port = viewer_port;
} }
setSettings(settings) { setSettings(settings) {
this.settings = settings; this.settings = settings;
} }
} }
export function registerAgent(settings) { export function registerAgent(settings, viewer_port) {
let agentConnection = new AgentConnection(settings); let agentConnection = new AgentConnection(settings, viewer_port);
agent_connections[settings.profile.name] = agentConnection; agent_connections[settings.profile.name] = agentConnection;
} }
export function logoutAgent(agentName) { export function logoutAgent(agentName) {
if (agent_connections[agentName]) { if (agent_connections[agentName]) {
agent_connections[agentName].in_game = false; agent_connections[agentName].in_game = false;
agentsUpdate(); agentsStatusUpdate();
} }
} }
@ -58,7 +59,7 @@ export function createMindServer(host_public = false, port = 8080) {
let curAgentName = null; let curAgentName = null;
console.log('Client connected'); console.log('Client connected');
agentsUpdate(socket); agentsStatusUpdate(socket);
socket.on('create-agent', (settings, callback) => { socket.on('create-agent', (settings, callback) => {
console.log('API create agent...'); 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].socket = socket;
agent_connections[agentName].in_game = true; agent_connections[agentName].in_game = true;
curAgentName = agentName; curAgentName = agentName;
agentsUpdate(); agentsStatusUpdate();
} }
else { else {
console.warn(`Unregistered agent ${agentName} tried to login`); console.warn(`Unregistered agent ${agentName} tried to login`);
@ -116,7 +117,7 @@ export function createMindServer(host_public = false, port = 8080) {
if (agent_connections[curAgentName]) { if (agent_connections[curAgentName]) {
console.log(`Agent ${curAgentName} disconnected`); console.log(`Agent ${curAgentName} disconnected`);
agent_connections[curAgentName].in_game = false; agent_connections[curAgentName].in_game = false;
agentsUpdate(); agentsStatusUpdate();
} }
if (agent_listeners.includes(socket)) { if (agent_listeners.includes(socket)) {
removeListener(socket); removeListener(socket);
@ -153,6 +154,14 @@ export function createMindServer(host_public = false, port = 8080) {
mindcraft.startAgent(agentName); 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', () => { socket.on('stop-all-agents', () => {
console.log('Killing all agents'); console.log('Killing all agents');
for (let agentName in agent_connections) { for (let agentName in agent_connections) {
@ -202,15 +211,20 @@ export function createMindServer(host_public = false, port = 8080) {
return server; return server;
} }
function agentsUpdate(socket) { function agentsStatusUpdate(socket) {
if (!socket) { if (!socket) {
socket = io; socket = io;
} }
let agents = []; let agents = [];
for (let agentName in agent_connections) { 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);
} }

View file

@ -174,7 +174,7 @@
.controls-row { .controls-row {
margin-top: 8px; margin-top: 8px;
display: grid; display: grid;
grid-template-columns: auto 1fr auto auto auto auto; grid-template-columns: auto minmax(100px, 1fr) repeat(5, auto);
gap: 8px; gap: 8px;
align-items: center; align-items: center;
} }
@ -383,6 +383,7 @@
let profileData = null; let profileData = null;
const agentSettings = {}; const agentSettings = {};
const agentLastMessage = {}; const agentLastMessage = {};
let currentAgents = [];
const statusEl = document.getElementById('msStatus'); const statusEl = document.getElementById('msStatus');
function updateStatus(connected) { function updateStatus(connected) {
@ -405,6 +406,8 @@
socket.on('connect', () => { socket.on('connect', () => {
updateStatus(true); updateStatus(true);
subscribeToState(); subscribeToState();
// Clear all cached settings on reconnect
Object.keys(agentSettings).forEach(name => delete agentSettings[name]);
}); });
socket.on('disconnect', () => { socket.on('disconnect', () => {
updateStatus(false); updateStatus(false);
@ -574,6 +577,16 @@
const e = st.inventory.equipment; const e = st.inventory.equipment;
equippedEl.textContent = `equipped: ${e.mainHand || 'none'}`; 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) { if (actionEl && st.action) {
actionEl.textContent = `${st.action.current || 'Idle'}`; actionEl.textContent = `${st.action.current || 'Idle'}`;
} }
@ -713,7 +726,7 @@
const viewerContainer = agentEl.querySelector('.agent-view-container'); const viewerContainer = agentEl.querySelector('.agent-view-container');
if (!viewerContainer) return; 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; const shouldShow = agentState?.in_game && settings?.render_bot_view === true;
viewerContainer.parentElement.style.display = shouldShow ? '' : 'none'; viewerContainer.parentElement.style.display = shouldShow ? '' : 'none';
} }
@ -735,23 +748,12 @@
closeAgentSettingsBtn.addEventListener('click', closeAgentSettings); 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 function renderAgentCard(agent) {
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 cfg = agentSettings[agent.name] || {};
const showViewer = agent.in_game && cfg.render_bot_view === true; const showViewer = agent.in_game && cfg.render_bot_view === true;
const viewerHTML = showViewer ? `<div class="agent-view-container"><iframe class="agent-viewer" src="http://localhost:${3000 + idx}"></iframe></div>` : ''; const viewerPort = agent.viewerPort;
const viewerHTML = showViewer ? `<div class="agent-view-container"><iframe class="agent-viewer" id="viewer-${agent.name}" src="http://localhost:${viewerPort}"></iframe></div>` : '';
const lastMessage = agentLastMessage[agent.name] || ''; const lastMessage = agentLastMessage[agent.name] || '';
return ` return `
<div class="agent" id="agent-${agent.name}"> <div class="agent" id="agent-${agent.name}">
@ -773,6 +775,7 @@
<div class="cell" id="equipped-${agent.name}">equipped: -</div> <div class="cell" id="equipped-${agent.name}">equipped: -</div>
<div class="agent-inventory" id="inventorySection-${agent.name}" style="display:none; grid-column: 1 / -1;"> <div class="agent-inventory" id="inventorySection-${agent.name}" style="display:none; grid-column: 1 / -1;">
<h3>Inventory</h3> <h3>Inventory</h3>
<div class="cell" id="armor-${agent.name}" style="margin-bottom: 8px;">armor: -</div>
<div class="inventory-grid" id="inventory-${agent.name}"></div> <div class="inventory-grid" id="inventory-${agent.name}"></div>
</div> </div>
<div id="lastMessage-${agent.name}" class="last-message" style="grid-column: 1 / -1;"><strong>Last Message:</strong> ${lastMessage}</div> <div id="lastMessage-${agent.name}" class="last-message" style="grid-column: 1 / -1;"><strong>Last Message:</strong> ${lastMessage}</div>
@ -783,30 +786,118 @@
oninput="onMsgInputChange('${agent.name}')" oninput="onMsgInputChange('${agent.name}')"
onkeydown="if(event.key === 'Enter') document.getElementById('sendBtn-${agent.name}').click()" onkeydown="if(event.key === 'Enter') document.getElementById('sendBtn-${agent.name}').click()"
${!agent.in_game ? 'disabled' : ''}> ${!agent.in_game ? 'disabled' : ''}>
<button class="neutral-btn" onclick="startAgent('${agent.name}')" style="display: ${agent.in_game ? 'none' : ''}">Connect</button>
<button class="neutral-btn" onclick="disconnectAgent('${agent.name}')" style="display: ${agent.in_game ? '' : 'none'}">Disconnect</button>
<button class="neutral-btn" onclick="restartAgent('${agent.name}')" ${!agent.in_game ? 'disabled' : ''}>Restart</button>
<button class="neutral-btn" onclick="sendMessage('${agent.name}', '!stop')" ${!agent.in_game ? 'disabled' : ''}>Stop Action</button> <button class="neutral-btn" onclick="sendMessage('${agent.name}', '!stop')" ${!agent.in_game ? 'disabled' : ''}>Stop Action</button>
<button class="neutral-btn" onclick="sendMessage('${agent.name}', '!stay(-1)')" ${!agent.in_game ? 'disabled' : ''}>Stay Still</button> <button class="neutral-btn" onclick="sendMessage('${agent.name}', '!stay(-1)')" ${!agent.in_game ? 'disabled' : ''}>Stay Still</button>
<button class="neutral-btn" onclick="restartAgent('${agent.name}')" ${!agent.in_game ? 'disabled' : ''}>Restart</button>
<button class="neutral-btn" onclick="${agent.in_game ? 'disconnectAgent' : 'startAgent'}('${agent.name}')">${agent.in_game ? 'Disconnect' : 'Connect'}</button>
<button class="stop-btn" onclick="destroyAgent('${agent.name}')">Remove</button>
</div> </div>
</div>`; </div>`;
}).join('') :
'<div class="agent">No agents connected</div>';
// Update viewers after DOM has updated
setTimeout(updateViewers, 0);
} }
socket.on('agents-update', async (agents) => { async function renderAgents(agents) {
// Fetch settings for any newly connected agents if (!agents.length) {
const newlyConnected = agents.filter(a => a.in_game && (!agentSettings[a.name] || !agentSettings[a.name].fetched)); agentsDiv.innerHTML = '<div class="agent">No agents connected</div>';
await Promise.all(newlyConnected.map(async (a) => { 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);
}
});
// 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); const settings = await fetchAgentSettings(a.name);
if (settings) { if (settings) {
agentSettings[a.name] = { ...settings, fetched: true }; agentSettings[a.name] = settings;
} }
})); }));
renderAgents(agents); }
// 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); } function restartAgent(n) { socket.emit('restart-agent', n); }
@ -820,6 +911,7 @@
socket.emit('start-agent', n); socket.emit('start-agent', n);
} }
function stopAgent(n) { socket.emit('stop-agent', n); } function stopAgent(n) { socket.emit('stop-agent', n); }
function destroyAgent(n) { socket.emit('destroy-agent', n); }
function disconnectAllAgents() { function disconnectAllAgents() {
socket.emit('stop-all-agents'); socket.emit('stop-all-agents');
} }