feat(ui): hybrid sidebar (sectioned + active pill + agent dots) + agent profile viewer in Settings

Sidebar: Spaces / Agents / Navigate sections, accent pill on active item, status
dots on agents. Settings Agents rows expand to show the agent's persona (soul) +
capabilities/scopes via GET /api/agents/:id/profile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-05 00:11:14 +10:00
parent 80363d3e68
commit bf58b624a3
5 changed files with 82 additions and 7 deletions

View File

@@ -51,6 +51,11 @@ async function renderTokens(c) {
paint();
}
function fileBlock(parent, label, content) {
parent.appendChild(el('div', { class: 'agent-file-label' }, label));
parent.appendChild(el('div', { class: 'agent-file-content' }, content));
}
async function renderAgents(c) {
c.replaceChildren(el('div', { class: 'muted' }, 'Loading…'));
let agents = [];
@@ -59,10 +64,35 @@ async function renderAgents(c) {
if (!agents.length) { c.appendChild(el('div', { class: 'muted' }, 'No agents.')); return; }
for (const a of agents) {
const caps = Object.entries(a.capabilities || {}).filter(([, v]) => v).map(([k]) => k).join(', ') || '—';
const scope = a.scopes?.space_id ? 'space-scoped' : '';
c.appendChild(el('div', { class: 'settings-row' },
el('span', { class: 'settings-label' }, a.name),
el('span', { class: 'settings-value muted' }, `${a.slug} · ${a.kind} · caps: ${caps}${scope ? ' · ' + scope : ''}`)));
const row = el('div', { class: 'agent-row' });
const body = el('div', { class: 'agent-body hidden' });
let loaded = false;
const hd = el('div', {
class: 'agent-row-hd',
onclick: async () => {
const open = row.classList.toggle('open');
body.classList.toggle('hidden', !open);
if (open && !loaded) {
loaded = true;
body.replaceChildren(el('div', { class: 'muted' }, 'Loading…'));
try {
const p = await api.get('/api/agents/' + a.id + '/profile');
body.replaceChildren();
if (p.persona) fileBlock(body, 'Soul · persona', p.persona);
else body.appendChild(el('div', { class: 'muted' }, 'No persona defined (config-only agent).'));
fileBlock(body, 'Capabilities', JSON.stringify(p.capabilities || {}, null, 2));
if (p.scopes && Object.keys(p.scopes).length) fileBlock(body, 'Scopes', JSON.stringify(p.scopes, null, 2));
body.appendChild(el('div', { class: 'muted', style: { fontSize: '11px', marginTop: '8px' } },
'Void 2 agents are persona-in-code + DB config — no separate memory files (unlike Void 1).'));
} catch (e) { body.replaceChildren(el('div', { class: 'err' }, 'Error: ' + e.message)); }
}
}
},
el('span', { class: 'agent-nm' }, a.name),
el('span', { class: 'settings-value muted' }, `${a.slug} · ${a.kind} · ${caps}`),
el('span', { class: 'agent-row-chev' }, ''));
row.append(hd, body);
c.appendChild(row);
}
}