feat: add agent emoji, model, and last activity to agents page
This commit is contained in:
@@ -116,13 +116,41 @@ function renderBoard() {
|
|||||||
const cardEl = document.createElement('div');
|
const cardEl = document.createElement('div');
|
||||||
cardEl.className = 'card';
|
cardEl.className = 'card';
|
||||||
cardEl.innerHTML = `
|
cardEl.innerHTML = `
|
||||||
<div class="card-head">
|
<div class="agent-header">
|
||||||
<h3 class="card-title">${escapeHtml(task.title)}</h3>
|
<h3 class="agent-name">${agent.emoji || '🤖'} ${escapeHtml(agent.name)}</h3>
|
||||||
<span class="badge priority-${task.priority}">${task.priority}</span>
|
<span class="agent-status ${statusClass}">${agent.status}</span>
|
||||||
|
</div>
|
||||||
|
<div class="agent-meta">
|
||||||
|
<span class="agent-model">🧠 ${agent.model || 'unknown'}</span>
|
||||||
|
<span class="agent-activity">⏰ ${agent.lastActivity ? new Date(agent.lastActivity).toLocaleString() : 'Never'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="agent-workload">
|
||||||
|
<span class="workload-badge">📋 ${agent.workload} active task${agent.workload !== 1 ? 's' : ''}</span>
|
||||||
|
</div>
|
||||||
|
<div class="agent-body">
|
||||||
|
<div class="agent-section">
|
||||||
|
<h4>📋 Current Task</h4>
|
||||||
|
<p class="agent-task">${agent.currentTask || 'No active task'}</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="card-desc">${escapeHtml(task.description || '')}</p>
|
<div class="agent-section">
|
||||||
<p class="meta assignee">${task.assignee || 'Unassigned'}</p>
|
<h4>🛠️ Tools</h4>
|
||||||
<p class="meta tags">${task.tags.map((t) => `<span class="tag">${escapeHtml(t)}</span>`).join(' ')}</p>
|
<div class="agent-tools">
|
||||||
|
${agent.tools.length ? agent.tools.slice(0, 5).map((tool) => \`<span class="tool-tag">\${escapeHtml(tool)}</span>\`).join('') : '<span class="no-data">No tools</span>'}
|
||||||
|
${agent.tools.length > 5 ? \`<span class="more-tag">+\${agent.tools.length - 5} more</span>\` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="agent-section">
|
||||||
|
<h4>📄 Recent Files</h4>
|
||||||
|
<div class="agent-files">
|
||||||
|
${agent.files.length ? agent.files.slice(0, 5).map((file) => \`<span class="file-tag">\${escapeHtml(file)}</span>\`).join('') : '<span class="no-data">No files</span>'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="agent-actions">
|
||||||
|
<button class="btn-secondary agent-details-btn" data-agent="\${escapeHtml(agent.name)}">Details</button>
|
||||||
|
<button class="btn-primary agent-assign-btn" data-agent="\${escapeHtml(agent.name)}">Assign Task</button>
|
||||||
|
</div>
|
||||||
|
`<span class="tag">${escapeHtml(t)}</span>`).join(' ')}</p>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" class="card-check" ${task.status === 'Done' ? 'checked' : ''} />
|
<input type="checkbox" class="card-check" ${task.status === 'Done' ? 'checked' : ''} />
|
||||||
Mark Complete
|
Mark Complete
|
||||||
@@ -419,9 +447,13 @@ function renderAgents(filter = '', statusFilter = '') {
|
|||||||
|
|
||||||
cardEl.innerHTML = `
|
cardEl.innerHTML = `
|
||||||
<div class="agent-header">
|
<div class="agent-header">
|
||||||
<h3 class="agent-name">${escapeHtml(agent.name)}</h3>
|
<h3 class="agent-name">${agent.emoji || '🤖'} ${escapeHtml(agent.name)}</h3>
|
||||||
<span class="agent-status ${statusClass}">${agent.status}</span>
|
<span class="agent-status ${statusClass}">${agent.status}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="agent-meta">
|
||||||
|
<span class="agent-model">🧠 ${agent.model || 'unknown'}</span>
|
||||||
|
<span class="agent-activity">⏰ ${agent.lastActivity ? new Date(agent.lastActivity).toLocaleString() : 'Never'}</span>
|
||||||
|
</div>
|
||||||
<div class="agent-workload">
|
<div class="agent-workload">
|
||||||
<span class="workload-badge">📋 ${agent.workload} active task${agent.workload !== 1 ? 's' : ''}</span>
|
<span class="workload-badge">📋 ${agent.workload} active task${agent.workload !== 1 ? 's' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -433,7 +465,22 @@ function renderAgents(filter = '', statusFilter = '') {
|
|||||||
<div class="agent-section">
|
<div class="agent-section">
|
||||||
<h4>🛠️ Tools</h4>
|
<h4>🛠️ Tools</h4>
|
||||||
<div class="agent-tools">
|
<div class="agent-tools">
|
||||||
${agent.tools.length ? agent.tools.slice(0, 5).map((tool) => `<span class="tool-tag">${escapeHtml(tool)}</span>`).join('') : '<span class="no-data">No tools</span>'}
|
${agent.tools.length ? agent.tools.slice(0, 5).map((tool) => \`<span class="tool-tag">\${escapeHtml(tool)}</span>\`).join('') : '<span class="no-data">No tools</span>'}
|
||||||
|
${agent.tools.length > 5 ? \`<span class="more-tag">+\${agent.tools.length - 5} more</span>\` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="agent-section">
|
||||||
|
<h4>📄 Recent Files</h4>
|
||||||
|
<div class="agent-files">
|
||||||
|
${agent.files.length ? agent.files.slice(0, 5).map((file) => \`<span class="file-tag">\${escapeHtml(file)}</span>\`).join('') : '<span class="no-data">No files</span>'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="agent-actions">
|
||||||
|
<button class="btn-secondary agent-details-btn" data-agent="\${escapeHtml(agent.name)}">Details</button>
|
||||||
|
<button class="btn-primary agent-assign-btn" data-agent="\${escapeHtml(agent.name)}">Assign Task</button>
|
||||||
|
</div>
|
||||||
|
`<span class="tool-tag">${escapeHtml(tool)}</span>`).join('') : '<span class="no-data">No tools</span>'}
|
||||||
${agent.tools.length > 5 ? `<span class="more-tag">+${agent.tools.length - 5} more</span>` : ''}
|
${agent.tools.length > 5 ? `<span class="more-tag">+${agent.tools.length - 5} more</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
44
server.js
44
server.js
@@ -558,12 +558,44 @@ app.get('/api/agents', (req, res) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const agentPromises = agentDirs.map(async (agentName) => {
|
// Load openclaw config for identity info
|
||||||
const agentPath = path.join(AGENTS_DIR, agentName);
|
let emoji = '🤖';
|
||||||
const workspacePath = path.join(agentPath, 'workspace');
|
let model = 'unknown';
|
||||||
|
let identityName = agentName;
|
||||||
|
|
||||||
|
if (fs.existsSync(OPENCLAW_CONFIG)) {
|
||||||
|
try {
|
||||||
|
const openclawConfig = JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, 'utf8'));
|
||||||
|
const agentConfig = openclawConfig.agents?.list?.find(a => a.id === agentName);
|
||||||
|
if (agentConfig) {
|
||||||
|
emoji = agentConfig.identity?.emoji || '🤖';
|
||||||
|
model = agentConfig.model?.primary || 'unknown';
|
||||||
|
identityName = agentConfig.identity?.name || agentName;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get last activity from session files
|
||||||
|
let lastActivity = null;
|
||||||
|
const sessionsPath = path.join(agentPath, 'sessions');
|
||||||
|
if (fs.existsSync(sessionsPath)) {
|
||||||
|
const sessionFiles = fs.readdirSync(sessionsPath).filter(f => f.endsWith('.jsonl'));
|
||||||
|
if (sessionFiles.length > 0) {
|
||||||
|
const latestSession = sessionFiles
|
||||||
|
.map(f => ({ file: f, mtime: fs.statSync(path.join(sessionsPath, f)).mtime }))
|
||||||
|
.sort((a, b) => b.mtime - a.mtime)[0];
|
||||||
|
if (latestSession) {
|
||||||
|
lastActivity = latestSession.mtime.toISOString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const agent = {
|
const agent = {
|
||||||
name: agentName,
|
id: agentName,
|
||||||
|
name: identityName,
|
||||||
|
emoji,
|
||||||
|
model,
|
||||||
|
lastActivity,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
currentTask: null,
|
currentTask: null,
|
||||||
tools: [],
|
tools: [],
|
||||||
@@ -575,10 +607,6 @@ app.get('/api/agents', (req, res) => {
|
|||||||
capabilities: []
|
capabilities: []
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fs.existsSync(workspacePath)) {
|
|
||||||
const files = fs.readdirSync(workspacePath);
|
|
||||||
agent.files = files.filter(f => f.endsWith('.md'));
|
|
||||||
|
|
||||||
const memoryPath = path.join(workspacePath, 'MEMORY.md');
|
const memoryPath = path.join(workspacePath, 'MEMORY.md');
|
||||||
if (fs.existsSync(memoryPath)) {
|
if (fs.existsSync(memoryPath)) {
|
||||||
const memory = fs.readFileSync(memoryPath, 'utf8');
|
const memory = fs.readFileSync(memoryPath, 'utf8');
|
||||||
|
|||||||
Reference in New Issue
Block a user