diff --git a/src/agent/library/full_state.js b/src/agent/library/full_state.js
index c5db85f..7323457 100644
--- a/src/agent/library/full_state.js
+++ b/src/agent/library/full_state.js
@@ -66,6 +66,8 @@ export function getFullState(agent) {
},
inventory: {
counts: getInventoryCounts(bot),
+ stacksUsed: bot.inventory.items().length,
+ totalSlots: bot.inventory.slots.length,
equipment: {
helmet: helmet ? helmet.name : null,
chestplate: chestplate ? chestplate.name : null,
@@ -78,7 +80,6 @@ export function getFullState(agent) {
humanPlayers: players,
botPlayers: bots,
entityTypes: getNearbyEntityTypes(bot).filter(t => t !== 'player' && t !== 'item'),
- blockTypes: getNearbyBlockTypes(bot)
},
modes: {
summary: bot.modes.getMiniDocs()
diff --git a/src/agent/mindserver_proxy.js b/src/agent/mindserver_proxy.js
index 2db78e3..b1043b1 100644
--- a/src/agent/mindserver_proxy.js
+++ b/src/agent/mindserver_proxy.js
@@ -58,9 +58,9 @@ class MindServerProxy {
this.agent.cleanKill();
});
- this.socket.on('send-message', (agentName, message) => {
+ this.socket.on('send-message', (data) => {
try {
- this.agent.respondFunc("NO USERNAME", message);
+ this.agent.respondFunc(data.from, data.message);
} catch (error) {
console.error('Error: ', JSON.stringify(error, Object.getOwnPropertyNames(error)));
}
diff --git a/src/mindcraft/mindserver.js b/src/mindcraft/mindserver.js
index 0112660..c06e0b5 100644
--- a/src/mindcraft/mindserver.js
+++ b/src/mindcraft/mindserver.js
@@ -173,14 +173,13 @@ export function createMindServer(host_public = false, port = 8080) {
});
- socket.on('send-message', (agentName, message) => {
+ socket.on('send-message', (agentName, data) => {
if (!agent_connections[agentName]) {
console.warn(`Agent ${agentName} not in game, cannot send message via MindServer.`);
return
}
try {
- console.log(`Sending message to agent ${agentName}: ${message}`);
- agent_connections[agentName].socket.emit('send-message', agentName, message)
+ agent_connections[agentName].socket.emit('send-message', data)
} catch (error) {
console.error('Error: ', error);
}
diff --git a/src/mindcraft/public/index.html b/src/mindcraft/public/index.html
index 78f1fe5..6e81b32 100644
--- a/src/mindcraft/public/index.html
+++ b/src/mindcraft/public/index.html
@@ -26,7 +26,7 @@
border-radius: 4px;
display: flex;
flex-direction: column;
- align-items: flex-start;
+ align-items: stretch;
}
.restart-btn, .start-btn, .stop-btn {
color: white;
@@ -107,18 +107,26 @@
.setting-wrapper input[type="checkbox"] {
transform: scale(1.2);
}
+ .agent-view-container {
+ width: 100%;
+ height: 100%;
+ aspect-ratio: 4/3;
+ overflow: hidden;
+ }
.agent-viewer {
- width: 200px;
- height: 150px;
+ width: 100%;
+ height: 100%;
border: none;
- margin-left: 10px;
+ display: block;
}
.last-message {
font-style: italic;
color: #aaa;
margin-top: 5px;
- white-space: pre-wrap;
- word-break: break-word;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 100%;
}
.start-btn:disabled {
opacity: 0.4;
@@ -129,6 +137,90 @@
display: flex;
justify-content: flex-start;
}
+ .agent-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 8px;
+ width: 100%;
+ align-items: start;
+ }
+ .agent-grid .cell {
+ background: #3a3a3a;
+ padding: 6px 8px;
+ border-radius: 4px;
+ }
+ .agent-grid .cell.title {
+ background: transparent;
+ padding: 0;
+ }
+ .agent-inventory {
+ margin-top: 8px;
+ background: #2f2f2f;
+ border-radius: 6px;
+ padding: 8px;
+ }
+ .agent-inventory h3 { margin: 0 0 6px 0; font-size: 1em; }
+ .inventory-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 6px;
+ }
+ .agent-details-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 6px;
+ width: 100%;
+ }
+ .controls-row {
+ margin-top: 8px;
+ display: grid;
+ grid-template-columns: auto 1fr auto auto auto auto;
+ gap: 8px;
+ align-items: center;
+ }
+ .msg-input {
+ width: calc(100% - 8px);
+ background: #262626;
+ border: 1px solid #555;
+ color: #e0e0e0;
+ border-radius: 4px;
+ padding: 4px 6px;
+ }
+ .neutral-btn {
+ color: white;
+ border: none;
+ padding: 5px 10px;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-left: 5px;
+ background: #505050;
+ }
+ .neutral-btn:hover { background: #5a5a5a; }
+ .neutral-btn:disabled {
+ background: #383838;
+ color: #666;
+ cursor: not-allowed;
+ }
+ .msg-input:disabled {
+ background: #1a1a1a;
+ color: #666;
+ cursor: not-allowed;
+ }
+ .page-footer {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: #2d2d2d;
+ padding: 16px;
+ display: flex;
+ gap: 12px;
+ justify-content: space-between;
+ box-shadow: 0 -2px 10px rgba(0,0,0,0.2);
+ }
+ body {
+ padding-bottom: 80px; /* Make room for footer */
+ }
.agent-stats-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
@@ -226,8 +318,14 @@
-
-
+
@@ -439,6 +537,7 @@
// Subscribe to aggregated state updates (re-sent on each connect)
socket.on('state-update', (states) => {
+ window.lastStates = states;
Object.keys(states || {}).forEach(name => {
const st = states[name];
const healthEl = document.getElementById(`health-${name}`);
@@ -452,9 +551,13 @@
const hunEl = document.getElementById(`hunger-${name}`);
const bioEl = document.getElementById(`biome-${name}`);
const modeEl = document.getElementById(`mode-${name}`);
+ const itemsEl = document.getElementById(`items-${name}`);
+ const equippedEl = document.getElementById(`equipped-${name}`);
+ const invGrid = document.getElementById(`inventory-${name}`);
+ const actionEl = document.getElementById(`action-${name}`);
if (posEl && gp.position) {
const p = gp.position;
- posEl.textContent = `pos: x ${p.x}, y ${p.y}, z ${p.z}`;
+ posEl.textContent = `x ${p.x}, y ${p.y}, z ${p.z}`;
}
if (hunEl && typeof gp.hunger === 'number') {
const fMax = typeof gp.hungerMax === 'number' ? gp.hungerMax : 20;
@@ -462,6 +565,24 @@
}
if (bioEl && gp.biome) bioEl.textContent = `biome: ${gp.biome}`;
if (modeEl && gp.gamemode) modeEl.textContent = `gamemode: ${gp.gamemode}`;
+ if (itemsEl && st.inventory) {
+ const used = st.inventory.stacksUsed ?? 0;
+ const total = st.inventory.totalSlots ?? 0;
+ itemsEl.textContent = `inventory slots: ${used}/${total}`;
+ }
+ if (equippedEl && st.inventory?.equipment) {
+ const e = st.inventory.equipment;
+ equippedEl.textContent = `equipped: ${e.mainHand || 'none'}`;
+ }
+ if (actionEl && st.action) {
+ actionEl.textContent = `${st.action.current || 'Idle'}`;
+ }
+ if (invGrid && st.inventory?.counts) {
+ const counts = st.inventory.counts;
+ invGrid.innerHTML = Object.keys(counts).length ?
+ Object.entries(counts).map(([k, v]) => `
${k}: ${v}
`).join('') :
+ '
(empty)
';
+ }
}
});
});
@@ -584,6 +705,19 @@
originalAgentSettings = null;
}
+ function updateAgentViewer(name) {
+ const agentEl = document.getElementById(`agent-${name}`);
+ if (!agentEl) return;
+
+ const settings = agentSettings[name];
+ const viewerContainer = agentEl.querySelector('.agent-view-container');
+ if (!viewerContainer) return;
+
+ const agentState = agents.find(a => a.name === name);
+ const shouldShow = agentState?.in_game && settings?.render_bot_view === true;
+ viewerContainer.parentElement.style.display = shouldShow ? '' : 'none';
+ }
+
discardBtn.addEventListener('click', () => {
if (!currentAgentName || !originalAgentSettings) return;
buildAgentSettingsForm(originalAgentSettings);
@@ -593,6 +727,9 @@
if (!currentAgentName) return;
const edited = getEditedAgentSettings();
socket.emit('set-agent-settings', currentAgentName, edited);
+ // Update local settings immediately
+ agentSettings[currentAgentName] = { ...edited, fetched: true };
+ updateAgentViewer(currentAgentName);
closeAgentSettings();
});
@@ -602,53 +739,118 @@
// 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 = cfg.render_bot_view === true;
+ const showViewer = agent.in_game && cfg.render_bot_view === true;
const viewerHTML = showViewer ? `
` : '';
const lastMessage = agentLastMessage[agent.name] || '';
return `
-
-
-
●${agent.name}
-
-
-
- ${agent.in_game ? `
-
-
-
-
- ` : `
-
- `}
+
+
+
+ ●${agent.name}
+
+
+
+ ${showViewer ? `
${viewerHTML}
` : ''}
+
action: -
+
gamemode: -
+
health: -
+
hunger: -
+
pos: -
+
biome: -
+
inventory slots: -
+
equipped: -
+
+
Last Message: ${lastMessage}
-
-
gamemode: -
-
health: -
-
hunger: -
-
pos: -
-
biome: -
+
+
+
+
+
+
+
+
-
${lastMessage}
- ${viewerHTML}
`;
- }).join('') +
- `
-
` :
+ }).join('') :
'
No agents connected
';
+
+ // Update viewers after DOM has updated
+ setTimeout(updateViewers, 0);
}
- socket.on('agents-update', agents => { renderAgents(agents); });
+ 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 };
+ }
+ }));
+ renderAgents(agents);
+ });
function restartAgent(n) { socket.emit('restart-agent', n); }
- function startAgent(n) { socket.emit('start-agent', n); }
+ function disconnectAgent(n) { socket.emit('stop-agent', n); }
+ function startAgent(n) {
+ const btn = document.querySelector(`button[onclick="startAgent('${n}')"]`);
+ if (btn) {
+ btn.textContent = 'Connecting...';
+ btn.disabled = true;
+ }
+ socket.emit('start-agent', n);
+ }
function stopAgent(n) { socket.emit('stop-agent', n); }
- function killAllAgents() { socket.emit('stop-all-agents'); }
- function shutdown() { socket.emit('shutdown'); }
- function sendMessage(n, m) { socket.emit('send-message', n, m); }
+ function disconnectAllAgents() {
+ socket.emit('stop-all-agents');
+ }
+ function confirmShutdown() {
+ if (confirm('Are you sure you want to perform a full shutdown?\nThis will stop all agents and close the server.')) {
+ socket.emit('shutdown');
+ }
+ }
+ function sendMessage(n, m) {
+ if (!m || !m.trim()) return;
+ socket.emit('send-message', n, { from: 'ADMIN', message: m });
+ const input = document.getElementById(`messageInput-${n}`);
+ const btn = document.getElementById(`sendBtn-${n}`);
+ if (input) input.value = '';
+ if (btn) btn.disabled = true;
+ }
+ function onMsgInputChange(name) {
+ const input = document.getElementById(`messageInput-${name}`);
+ const btn = document.getElementById(`sendBtn-${name}`);
+ if (btn && input) {
+ btn.disabled = !(input.value && input.value.trim().length > 0);
+ }
+ }
+
+ function toggleDetails(name) {
+ const invSection = document.getElementById(`inventorySection-${name}`);
+ if (!invSection) return;
+ const visible = invSection.style.display !== 'none';
+ invSection.style.display = visible ? 'none' : '';
+ }
+ window.toggleDetails = toggleDetails;