diff --git a/panel/src/components/settings/__tests__/ai-routing-card.test.tsx b/panel/src/components/settings/__tests__/ai-routing-card.test.tsx index a19ed50a..22dbc6dd 100644 --- a/panel/src/components/settings/__tests__/ai-routing-card.test.tsx +++ b/panel/src/components/settings/__tests__/ai-routing-card.test.tsx @@ -73,6 +73,187 @@ vi.mock("@/lib/api/providers", () => ({ vi.mock("sonner", () => ({ toast: { success: vi.fn(), error: vi.fn() } })); +// Full live roster (minus CEO/system) — mirrors foundation/identity.py so the +// per-agent override grid is exercised against the real 25-agent org chart, +// including the four PR reviewers a prior hard-coded literal dropped. +const { useAgentDefinitions } = vi.hoisted(() => ({ + useAgentDefinitions: vi.fn(), +})); + +vi.mock("@/hooks/use-agents", () => ({ useAgentDefinitions })); + +const FULL_ROSTER = [ + { + id: "main-pm", + uuid: "u-main-pm", + name: "Main PM", + role: "main_pm", + team: "main_pm", + }, + { + id: "product-owner", + uuid: "u-po", + name: "Product Owner", + role: "product_owner", + team: "board", + }, + { + id: "head-marketing", + uuid: "u-hom", + name: "Head of Marketing", + role: "head_marketing", + team: "board", + }, + { + id: "auditor", + uuid: "u-auditor", + name: "Auditor", + role: "auditor", + team: "board", + }, + { + id: "intake-1", + uuid: "u-intake", + name: "Intake", + role: "prompter", + team: "board", + }, + { + id: "secretary-1", + uuid: "u-secretary", + name: "Secretary", + role: "secretary", + team: "board", + }, + { + id: "pr-reviewer-1", + uuid: "u-prr", + name: "PR Reviewer", + role: "pr_reviewer", + team: "board", + }, + { + id: "be-dev-1", + uuid: "u-be1", + name: "Backend Developer 1", + role: "developer", + team: "backend", + }, + { + id: "be-dev-2", + uuid: "u-be2", + name: "Backend Developer 2", + role: "developer", + team: "backend", + }, + { + id: "be-qa", + uuid: "u-beqa", + name: "Backend QA", + role: "qa", + team: "backend", + }, + { + id: "be-pm", + uuid: "u-bepm", + name: "Backend PM", + role: "cell_pm", + team: "backend", + }, + { + id: "be-doc", + uuid: "u-bedoc", + name: "Backend Documenter", + role: "documenter", + team: "backend", + }, + { + id: "be-pr-reviewer", + uuid: "u-bepr", + name: "Backend PR Reviewer", + role: "pr_reviewer", + team: "backend", + }, + { + id: "fe-dev-1", + uuid: "u-fe1", + name: "Frontend Developer 1", + role: "developer", + team: "frontend", + }, + { + id: "fe-dev-2", + uuid: "u-fe2", + name: "Frontend Developer 2", + role: "developer", + team: "frontend", + }, + { + id: "fe-qa", + uuid: "u-feqa", + name: "Frontend QA", + role: "qa", + team: "frontend", + }, + { + id: "fe-pm", + uuid: "u-fepm", + name: "Frontend PM", + role: "cell_pm", + team: "frontend", + }, + { + id: "fe-doc", + uuid: "u-fedoc", + name: "Frontend Documenter", + role: "documenter", + team: "frontend", + }, + { + id: "fe-pr-reviewer", + uuid: "u-fepr", + name: "Frontend PR Reviewer", + role: "pr_reviewer", + team: "frontend", + }, + { + id: "ux-dev-1", + uuid: "u-ux1", + name: "UX/UI Developer 1", + role: "developer", + team: "ux_ui", + }, + { + id: "ux-dev-2", + uuid: "u-ux2", + name: "UX/UI Developer 2", + role: "developer", + team: "ux_ui", + }, + { id: "ux-qa", uuid: "u-uxqa", name: "UX/UI QA", role: "qa", team: "ux_ui" }, + { + id: "ux-pm", + uuid: "u-uxpm", + name: "UX/UI PM", + role: "cell_pm", + team: "ux_ui", + }, + { + id: "ux-doc", + uuid: "u-uxdoc", + name: "UX/UI Documenter", + role: "documenter", + team: "ux_ui", + }, + { + id: "ux-pr-reviewer", + uuid: "u-uxpr", + name: "UX/UI PR Reviewer", + role: "pr_reviewer", + team: "ux_ui", + }, +] as unknown as import("@/hooks/use-agents").AgentDefinition[]; + import { toast } from "sonner"; import { AIRoutingCard } from "../ai-routing-card"; @@ -96,6 +277,10 @@ describe("AIRoutingCard", () => { saveSelfHostedConfig.mockClear(); testSelfHosted.mockClear(); getSelfHostedModels.mockClear(); + useAgentDefinitions.mockReturnValue({ + data: FULL_ROSTER, + isLoading: false, + }); }); afterEach(() => { vi.clearAllMocks(); @@ -105,7 +290,9 @@ describe("AIRoutingCard", () => { render(withQueryClient()); await screen.findByText("Grok (xAI) API key"); - const grokSection = screen.getByText("Grok (xAI) API key").closest("section"); + const grokSection = screen + .getByText("Grok (xAI) API key") + .closest("section"); const ollamaSection = screen .getByText("Ollama Cloud API key") .closest("section"); @@ -166,7 +353,7 @@ describe("AIRoutingCard", () => { await waitFor(() => expect(ollamaInput.value).toBe("")); }); - it("groups the per-agent override list by org structure with two-column rows inside each group", async () => { + it("groups the per-agent override list by org structure with two-column rows inside each group, including all four PR reviewers", async () => { render(withQueryClient()); await screen.findByText("Per-agent override (mix mode)"); @@ -176,34 +363,54 @@ describe("AIRoutingCard", () => { "Backend Cell", "Frontend Cell", "UX/UI Cell", - "Intake / Secretary", + "Intake / Secretary / PR Review", ]) { expect( screen.getByRole("heading", { level: 4, name: title }), ).toBeInTheDocument(); } - // Spot-check a row from each end of the org chart. + // Spot-check a row from each end of the org chart, including the agents + // the old hard-coded literal dropped: ux-dev-2 and all four reviewers. expect(screen.getByText("product-owner")).toBeInTheDocument(); expect(screen.getByText("be-dev-1")).toBeInTheDocument(); expect(screen.getByText("secretary-1")).toBeInTheDocument(); + expect(screen.getByText("ux-dev-2")).toBeInTheDocument(); + expect(screen.getByText("pr-reviewer-1")).toBeInTheDocument(); + expect(screen.getByText("be-pr-reviewer")).toBeInTheDocument(); + expect(screen.getByText("fe-pr-reviewer")).toBeInTheDocument(); + expect(screen.getByText("ux-pr-reviewer")).toBeInTheDocument(); - // The Backend Cell group renders its 5 rows inside a 2-column grid. + // The Backend Cell group renders its 6 rows (5 + PR reviewer) inside a + // 2-column grid. const backendHeader = screen.getByRole("heading", { level: 4, name: "Backend Cell", }); const rowGrid = backendHeader.nextElementSibling as HTMLElement; expect(rowGrid.className).toContain("sm:grid-cols-2"); - expect(rowGrid.querySelectorAll(":scope > div")).toHaveLength(5); + expect(rowGrid.querySelectorAll(":scope > div")).toHaveLength(6); - // All 20 agents still render exactly one override select each. + // All 25 agents (the full roster minus the CEO) still render exactly one + // override select each. const mixSection = screen .getByText("Per-agent override (mix mode)") .closest("section")!; + expect(mixSection.querySelectorAll('[role="combobox"]')).toHaveLength(25); + }); + + it("shows a loading skeleton (not an empty grid) while the agent roster is still loading", async () => { + useAgentDefinitions.mockReturnValue({ data: undefined, isLoading: true }); + render(withQueryClient()); + await screen.findByText("Per-agent override (mix mode)"); + expect( - mixSection.querySelectorAll('[role="combobox"]'), - ).toHaveLength(20); + screen.queryByRole("heading", { level: 4, name: "Board" }), + ).not.toBeInTheDocument(); + const mixSection = screen + .getByText("Per-agent override (mix mode)") + .closest("section")!; + expect(mixSection.querySelector(".animate-pulse")).toBeInTheDocument(); }); it("tooltip-wraps the Grok/Ollama key labels and status badges, not the raw Switch", async () => { diff --git a/panel/src/components/settings/ai-routing-card.tsx b/panel/src/components/settings/ai-routing-card.tsx index 233314ff..58169db9 100644 --- a/panel/src/components/settings/ai-routing-card.tsx +++ b/panel/src/components/settings/ai-routing-card.tsx @@ -42,80 +42,106 @@ import { Zap, } from "lucide-react"; import { toast } from "sonner"; -import { AssignmentScope, ModelProvider } from "@/types"; +import { AssignmentScope, AgentRole, ModelProvider } from "@/types"; import type { SelfHostedModel } from "@/lib/api/providers"; import type { RoutingMode, SelfHostedTestResult } from "@/lib/api/providers"; import { SelfHostedSection } from "@/components/settings/self-hosted-section"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { HelpTip } from "@/components/ui/help-tip"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useAgentDefinitions } from "@/hooks/use-agents"; +import { + getBoardAgents, + getMainPm, + getBackendAgents, + getFrontendAgents, + getUxAgents, + getSupportAgents, + type AgentDefinition, +} from "@/lib/agent-definitions"; -// Matches the roboco agents_config AGENT_ROLE_MAP / AGENT_TEAM_MAP. -// Hard-coded so Mix mode shows a stable 18-row picker without an extra -// server round-trip. Grouped + ordered to mirror the org chart in CLAUDE.md. -// -// NOTE: CEO is explicitly excluded — it's the human-in-the-loop seat -// (Renzo), not an LLM-backed agent. Routing it anywhere would be a -// no-op in spawn_agent but confusing in the UI. -const AGENT_GROUPS: { title: string; agents: { slug: string; label: string }[] }[] = [ +// Grouped to mirror the org chart in CLAUDE.md, sourced from the live +// `/api/agents` roster (useAgentDefinitions) instead of a hand-maintained +// literal — that literal drifted (missing ux-dev-2 + all 4 PR reviewers). +// CEO is excluded by construction: none of these selectors ever match it. +const AGENT_GROUP_DEFS: { + title: string; + titleHint: string; + select: (agents: AgentDefinition[] | undefined | null) => AgentDefinition[]; +}[] = [ { title: "Board", - agents: [ - { slug: "product-owner", label: "Product Owner" }, - { slug: "head-marketing", label: "Head of Marketing" }, - { slug: "auditor", label: "Auditor" }, - ], + titleHint: "Product Owner, Head of Marketing, Auditor", + select: getBoardAgents, }, { title: "Main PM", - agents: [{ slug: "main-pm", label: "Main PM" }], + titleHint: "Coordinates all three delivery cells", + select: getMainPm, }, { title: "Backend Cell", - agents: [ - { slug: "be-pm", label: "Backend PM" }, - { slug: "be-dev-1", label: "Backend Dev 1" }, - { slug: "be-dev-2", label: "Backend Dev 2" }, - { slug: "be-qa", label: "Backend QA" }, - { slug: "be-doc", label: "Backend Documenter" }, - ], + titleHint: "2 Devs, 1 QA, 1 PM, 1 Documenter, 1 PR Reviewer", + select: getBackendAgents, }, { title: "Frontend Cell", - agents: [ - { slug: "fe-pm", label: "Frontend PM" }, - { slug: "fe-dev-1", label: "Frontend Dev 1" }, - { slug: "fe-dev-2", label: "Frontend Dev 2" }, - { slug: "fe-qa", label: "Frontend QA" }, - { slug: "fe-doc", label: "Frontend Documenter" }, - ], + titleHint: "2 Devs, 1 QA, 1 PM, 1 Documenter, 1 PR Reviewer", + select: getFrontendAgents, }, { title: "UX/UI Cell", - agents: [ - { slug: "ux-pm", label: "UX/UI PM" }, - { slug: "ux-dev-1", label: "UX/UI Dev" }, - { slug: "ux-qa", label: "UX/UI QA" }, - { slug: "ux-doc", label: "UX/UI Documenter" }, - ], + titleHint: "2 Devs, 1 QA, 1 PM, 1 Documenter, 1 PR Reviewer", + select: getUxAgents, }, { - title: "Intake / Secretary", - agents: [ - // Interactive (held-open chat) roles. Claude (SDK driver) and Grok - // (grok CLI) are the supported runtimes; assigning a Grok model routes - // them to the grok-prompter / grok-secretary image. - { slug: "intake-1", label: "Intake (Prompter)" }, - { slug: "secretary-1", label: "Secretary" }, - ], + title: "Intake / Secretary / PR Review", + titleHint: + "On-demand CEO-facing roles (Intake, Secretary) plus the root PR reviewer — reviews root→master PRs and inbound external/fork PRs.", + select: getSupportAgents, }, ]; +// Stable within-group ordering (PM/lead first, devs, QA, doc, reviewer last) +// so the picker doesn't churn alphabetically as the live roster loads — +// ties (e.g. dev-1/dev-2) break on slug, which already sorts correctly. +const ROLE_RANK: Partial> = { + [AgentRole.PRODUCT_OWNER]: 0, + [AgentRole.HEAD_MARKETING]: 1, + [AgentRole.AUDITOR]: 2, + [AgentRole.MAIN_PM]: 0, + [AgentRole.CELL_PM]: 0, + [AgentRole.DEVELOPER]: 1, + [AgentRole.QA]: 2, + [AgentRole.DOCUMENTER]: 3, + [AgentRole.PR_REVIEWER]: 4, + [AgentRole.PROMPTER]: 0, + [AgentRole.SECRETARY]: 1, +}; + +function byOrgOrder(a: AgentDefinition, b: AgentDefinition): number { + const ra = (a.role && ROLE_RANK[a.role]) ?? 99; + const rb = (b.role && ROLE_RANK[b.role]) ?? 99; + return ra !== rb ? ra - rb : a.id.localeCompare(b.id); +} + export function AIRoutingCard() { const { data: catalog = [] } = useCatalog(); const { data: keyStatus } = useOllamaKey(); const { data: snapshot } = useRoutingMode(); const { data: selfHostedModels = [] } = useSelfHostedModels(); + const { data: agentDefs, isLoading: agentsLoading } = useAgentDefinitions(); + + const agentGroups = useMemo( + () => + AGENT_GROUP_DEFS.map((g) => ({ + title: g.title, + titleHint: g.titleHint, + agents: g.select(agentDefs).slice().sort(byOrgOrder), + })).filter((g) => g.agents.length > 0), + [agentDefs], + ); const setKey = useSetOllamaKey(); const applyMode = useApplyMode(); @@ -231,7 +257,9 @@ export function AIRoutingCard() { return; try { await applyMode.mutateAsync({ mode: "anthropic" }); - toast.success("Role/global routing now on Anthropic — per-agent pins kept"); + toast.success( + "Role/global routing now on Anthropic — per-agent pins kept", + ); } catch (e) { toast.error("Switch failed: " + errMsg(e)); } @@ -291,7 +319,9 @@ export function AIRoutingCard() { mode: "self_hosted", ...(selfHostedModel ? { default_model: selfHostedModel } : {}), }); - toast.success("Role/global routing now on Self-Hosted LLM — per-agent pins kept"); + toast.success( + "Role/global routing now on Self-Hosted LLM — per-agent pins kept", + ); } catch (e) { toast.error("Switch failed: " + errMsg(e)); } @@ -397,7 +427,10 @@ export function AIRoutingCard() { } disabled={clearGrokKey} /> - @@ -629,155 +662,171 @@ export function AIRoutingCard() { Leave a row blank to inherit from the global mode. Saving overwrites all per-agent overrides with what's picked here.

-
- {AGENT_GROUPS.map((group) => ( -
-

- {group.title} -

-
- {group.agents.map((a) => ( -
-
-
- {a.slug} -
-
- {a.label} -
-
- -
- ))} + {agentsLoading ? ( +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ +
+ + +
-
- ))} -
+ ))} +
+ ) : ( +
+ {agentGroups.map((group) => ( +
+ +

+ {group.title} +

+
+
+ {group.agents.map((a) => ( +
+
+
+ {a.id} +
+
+ {a.name} +
+
+ +
+ ))} +
+
+ ))} +
+ )} {catalogOllamaOnly.length === 0 ? (

Ollama catalog empty — check /api/providers/catalog. diff --git a/panel/src/hooks/use-agents.ts b/panel/src/hooks/use-agents.ts index f97bc4a3..82acdb20 100644 --- a/panel/src/hooks/use-agents.ts +++ b/panel/src/hooks/use-agents.ts @@ -125,6 +125,15 @@ const AGENT_ROSTER: Agent[] = [ cell: "backend", status: "idle" as AgentState, }, + { + id: "23", + agent_id: "be-pr-reviewer", + name: "Backend PR Reviewer", + role: "pr_reviewer" as AgentRole, + team: "backend" as Team, + cell: "backend", + status: "idle" as AgentState, + }, // Frontend Cell { id: "10", @@ -171,6 +180,15 @@ const AGENT_ROSTER: Agent[] = [ cell: "frontend", status: "idle" as AgentState, }, + { + id: "24", + agent_id: "fe-pr-reviewer", + name: "Frontend PR Reviewer", + role: "pr_reviewer" as AgentRole, + team: "frontend" as Team, + cell: "frontend", + status: "idle" as AgentState, + }, // UX/UI Cell { id: "15", @@ -217,6 +235,15 @@ const AGENT_ROSTER: Agent[] = [ cell: "ux_ui", status: "idle" as AgentState, }, + { + id: "25", + agent_id: "ux-pr-reviewer", + name: "UX/UI PR Reviewer", + role: "pr_reviewer" as AgentRole, + team: "ux_ui" as Team, + cell: "ux_ui", + status: "idle" as AgentState, + }, ]; // Query keys diff --git a/panel/src/lib/agent-utils.ts b/panel/src/lib/agent-utils.ts index 917f3721..2452fb59 100644 --- a/panel/src/lib/agent-utils.ts +++ b/panel/src/lib/agent-utils.ts @@ -55,18 +55,21 @@ const AGENT_UUIDS: Record = { "00000000-0000-0000-0001-000000000003": "be-qa", "00000000-0000-0000-0001-000000000004": "be-pm", "00000000-0000-0000-0001-000000000005": "be-doc", + "00000000-0000-0000-0001-000000000006": "be-pr-reviewer", // Frontend Cell "00000000-0000-0000-0002-000000000001": "fe-dev-1", "00000000-0000-0000-0002-000000000002": "fe-dev-2", "00000000-0000-0000-0002-000000000003": "fe-qa", "00000000-0000-0000-0002-000000000004": "fe-pm", "00000000-0000-0000-0002-000000000005": "fe-doc", + "00000000-0000-0000-0002-000000000006": "fe-pr-reviewer", // UX/UI Cell "00000000-0000-0000-0003-000000000001": "ux-dev-1", "00000000-0000-0000-0003-000000000002": "ux-dev-2", "00000000-0000-0000-0003-000000000003": "ux-qa", "00000000-0000-0000-0003-000000000004": "ux-pm", "00000000-0000-0000-0003-000000000005": "ux-doc", + "00000000-0000-0000-0003-000000000006": "ux-pr-reviewer", // Board / Management "00000000-0000-0000-0004-000000000001": "main-pm", "00000000-0000-0000-0004-000000000002": "product-owner", @@ -92,18 +95,21 @@ const AGENT_NAMES: Record = { "be-dev-2": "Backend Dev 2", "be-qa": "Backend QA", "be-doc": "Backend Doc", + "be-pr-reviewer": "Backend PR Reviewer", // Frontend Cell "fe-pm": "Frontend PM", "fe-dev-1": "Frontend Dev 1", "fe-dev-2": "Frontend Dev 2", "fe-qa": "Frontend QA", "fe-doc": "Frontend Doc", + "fe-pr-reviewer": "Frontend PR Reviewer", // UX/UI Cell "ux-pm": "UX/UI PM", "ux-dev-1": "UX/UI Dev 1", "ux-dev-2": "UX/UI Dev 2", "ux-qa": "UX/UI QA", "ux-doc": "UX/UI Doc", + "ux-pr-reviewer": "UX/UI PR Reviewer", // CEO (human) ceo: "CEO", CEO: "CEO", @@ -181,18 +187,21 @@ const AGENT_CODES: Record = { "be-dev-2": "BD2", "be-qa": "BQA", "be-doc": "BDC", + "be-pr-reviewer": "BPR", // Frontend Cell "fe-pm": "FPM", "fe-dev-1": "FD1", "fe-dev-2": "FD2", "fe-qa": "FQA", "fe-doc": "FDC", + "fe-pr-reviewer": "FPR", // UX/UI Cell "ux-pm": "UPM", "ux-dev-1": "UD1", "ux-dev-2": "UD2", "ux-qa": "UQA", "ux-doc": "UDC", + "ux-pr-reviewer": "UPR", // CEO ceo: "CEO", CEO: "CEO", @@ -240,12 +249,7 @@ export function isKnownAgent(agentId: string | null | undefined): boolean { // --------------------------------------------------------------------------- export type AgentTeamColor = - | "backend" - | "frontend" - | "ux_ui" - | "board" - | "ceo" - | "system"; + "backend" | "frontend" | "ux_ui" | "board" | "ceo" | "system"; /** Every value here is an existing Tailwind color family already used * elsewhere in the codebase at the same `/15` bg + `/40` border weight diff --git a/panel/src/lib/mock-data.ts b/panel/src/lib/mock-data.ts index c55e4bac..ed7300ac 100644 --- a/panel/src/lib/mock-data.ts +++ b/panel/src/lib/mock-data.ts @@ -25,24 +25,31 @@ export const AGENT_IDS = { mainPm: "main-pm", headMarketing: "head-marketing", auditor: "auditor", + // Board-adjacent singletons (CEO-facing / read-only) + intake: "intake-1", + secretary: "secretary-1", + prReviewer: "pr-reviewer-1", // Backend Cell bePm: "be-pm", beDev1: "be-dev-1", beDev2: "be-dev-2", beQa: "be-qa", beDoc: "be-doc", + bePrReviewer: "be-pr-reviewer", // Frontend Cell fePm: "fe-pm", feDev1: "fe-dev-1", feDev2: "fe-dev-2", feQa: "fe-qa", feDoc: "fe-doc", + fePrReviewer: "fe-pr-reviewer", // UX/UI Cell uxPm: "ux-pm", uxDev1: "ux-dev-1", uxDev2: "ux-dev-2", uxQa: "ux-qa", uxDoc: "ux-doc", + uxPrReviewer: "ux-pr-reviewer", }; export const TASK_IDS = { @@ -123,6 +130,13 @@ export const mockAgents: MockAgent[] = [ role: AgentRole.DOCUMENTER, team: Team.BACKEND, }, + { + id: AGENT_IDS.bePrReviewer, + name: "Backend PR Reviewer", + slug: "be-pr-reviewer", + role: AgentRole.PR_REVIEWER, + team: Team.BACKEND, + }, // Frontend Cell { id: AGENT_IDS.feDev1, @@ -159,7 +173,14 @@ export const mockAgents: MockAgent[] = [ role: AgentRole.DOCUMENTER, team: Team.FRONTEND, }, - // UX/UI Cell (only 1 dev per seed data) + { + id: AGENT_IDS.fePrReviewer, + name: "Frontend PR Reviewer", + slug: "fe-pr-reviewer", + role: AgentRole.PR_REVIEWER, + team: Team.FRONTEND, + }, + // UX/UI Cell (2 devs per seed data) { id: AGENT_IDS.uxDev1, name: "UX/UI Developer 1", @@ -195,6 +216,13 @@ export const mockAgents: MockAgent[] = [ role: AgentRole.DOCUMENTER, team: Team.UX_UI, }, + { + id: AGENT_IDS.uxPrReviewer, + name: "UX/UI PR Reviewer", + slug: "ux-pr-reviewer", + role: AgentRole.PR_REVIEWER, + team: Team.UX_UI, + }, // Board/Management (team=null per seed data) { id: AGENT_IDS.mainPm, @@ -224,6 +252,31 @@ export const mockAgents: MockAgent[] = [ role: AgentRole.AUDITOR, team: null, }, + // Board-adjacent singletons — CEO-facing on-demand roles, not Board + // reviewers. pr-reviewer-1 carries team=board (unlike its siblings here) + // because that's the only signal distinguishing it from the per-cell + // reviewers below, which carry their own cell's team instead. + { + id: AGENT_IDS.intake, + name: "Intake", + slug: "intake-1", + role: AgentRole.PROMPTER, + team: null, + }, + { + id: AGENT_IDS.secretary, + name: "Secretary", + slug: "secretary-1", + role: AgentRole.SECRETARY, + team: null, + }, + { + id: AGENT_IDS.prReviewer, + name: "PR Reviewer", + slug: "pr-reviewer-1", + role: AgentRole.PR_REVIEWER, + team: Team.BOARD, + }, // CEO (Human - Renzo) { id: AGENT_IDS.ceo,