import { loadConfig } from '../config.js'; import { listTasks } from './taskService.js'; /** One agent in the team, resolved from the roster (config.agents) with a * fallback to whatever agents actually touched tasks. */ export interface RosterEntry { name: string; role: string; model?: string; kind?: string; description?: string; budgetEur?: number; dispatch: 'loop' | 'architect'; } /** Infer a provider ("kind") from an agent name when the roster doesn't say. */ export function inferKind(name: string): string | undefined { const n = name.toLowerCase(); if (n.includes('claude') || n === 'backyard' || n === 'architect') return 'anthropic'; if (n.includes('codex') || n.includes('gpt')) return 'openai'; if (n.includes('kimi')) return 'moonshot'; return undefined; } /** Rough default display model per provider, for agents missing from the roster. */ function inferModel(kind?: string): string | undefined { if (kind === 'anthropic') return 'Claude'; if (kind === 'openai') return 'GPT-5 Codex'; if (kind === 'moonshot') return 'Kimi K2'; return undefined; } /** * The team roster: the named agents from `config.agents` if configured, * otherwise one entry per agent that has touched a task (assignedTo / doneBy), * so the board/budget views work even before a roster is filled in. */ export function getRoster(cwd: string): RosterEntry[] { const config = loadConfig(cwd); if (config.agents && Object.keys(config.agents).length > 0) { const preferredRole = new Map(); for (const [role, spec] of Object.entries(config.roles)) { // Architect is authoritative when one agent is preferred for several // roles (the default Claude config also prefers it as reviewer). if (!preferredRole.has(spec.preferredAgent) || role === 'architect') { preferredRole.set(spec.preferredAgent, role); } } return Object.entries(config.agents).map(([name, a]) => ({ name, role: preferredRole.get(name) ?? a.role, model: a.model, kind: a.kind ?? inferKind(name), description: a.description, budgetEur: a.budgetEur, dispatch: a.dispatch, })); } // Fallback: derive from tasks + the role config's preferred agents. const names = new Set(); for (const [, cfg] of Object.entries(config.roles)) names.add(cfg.preferredAgent); const roleByAgent = new Map(); for (const [role, cfg] of Object.entries(config.roles)) roleByAgent.set(cfg.preferredAgent, role); for (const t of listTasks(cwd)) { if (t.assignedTo) names.add(t.assignedTo); } return Array.from(names).map((name) => { const kind = inferKind(name); return { name, role: roleByAgent.get(name) ?? 'implementer', kind, model: inferModel(kind), dispatch: 'loop' }; }); }