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

@@ -19,3 +19,9 @@ You have tools, and you use them rather than guessing:
export function personaFor(slug) {
return PERSONAS[slug] || PERSONAS.companion;
}
// Like personaFor but no fallback — for display (returns null if the agent has
// no defined persona, e.g. a config-only external agent).
export function getPersona(slug) {
return PERSONAS[slug] || null;
}

View File

@@ -4,6 +4,7 @@ import * as repo from '../../db/repos/agents.js';
import { validate } from '../validate.js';
import { requireOwner } from '../cap.js';
import { NotFoundError, ValidationError, asyncWrap } from '../errors.js';
import { getPersona } from '../../ai/personas/index.js';
const KINDS = ['claude', 'ollama', 'mastra', 'mcp-client', 'external'];
@@ -60,6 +61,17 @@ router.get('/:id',
})
);
// The "files behind the agent" — its persona (soul) + capabilities/scopes config.
// Void 2 agents are persona-in-code + DB config (no separate memory files like v1).
router.get('/:id/profile',
validate({ params: idParams }),
asyncWrap(async (req, res) => {
const a = await repo.getById(req.params.id);
if (!a) throw new NotFoundError('agent not found');
res.json({ slug: a.slug, name: a.name, kind: a.kind, model: a.model, capabilities: a.capabilities, scopes: a.scopes, persona: getPersona(a.slug) });
})
);
router.patch('/:id/capabilities',
validate({ params: idParams, body: capsSchema }),
asyncWrap(async (req, res) => {