mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
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:
@@ -17,7 +17,7 @@ import {
|
|||||||
getBackendAgents,
|
getBackendAgents,
|
||||||
getFrontendAgents,
|
getFrontendAgents,
|
||||||
getUxAgents,
|
getUxAgents,
|
||||||
getOnDemandAgents,
|
getSupportAgents,
|
||||||
} from "@/lib/agent-definitions";
|
} from "@/lib/agent-definitions";
|
||||||
import {
|
import {
|
||||||
OrchestratorStatusCards,
|
OrchestratorStatusCards,
|
||||||
@@ -136,12 +136,12 @@ export default function AgentsPage() {
|
|||||||
columns={4}
|
columns={4}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* On-Demand section: Prompter/Intake and Secretary agents — only rendered
|
{/* Support section: the CEO-direct helpers — Intake/Prompter, Secretary,
|
||||||
when the API returns at least one matching agent */}
|
and the root PR Reviewer — only rendered when at least one matches */}
|
||||||
{getOnDemandAgents(agents).length > 0 && (
|
{getSupportAgents(agents).length > 0 && (
|
||||||
<AgentGrid
|
<AgentGrid
|
||||||
title="On-Demand"
|
title="Support"
|
||||||
agents={getOnDemandAgents(agents)}
|
agents={getSupportAgents(agents)}
|
||||||
agentStatuses={agentStatuses}
|
agentStatuses={agentStatuses}
|
||||||
agentUsage={agentUsageMap}
|
agentUsage={agentUsageMap}
|
||||||
isLoading={(isLoading || agentsLoading) && !isOffline}
|
isLoading={(isLoading || agentsLoading) && !isOffline}
|
||||||
|
|||||||
@@ -19,11 +19,22 @@ function groupByTeam(agents: Agent[]): Record<string, Agent[]> {
|
|||||||
ux_ui: [],
|
ux_ui: [],
|
||||||
marketing: [],
|
marketing: [],
|
||||||
board: [],
|
board: [],
|
||||||
|
support: [],
|
||||||
management: [],
|
management: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
agents.forEach((agent) => {
|
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);
|
groups.backend.push(agent);
|
||||||
} else if (agent.team === Team.FRONTEND) {
|
} else if (agent.team === Team.FRONTEND) {
|
||||||
groups.frontend.push(agent);
|
groups.frontend.push(agent);
|
||||||
@@ -53,6 +64,7 @@ const TEAM_LABELS: Record<string, string> = {
|
|||||||
ux_ui: "UX/UI",
|
ux_ui: "UX/UI",
|
||||||
marketing: "Marketing",
|
marketing: "Marketing",
|
||||||
board: "Board",
|
board: "Board",
|
||||||
|
support: "Support",
|
||||||
management: "Management",
|
management: "Management",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
getFrontendAgents,
|
getFrontendAgents,
|
||||||
getUxAgents,
|
getUxAgents,
|
||||||
getMarketingAgents,
|
getMarketingAgents,
|
||||||
getOnDemandAgents,
|
getSupportAgents,
|
||||||
type AgentDefinition,
|
type AgentDefinition,
|
||||||
} from "@/lib/agent-definitions";
|
} from "@/lib/agent-definitions";
|
||||||
import { AgentRole, Team } from "@/types";
|
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 auditor = makeAgent("auditor-1", AgentRole.AUDITOR, Team.BOARD);
|
||||||
const headMarketing = makeAgent("hm-1", AgentRole.HEAD_MARKETING, null);
|
const headMarketing = makeAgent("hm-1", AgentRole.HEAD_MARKETING, null);
|
||||||
const productOwner = makeAgent("po-1", AgentRole.PRODUCT_OWNER, Team.BOARD);
|
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 beCellPm = makeAgent("be-pm", AgentRole.CELL_PM, Team.BACKEND);
|
||||||
const beDev1 = makeAgent("be-dev-1", AgentRole.DEVELOPER, Team.BACKEND);
|
const beDev1 = makeAgent("be-dev-1", AgentRole.DEVELOPER, Team.BACKEND);
|
||||||
const feDev1 = makeAgent("fe-dev-1", AgentRole.DEVELOPER, Team.FRONTEND);
|
const feDev1 = makeAgent("fe-dev-1", AgentRole.DEVELOPER, Team.FRONTEND);
|
||||||
@@ -46,6 +47,7 @@ const ALL_AGENTS: AgentDefinition[] = [
|
|||||||
headMarketing,
|
headMarketing,
|
||||||
productOwner,
|
productOwner,
|
||||||
prReviewer,
|
prReviewer,
|
||||||
|
feReviewer,
|
||||||
beCellPm,
|
beCellPm,
|
||||||
beDev1,
|
beDev1,
|
||||||
feDev1,
|
feDev1,
|
||||||
@@ -81,9 +83,11 @@ describe("getBoardAgents", () => {
|
|||||||
expect(result).toContainEqual(headMarketing);
|
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);
|
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.)", () => {
|
it("excludes cell agents (BACKEND, FRONTEND, etc.)", () => {
|
||||||
@@ -162,10 +166,11 @@ describe("getBackendAgents", () => {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
describe("getFrontendAgents", () => {
|
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);
|
const result = getFrontendAgents(ALL_AGENTS);
|
||||||
expect(result).toContainEqual(feDev1);
|
expect(result).toContainEqual(feDev1);
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toContainEqual(feReviewer);
|
||||||
|
expect(result).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("excludes agents from other teams", () => {
|
it("excludes agents from other teams", () => {
|
||||||
@@ -237,37 +242,52 @@ describe("getMarketingAgents", () => {
|
|||||||
// getOnDemandAgents
|
// getOnDemandAgents
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
describe("getOnDemandAgents", () => {
|
describe("getSupportAgents", () => {
|
||||||
it("returns PROMPTER agents", () => {
|
it("returns PROMPTER (Intake) agents", () => {
|
||||||
const result = getOnDemandAgents(ALL_AGENTS);
|
const result = getSupportAgents(ALL_AGENTS);
|
||||||
expect(result).toContainEqual(prompter);
|
expect(result).toContainEqual(prompter);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns SECRETARY agents", () => {
|
it("returns SECRETARY agents", () => {
|
||||||
const result = getOnDemandAgents(ALL_AGENTS);
|
const result = getSupportAgents(ALL_AGENTS);
|
||||||
expect(result).toContainEqual(secretary);
|
expect(result).toContainEqual(secretary);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("excludes agents that are neither PROMPTER nor SECRETARY", () => {
|
it("returns the root PR Reviewer (team=board)", () => {
|
||||||
const result = getOnDemandAgents(ALL_AGENTS);
|
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(beDev1);
|
||||||
expect(result).not.toContainEqual(ceo);
|
expect(result).not.toContainEqual(ceo);
|
||||||
expect(result).not.toContainEqual(mainPm);
|
expect(result).not.toContainEqual(mainPm);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns both on-demand roles and no others", () => {
|
it("returns exactly the three support roles and no others", () => {
|
||||||
const result = getOnDemandAgents(ALL_AGENTS);
|
const result = getSupportAgents(ALL_AGENTS);
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(3);
|
||||||
expect(result.map((a) => a.role)).toEqual(
|
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", () => {
|
it("returns an empty array for null input", () => {
|
||||||
expect(getOnDemandAgents(null)).toEqual([]);
|
expect(getSupportAgents(null)).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns an empty array for undefined input", () => {
|
it("returns an empty array for undefined input", () => {
|
||||||
expect(getOnDemandAgents(undefined)).toEqual([]);
|
expect(getSupportAgents(undefined)).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,16 +21,13 @@ export interface AgentDefinition {
|
|||||||
export const getBoardAgents = (agents: AgentDefinition[] | undefined | null) =>
|
export const getBoardAgents = (agents: AgentDefinition[] | undefined | null) =>
|
||||||
(agents ?? []).filter(
|
(agents ?? []).filter(
|
||||||
(a) =>
|
(a) =>
|
||||||
// The CEO is the human operator, not a spawnable agent — exclude it even
|
// The Board is exactly the three review/oversight roles: Product Owner,
|
||||||
// though its record carries team=board.
|
// Head of Marketing, and the Auditor. The CEO (human operator) and the
|
||||||
// MAIN_PM has its own dedicated section below Board, so exclude it here.
|
// Main PM are not Board agents, and neither are the CEO-direct helpers
|
||||||
a.role !== AgentRole.CEO &&
|
// (Intake, Secretary, root PR Reviewer) — those are grouped as Support.
|
||||||
a.role !== AgentRole.MAIN_PM &&
|
a.role === AgentRole.PRODUCT_OWNER ||
|
||||||
(a.team === Team.BOARD ||
|
a.role === AgentRole.HEAD_MARKETING ||
|
||||||
a.role === AgentRole.HEAD_MARKETING ||
|
a.role === AgentRole.AUDITOR
|
||||||
a.role === AgentRole.AUDITOR ||
|
|
||||||
a.role === AgentRole.PRODUCT_OWNER ||
|
|
||||||
a.role === AgentRole.PR_REVIEWER)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getMainPm = (agents: AgentDefinition[] | undefined | null) =>
|
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) =>
|
export const getMarketingAgents = (agents: AgentDefinition[] | undefined | null) =>
|
||||||
(agents ?? []).filter((a) => a.team === Team.MARKETING);
|
(agents ?? []).filter((a) => a.team === Team.MARKETING);
|
||||||
|
|
||||||
// On-demand agents (Prompter/Intake, Secretary) are not part of any standing
|
// CEO-direct support roles — Intake (Prompter), Secretary, and the root PR
|
||||||
// cell team — they are spawned on request. Captured by inclusion of their
|
// Reviewer. Board-adjacent and spawned on demand, but NOT Board members. Cell
|
||||||
// explicit roles so new on-demand agents appear automatically when roles
|
// PR reviewers carry their cell's team and stay grouped under that cell; only
|
||||||
// are added to the enum.
|
// the root reviewer carries team=board, which is how it is distinguished here.
|
||||||
export const getOnDemandAgents = (agents: AgentDefinition[] | undefined | null) =>
|
export const getSupportAgents = (agents: AgentDefinition[] | undefined | null) =>
|
||||||
(agents ?? []).filter(
|
(agents ?? []).filter(
|
||||||
(a) =>
|
(a) =>
|
||||||
a.role === AgentRole.PROMPTER ||
|
a.role === AgentRole.PROMPTER ||
|
||||||
a.role === AgentRole.SECRETARY
|
a.role === AgentRole.SECRETARY ||
|
||||||
|
(a.role === AgentRole.PR_REVIEWER && a.team === Team.BOARD)
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user