diff --git a/src/renderer/components/organization/OrgChart.tsx b/src/renderer/components/organization/OrgChart.tsx index 338cdb60..4e093768 100644 --- a/src/renderer/components/organization/OrgChart.tsx +++ b/src/renderer/components/organization/OrgChart.tsx @@ -6,6 +6,16 @@ import { EmptyState } from "../ui"; import { useT } from "../../i18n"; import { buildOrgTree, layoutForest, flattenLayout, collectEdges, statusDotColor, CARD_W, CARD_H, CHART_PADDING } from "./orgTreeLayout"; +const LEGEND_ORDER: Array<{ status: string; labelKey: string }> = [ + { status: "running", labelKey: "agent.form.statusRunning" }, + { status: "active", labelKey: "agent.form.statusActive" }, + { status: "idle", labelKey: "agent.form.statusIdle" }, + { status: "paused", labelKey: "agent.form.statusPaused" }, + { status: "pending_approval", labelKey: "agent.pendingApproval" }, + { status: "error", labelKey: "agent.form.statusError" }, + { status: "terminated", labelKey: "agent.form.statusTerminated" }, +]; + export function OrgChart({ agents, connectors, onSelectAgent }: { agents: AgentRecord[]; connectors?: ConnectorRecord[]; onSelectAgent?: (agentId: string) => void }) { const t = useT(); const containerRef = useRef(null); @@ -18,6 +28,11 @@ export function OrgChart({ agents, connectors, onSelectAgent }: { agents: AgentR const layout = useMemo(() => layoutForest(orgTree), [orgTree]); const allNodes = useMemo(() => flattenLayout(layout), [layout]); const edges = useMemo(() => collectEdges(layout), [layout]); + const legendEntries = useMemo(() => { + const present = new Set(); + for (const a of agents) present.add(a.status); + return LEGEND_ORDER.filter((entry) => present.has(entry.status)); + }, [agents]); const bounds = useMemo(() => { if (allNodes.length === 0) return { width: 800, height: 600 }; let maxX = 0, maxY = 0; @@ -85,6 +100,17 @@ export function OrgChart({ agents, connectors, onSelectAgent }: { agents: AgentR ))} + {legendEntries.length > 0 ? ( +
+
{t("org.statusLegend")}
+ {legendEntries.map((entry) => ( +
+
+ ))} +
+ ) : null} {edges.map(({ parent, child }) => { diff --git a/src/renderer/components/organization/agent-form/agent-form-fields/EditFormBody.tsx b/src/renderer/components/organization/agent-form/agent-form-fields/EditFormBody.tsx index 5578c367..e8ea775b 100644 --- a/src/renderer/components/organization/agent-form/agent-form-fields/EditFormBody.tsx +++ b/src/renderer/components/organization/agent-form/agent-form-fields/EditFormBody.tsx @@ -78,6 +78,11 @@ export function EditFormBody({ const [saveError, setSaveError] = useState(null); const [confirmingDelete, setConfirmingDelete] = useState(false); const pendingApproval = agent.status === "pending_approval"; + // Root agents (CEO / no reportsTo) must not be terminable or deletable from + // this form — their removal requires reassigning direct reports first, and + // we would rather the user see a disabled control + reason than discover + // the orphaning side-effect only after clicking through a confirm. + const isRootAgent = !agent.reportsTo; const skills = useAppStore((s) => s.skills); const setSkills = useAppStore((s) => s.setSkills); @@ -211,13 +216,16 @@ export function EditFormBody({ { value: "running", label: t("agent.form.statusRunning") }, { value: "paused", label: t("agent.form.statusPaused") }, { value: "error", label: t("agent.form.statusError") }, - { value: "terminated", label: t("agent.form.statusTerminated") }, + ...(isRootAgent ? [] : [{ value: "terminated", label: t("agent.form.statusTerminated") }]), ] } /> {pendingApproval ? (

{t("agent.pendingHireNote")}

) : null} + {isRootAgent && !pendingApproval ? ( +

{t("agent.form.rootTerminateBlocked")}

+ ) : null}