import { html } from 'lit'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { LightElement, renderMarkdown } from '../lib/base.js'; const STRENGTH_COLORS = { very_high: '#ef4444', high: '#f97316', average: '#eab308', low: '#84cc16', very_low: '#22c55e', }; const STRENGTH_LABELS = { very_high: 'Very High', high: 'High', average: 'Average', low: 'Low', very_low: 'Very Low', }; export class AgentsPage extends LightElement { static properties = { _open: { state: true }, _agents: { state: true }, _detail: { state: true }, // null | { meta, prompt, models } _loading: { state: true }, _error: { state: true }, }; constructor() { super(); this._open = false; this._agents = []; this._detail = null; this._loading = false; this._error = null; } connectedCallback() { super.connectedCallback(); window.addEventListener('llm-page-change', (e) => { this._open = e.detail.page === 'agents'; this.style.display = this._open ? 'flex' : 'none'; if (this._open && this._agents.length === 0) this._loadList(); if (!this._open) this._detail = null; }); } async _loadList() { this._loading = true; this._error = null; try { const res = await fetch('/api/agents'); if (!res.ok) throw new Error(`HTTP ${res.status}`); this._agents = await res.json(); } catch (e) { this._error = e.message; } finally { this._loading = false; } } async _openDetail(agent) { this._loading = true; this._error = null; try { const res = await fetch(`/api/agents/${agent.id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); this._detail = await res.json(); } catch (e) { this._error = e.message; } finally { this._loading = false; } } _back() { this._detail = null; this._error = null; } // ── Render helpers ──────────────────────────────────────────────────────── _strengthDot(strength, size = '0.62rem') { if (!strength) return html``; return html` `; } _scopePill(scope) { return html`${scope}`; } // ── List view ───────────────────────────────────────────────────────────── _renderCard(agent) { return html`
this._openDetail(agent)}>
${agent.icon ? html` ${agent.name} ` : ''}
${agent.name} ${agent.id}

${agent.friendly_description ?? agent.description}

${agent.strength ? html` ${this._strengthDot(agent.strength)} ${STRENGTH_LABELS[agent.strength] ?? agent.strength} ` : ''} ${agent.scope ? html`${this._scopePill(agent.scope)}` : ''} ${agent.client ? html` ${agent.client} ` : ''}
`; } _renderSection(title, agents) { if (agents.length === 0) return ''; return html`

${title}

${agents.map(a => this._renderCard(a))}
`; } _renderList() { if (this._loading) return html`
Loading…
`; if (this._error) return html`
${this._error}
`; if (this._agents.length === 0) return html`

No agents found.

`; // Group by role: chat entry-points, dispatchable task executors, and // runtime-internal system agents (e.g. tic). const chat = this._agents.filter(a => a.type === 'chat'); const task = this._agents.filter(a => a.type === 'task'); const system = this._agents.filter(a => a.type === 'system'); return html` ${this._renderSection('Chat', chat)} ${this._renderSection('Task Executors', task)} ${this._renderSection('System', system)} `; } // ── Detail view ─────────────────────────────────────────────────────────── _renderModelRow(m, i) { const isFirst = i === 0; return html` ${i + 1} ${this._strengthDot(m.strength)} ${m.name} ${m.is_default ? html`default` : ''} ${m.model_id} ${(m.scope ?? []).map(s => this._scopePill(s))} `; } _renderDetail() { if (this._loading && !this._detail) return html`
Loading…
`; if (!this._detail) return ''; const { meta, prompt, models } = this._detail; return html`
${meta.icon ? html` ${meta.name} ` : ''}

${meta.name}

${meta.friendly_description ?? meta.description}

${this._error ? html`
${this._error}
` : ''}

Metadata

${meta.strength ? html` ` : ''} ${meta.scope ? html` ` : ''} ${meta.client ? html` ` : ''} ${meta.inject_memory?.length ? html` ` : ''}
ID${meta.id}
Strength ${this._strengthDot(meta.strength)} ${STRENGTH_LABELS[meta.strength] ?? meta.strength}
Scope${this._scopePill(meta.scope)}
Pinned model${meta.client}
Memory files ${meta.inject_memory.map(f => html`
${f}
`)}

Model resolution order

Models sorted by how well they match this agent's requirements. The system uses the first available model from the top.

${models.length === 0 ? html`

No models configured.

` : html`
${models.map((m, i) => this._renderModelRow(m, i))}
# Strength Name Model ID Scope
` }

System prompt

${unsafeHTML(renderMarkdown(prompt))}
`; } // ── Root render ─────────────────────────────────────────────────────────── render() { return html`
${this._detail ? this._renderDetail() : html`

Agents

Read-only view. Agents are defined by files in agents/ — to add, remove, or modify an agent, edit the corresponding AGENT.md file in that directory.

You can also ask Copilot (top bar) to create a new agent for you — just describe what it should do and it will set up all the files automatically.

${this._renderList()} ` }
`; } }