Phase 3: Health Check + Task Queue for Agent Tab

Backend: _get_agent_health() with CPU/Memory/Threads from ps, get_agent_tasks() reads tasks.json. API: GET /api/agents/{id}/health + /tasks. Frontend: Health metrics block in Overview tab (CPU, Memory bar, Threads), Tasks tab with status-colored task list.
This commit is contained in:
Rose
2026-04-20 13:45:20 +02:00
parent e5b55c6f3a
commit 8b8a507ace
4 changed files with 250 additions and 1 deletions

View File

@@ -1918,6 +1918,7 @@ async function openAgentDetail(agentId) {
<button class="agent-tab${_agentTab==='activity'?' active':''}" onclick="switchAgentTab('activity')">Activity</button>
<button class="agent-tab${_agentTab==='errors'?' active':''}" onclick="switchAgentTab('errors')">Errors</button>
<button class="agent-tab${_agentTab==='chat'?' active':''}" onclick="switchAgentTab('chat')">Chat History</button>
<button class="agent-tab${_agentTab==='tasks'?' active':''}" onclick="switchAgentTab('tasks')">Tasks</button>
</div>
<div id="agentTabContent" class="agent-tab-content">
@@ -1937,7 +1938,7 @@ async function switchAgentTab(tab) {
// Update tab buttons
document.querySelectorAll('.agent-tab').forEach((el, i) => {
const tabs = ['overview', 'soul', 'memory', 'inbox', 'activity', 'errors', 'chat'];
const tabs = ['overview', 'soul', 'memory', 'inbox', 'activity', 'errors', 'chat', 'tasks'];
el.classList.toggle('active', tabs[i] === tab);
});
@@ -1965,6 +1966,9 @@ async function switchAgentTab(tab) {
case 'chat':
await loadAgentChatHistory(agentId, content);
break;
case 'tasks':
await loadAgentTasks(agentId, content);
break;
}
}
@@ -2010,11 +2014,44 @@ async function loadAgentOverview(agentId, content) {
</div>` : ''}
</div>
`;
// Fetch health metrics in parallel
try {
const health = await api(`/api/agents/${agentId}/health`);
if (!health.error && health.status !== 'offline') {
const memBar = health.memory_mb > 0 ? `<div class="health-bar"><div class="health-bar-fill" style="width:${Math.min(health.memory_mb / 512 * 100, 100)}%"></div></div>` : '';
const uptime = health.uptime_seconds > 0 ? _formatUptime(health.uptime_seconds) : 'N/A';
content.innerHTML += `
<div class="health-metrics" style="margin-top:12px;padding:12px;background:var(--card-bg);border:1px solid var(--border);border-radius:8px">
<div style="font-size:10px;font-weight:600;color:var(--muted);margin-bottom:8px;text-transform:uppercase">System Health</div>
<div class="agent-info-row">
<span class="agent-info-label">CPU</span>
<span style="font-size:11px">${health.cpu_percent}%</span>
</div>
<div class="agent-info-row">
<span class="agent-info-label">Memory</span>
<span style="font-size:11px">${health.memory_mb} MB ${memBar}</span>
</div>
<div class="agent-info-row">
<span class="agent-info-label">Threads</span>
<span style="font-size:11px">${health.threads}</span>
</div>
</div>`;
}
} catch(e) {}
} catch(e) {
content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
}
}
function _formatUptime(seconds) {
if (!seconds || seconds <= 0) return 'N/A';
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
return h > 0 ? `${h}h ${m}m` : `${m}m`;
}
async function loadAgentSoul(agentId, content) {
const canEdit = agentId !== 'rose';
try {
@@ -2301,6 +2338,45 @@ function openAgentChatSession(agentId, sessionId) {
showToast(`Loading chat session...`);
}
async function loadAgentTasks(agentId, content) {
try {
const data = await api(`/api/agents/${agentId}/tasks`);
const tasks = data.tasks || [];
if (tasks.length === 0) {
content.innerHTML = `
<div style="padding:24px;text-align:center;color:var(--muted);font-size:12px">
<div style="font-size:28px;margin-bottom:8px">📋</div>
<div>No tasks in queue</div>
<div style="font-size:10px;margin-top:4px;opacity:0.6">Tasks will appear here when agents are working on something</div>
</div>`;
return;
}
const TASK_STATUS_COLORS = { 'running': '#4caf50', 'queued': '#ff9800', 'completed': '#9e9e9e', 'failed': '#f44336' };
const rows = tasks.map(t => {
const color = TASK_STATUS_COLORS[t.status] || '#9e9e9e';
const ts = t.created_at ? new Date(t.created_at).toLocaleString() : '';
return `
<div class="task-row">
<span class="task-status-dot" style="background:${color}"></span>
<div style="flex:1;min-width:0">
<div style="font-size:12px">${esc(t.description || 'Task')}</div>
<div style="font-size:9px;color:var(--muted);margin-top:2px">${esc(ts)} · <span style="color:${color}">${esc(t.status)}</span></div>
</div>
</div>`;
}).join('');
content.innerHTML = `
<div style="padding:8px 0 8px;font-size:10px;color:var(--muted)">${tasks.length} task${tasks.length !== 1 ? 's' : ''}</div>
<div class="task-list">${rows}</div>`;
} catch(e) {
content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
}
}
// Edit handlers
function editAgentSoul(agentId) {
document.getElementById('soulView').style.display = 'none';

View File

@@ -1668,3 +1668,47 @@ body.resizing{user-select:none;cursor:col-resize;}
gap: 8px;
flex-wrap: wrap;
}
.health-metrics .agent-info-row {
padding: 4px 0;
}
.health-bar {
height: 4px;
background: var(--border);
border-radius: 2px;
margin-top: 4px;
width: 100px;
}
.health-bar-fill {
height: 4px;
background: #4caf50;
border-radius: 2px;
transition: width 0.3s;
}
.task-list {
padding: 8px;
display: flex;
flex-direction: column;
gap: 4px;
}
.task-row {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 8px 10px;
border-radius: 8px;
background: var(--card-bg);
border: 1px solid var(--border);
}
.task-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 4px;
}