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}
/>
-
-
- {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,