fix(panel): group Intake / Secretary / root PR Reviewer as Support, not Board

The Board is the three oversight roles — Product Owner, Head of Marketing, Auditor. Intake (Prompter), the Secretary, and the root PR Reviewer are CEO-direct helpers (per the org chart), but they carry team=board internally, so both agent groupings bucketed them under 'Board' — and on the agents page the helpers were even duplicated into both Board and On-Demand. They now render in a dedicated Support group in the journals list and the agents page; cell PR reviewers keep their cell's team and stay grouped under that cell. Board is now exactly PO/HoM/Auditor.
This commit is contained in:
Renn F
2026-06-22 14:00:06 +02:00
parent 71f068ea6c
commit bdf9481775
4 changed files with 71 additions and 41 deletions
+6 -6
View File
@@ -17,7 +17,7 @@ import {
getBackendAgents,
getFrontendAgents,
getUxAgents,
getOnDemandAgents,
getSupportAgents,
} from "@/lib/agent-definitions";
import {
OrchestratorStatusCards,
@@ -136,12 +136,12 @@ export default function AgentsPage() {
columns={4}
/>
{/* On-Demand section: Prompter/Intake and Secretary agents — only rendered
when the API returns at least one matching agent */}
{getOnDemandAgents(agents).length > 0 && (
{/* Support section: the CEO-direct helpers — Intake/Prompter, Secretary,
and the root PR Reviewer — only rendered when at least one matches */}
{getSupportAgents(agents).length > 0 && (
<AgentGrid
title="On-Demand"
agents={getOnDemandAgents(agents)}
title="Support"
agents={getSupportAgents(agents)}
agentStatuses={agentStatuses}
agentUsage={agentUsageMap}
isLoading={(isLoading || agentsLoading) && !isOffline}
+13 -1
View File
@@ -19,11 +19,22 @@ function groupByTeam(agents: Agent[]): Record<string, Agent[]> {
ux_ui: [],
marketing: [],
board: [],
support: [],
management: [],
};
agents.forEach((agent) => {
if (agent.team === Team.BACKEND) {
// CEO-direct helpers — Intake, Secretary, and the root PR Reviewer — are
// board-ADJACENT support roles, not Board members, so they get their own
// group. Cell PR reviewers (team = backend/frontend/ux_ui) stay under their
// cell; only the root reviewer carries team = board.
if (
agent.role === AgentRole.PROMPTER ||
agent.role === AgentRole.SECRETARY ||
(agent.role === AgentRole.PR_REVIEWER && agent.team === Team.BOARD)
) {
groups.support.push(agent);
} else if (agent.team === Team.BACKEND) {
groups.backend.push(agent);
} else if (agent.team === Team.FRONTEND) {
groups.frontend.push(agent);
@@ -53,6 +64,7 @@ const TEAM_LABELS: Record<string, string> = {
ux_ui: "UX/UI",
marketing: "Marketing",
board: "Board",
support: "Support",
management: "Management",
};
@@ -6,7 +6,7 @@ import {
getFrontendAgents,
getUxAgents,
getMarketingAgents,
getOnDemandAgents,
getSupportAgents,
type AgentDefinition,
} from "@/lib/agent-definitions";
import { AgentRole, Team } from "@/types";
@@ -30,7 +30,8 @@ const mainPm = makeAgent("main-pm-1", AgentRole.MAIN_PM, Team.MAIN_PM);
const auditor = makeAgent("auditor-1", AgentRole.AUDITOR, Team.BOARD);
const headMarketing = makeAgent("hm-1", AgentRole.HEAD_MARKETING, null);
const productOwner = makeAgent("po-1", AgentRole.PRODUCT_OWNER, Team.BOARD);
const prReviewer = makeAgent("prr-1", AgentRole.PR_REVIEWER, null);
const prReviewer = makeAgent("prr-root", AgentRole.PR_REVIEWER, Team.BOARD);
const feReviewer = makeAgent("prr-fe", AgentRole.PR_REVIEWER, Team.FRONTEND);
const beCellPm = makeAgent("be-pm", AgentRole.CELL_PM, Team.BACKEND);
const beDev1 = makeAgent("be-dev-1", AgentRole.DEVELOPER, Team.BACKEND);
const feDev1 = makeAgent("fe-dev-1", AgentRole.DEVELOPER, Team.FRONTEND);
@@ -46,6 +47,7 @@ const ALL_AGENTS: AgentDefinition[] = [
headMarketing,
productOwner,
prReviewer,
feReviewer,
beCellPm,
beDev1,
feDev1,
@@ -81,9 +83,11 @@ describe("getBoardAgents", () => {
expect(result).toContainEqual(headMarketing);
});
it("includes PR_REVIEWER role regardless of team", () => {
it("excludes the CEO-direct helpers (root PR Reviewer, Intake, Secretary)", () => {
const result = getBoardAgents(ALL_AGENTS);
expect(result).toContainEqual(prReviewer);
expect(result).not.toContainEqual(prReviewer);
expect(result).not.toContainEqual(prompter);
expect(result).not.toContainEqual(secretary);
});
it("excludes cell agents (BACKEND, FRONTEND, etc.)", () => {
@@ -162,10 +166,11 @@ describe("getBackendAgents", () => {
// ---------------------------------------------------------------------------
describe("getFrontendAgents", () => {
it("returns all agents on the FRONTEND team", () => {
it("returns all agents on the FRONTEND team, including the cell PR reviewer", () => {
const result = getFrontendAgents(ALL_AGENTS);
expect(result).toContainEqual(feDev1);
expect(result).toHaveLength(1);
expect(result).toContainEqual(feReviewer);
expect(result).toHaveLength(2);
});
it("excludes agents from other teams", () => {
@@ -237,37 +242,52 @@ describe("getMarketingAgents", () => {
// getOnDemandAgents
// ---------------------------------------------------------------------------
describe("getOnDemandAgents", () => {
it("returns PROMPTER agents", () => {
const result = getOnDemandAgents(ALL_AGENTS);
describe("getSupportAgents", () => {
it("returns PROMPTER (Intake) agents", () => {
const result = getSupportAgents(ALL_AGENTS);
expect(result).toContainEqual(prompter);
});
it("returns SECRETARY agents", () => {
const result = getOnDemandAgents(ALL_AGENTS);
const result = getSupportAgents(ALL_AGENTS);
expect(result).toContainEqual(secretary);
});
it("excludes agents that are neither PROMPTER nor SECRETARY", () => {
const result = getOnDemandAgents(ALL_AGENTS);
it("returns the root PR Reviewer (team=board)", () => {
const result = getSupportAgents(ALL_AGENTS);
expect(result).toContainEqual(prReviewer);
});
it("excludes cell PR reviewers — they belong to their cell", () => {
const result = getSupportAgents(ALL_AGENTS);
expect(result).not.toContainEqual(feReviewer);
});
it("excludes Board members, the CEO, the Main PM, and cell agents", () => {
const result = getSupportAgents(ALL_AGENTS);
expect(result).not.toContainEqual(productOwner);
expect(result).not.toContainEqual(beDev1);
expect(result).not.toContainEqual(ceo);
expect(result).not.toContainEqual(mainPm);
});
it("returns both on-demand roles and no others", () => {
const result = getOnDemandAgents(ALL_AGENTS);
expect(result).toHaveLength(2);
it("returns exactly the three support roles and no others", () => {
const result = getSupportAgents(ALL_AGENTS);
expect(result).toHaveLength(3);
expect(result.map((a) => a.role)).toEqual(
expect.arrayContaining([AgentRole.PROMPTER, AgentRole.SECRETARY])
expect.arrayContaining([
AgentRole.PROMPTER,
AgentRole.SECRETARY,
AgentRole.PR_REVIEWER,
])
);
});
it("returns an empty array for null input", () => {
expect(getOnDemandAgents(null)).toEqual([]);
expect(getSupportAgents(null)).toEqual([]);
});
it("returns an empty array for undefined input", () => {
expect(getOnDemandAgents(undefined)).toEqual([]);
expect(getSupportAgents(undefined)).toEqual([]);
});
});
+14 -16
View File
@@ -21,16 +21,13 @@ export interface AgentDefinition {
export const getBoardAgents = (agents: AgentDefinition[] | undefined | null) =>
(agents ?? []).filter(
(a) =>
// The CEO is the human operator, not a spawnable agent — exclude it even
// though its record carries team=board.
// MAIN_PM has its own dedicated section below Board, so exclude it here.
a.role !== AgentRole.CEO &&
a.role !== AgentRole.MAIN_PM &&
(a.team === Team.BOARD ||
a.role === AgentRole.HEAD_MARKETING ||
a.role === AgentRole.AUDITOR ||
a.role === AgentRole.PRODUCT_OWNER ||
a.role === AgentRole.PR_REVIEWER)
// The Board is exactly the three review/oversight roles: Product Owner,
// Head of Marketing, and the Auditor. The CEO (human operator) and the
// Main PM are not Board agents, and neither are the CEO-direct helpers
// (Intake, Secretary, root PR Reviewer) — those are grouped as Support.
a.role === AgentRole.PRODUCT_OWNER ||
a.role === AgentRole.HEAD_MARKETING ||
a.role === AgentRole.AUDITOR
);
export const getMainPm = (agents: AgentDefinition[] | undefined | null) =>
@@ -48,13 +45,14 @@ export const getUxAgents = (agents: AgentDefinition[] | undefined | null) =>
export const getMarketingAgents = (agents: AgentDefinition[] | undefined | null) =>
(agents ?? []).filter((a) => a.team === Team.MARKETING);
// On-demand agents (Prompter/Intake, Secretary) are not part of any standing
// cell team — they are spawned on request. Captured by inclusion of their
// explicit roles so new on-demand agents appear automatically when roles
// are added to the enum.
export const getOnDemandAgents = (agents: AgentDefinition[] | undefined | null) =>
// CEO-direct support roles — Intake (Prompter), Secretary, and the root PR
// Reviewer. Board-adjacent and spawned on demand, but NOT Board members. Cell
// PR reviewers carry their cell's team and stay grouped under that cell; only
// the root reviewer carries team=board, which is how it is distinguished here.
export const getSupportAgents = (agents: AgentDefinition[] | undefined | null) =>
(agents ?? []).filter(
(a) =>
a.role === AgentRole.PROMPTER ||
a.role === AgentRole.SECRETARY
a.role === AgentRole.SECRETARY ||
(a.role === AgentRole.PR_REVIEWER && a.team === Team.BOARD)
);