diff --git a/apps/web/src/app/api/microdao/[slug]/agents/route.ts b/apps/web/src/app/api/microdao/[slug]/agents/route.ts
new file mode 100644
index 00000000..ba7bf82a
--- /dev/null
+++ b/apps/web/src/app/api/microdao/[slug]/agents/route.ts
@@ -0,0 +1,42 @@
+import { NextRequest, NextResponse } from "next/server";
+
+const CITY_API_URL = process.env.CITY_API_URL || "http://localhost:7001";
+
+/**
+ * GET /api/microdao/[slug]/agents
+ * Get all agents for a MicroDAO
+ */
+export async function GET(
+ request: NextRequest,
+ context: { params: Promise<{ slug: string }> }
+) {
+ try {
+ const { slug } = await context.params;
+
+ const response = await fetch(`${CITY_API_URL}/city/microdao/${slug}/agents`, {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ cache: "no-store",
+ });
+
+ if (!response.ok) {
+ const text = await response.text();
+ console.error("Failed to get microdao agents:", response.status, text);
+ return NextResponse.json(
+ { error: `Backend error: ${response.status}` },
+ { status: response.status }
+ );
+ }
+
+ const data = await response.json();
+ return NextResponse.json(data);
+ } catch (error) {
+ console.error("Error getting microdao agents:", error);
+ return NextResponse.json(
+ { error: "Internal server error" },
+ { status: 500 }
+ );
+ }
+}
+
diff --git a/apps/web/src/app/microdao/[slug]/page.tsx b/apps/web/src/app/microdao/[slug]/page.tsx
index ef936e12..64cf0960 100644
--- a/apps/web/src/app/microdao/[slug]/page.tsx
+++ b/apps/web/src/app/microdao/[slug]/page.tsx
@@ -2,11 +2,12 @@
import { useParams, useRouter } from "next/navigation";
import Link from "next/link";
-import { useMicrodaoDetail, useMicrodaoRooms } from "@/hooks/useMicrodao";
+import { useMicrodaoDetail, useMicrodaoRooms, useMicrodaoAgents } from "@/hooks/useMicrodao";
import { DISTRICT_COLORS } from "@/lib/microdao";
import { MicrodaoVisibilityCard } from "@/components/microdao/MicrodaoVisibilityCard";
import { MicrodaoRoomsSection } from "@/components/microdao/MicrodaoRoomsSection";
import { MicrodaoRoomsAdminPanel } from "@/components/microdao/MicrodaoRoomsAdminPanel";
+import { MicrodaoAgentsSection } from "@/components/microdao/MicrodaoAgentsSection";
import { ChevronLeft, Users, MessageSquare, Crown, Building2, Globe, Lock, Layers, BarChart3, Bot, MessageCircle } from "lucide-react";
import { CityChatWidget } from "@/components/city/CityChatWidget";
import { AgentChatWidget } from "@/components/chat/AgentChatWidget";
@@ -18,6 +19,7 @@ export default function MicrodaoDetailPage() {
const slug = params?.slug as string;
const { microdao, isLoading, error, mutate: refreshMicrodao } = useMicrodaoDetail(slug);
const { rooms, mutate: refreshRooms } = useMicrodaoRooms(slug);
+ const { agents: microdaoAgents } = useMicrodaoAgents(slug);
const handleRoomUpdated = () => {
refreshRooms();
@@ -224,48 +226,53 @@ export default function MicrodaoDetailPage() {
onEnsureOrchestratorRoom={handleEnsureOrchestratorRoom}
/>
-
- {/* Agents */}
-
-
-
- Агентська команда
- ({microdao.agents.length})
-
+ {/* MicroDAO Agents Section - using new API */}
+
- {microdao.agents.length === 0 ? (
- Агенти ще не привʼязані.
- ) : (
-
- {microdao.agents.map((a) => (
-
-
-
- {a.display_name}
- {a.agent_id === microdao.orchestrator_agent_id && (
-
+
+ {/* Legacy Agents (from microdao detail) - hidden if new API has data */}
+ {microdaoAgents.length === 0 && (
+
+
+
+ Агентська команда
+ ({microdao.agents.length})
+
+
+ {microdao.agents.length === 0 ? (
+ Агенти ще не привʼязані.
+ ) : (
+
+ {microdao.agents.map((a) => (
+
+
+
+ {a.display_name}
+ {a.agent_id === microdao.orchestrator_agent_id && (
+
+ )}
+
+ {a.role && (
+
{a.role}
)}
-
- {a.role && (
-
{a.role}
+
+ {a.is_core && (
+
+ Core
+
)}
- {a.is_core && (
-
- Core
-
- )}
-
- ))}
-
- )}
-
+ ))}
+
+ )}
+
+ )}
{/* Public Citizens */}
diff --git a/apps/web/src/components/microdao/MicrodaoAgentsSection.tsx b/apps/web/src/components/microdao/MicrodaoAgentsSection.tsx
new file mode 100644
index 00000000..bf7456b7
--- /dev/null
+++ b/apps/web/src/components/microdao/MicrodaoAgentsSection.tsx
@@ -0,0 +1,214 @@
+"use client";
+
+import Link from "next/link";
+import { Bot, Crown, Users, Shield, Sparkles } from "lucide-react";
+import { MicrodaoAgent } from "@/hooks/useMicrodao";
+
+interface MicrodaoAgentsSectionProps {
+ agents: MicrodaoAgent[];
+ microdaoSlug?: string;
+}
+
+const ROLE_META: Record = {
+ orchestrator: {
+ label: "Orchestrator",
+ chipClass: "bg-fuchsia-500/10 text-fuchsia-300 border-fuchsia-500/30",
+ icon: ,
+ },
+ district_lead: {
+ label: "District Lead",
+ chipClass: "bg-purple-500/10 text-purple-300 border-purple-500/30",
+ icon: ,
+ },
+ core_team: {
+ label: "Core Team",
+ chipClass: "bg-indigo-500/10 text-indigo-300 border-indigo-500/30",
+ icon: ,
+ },
+ member: {
+ label: "Member",
+ chipClass: "bg-slate-500/10 text-slate-300 border-slate-500/30",
+ icon: ,
+ },
+ guardian: {
+ label: "Guardian",
+ chipClass: "bg-rose-500/10 text-rose-300 border-rose-500/30",
+ icon: ,
+ },
+ steward: {
+ label: "Steward",
+ chipClass: "bg-emerald-500/10 text-emerald-300 border-emerald-500/30",
+ icon: ,
+ },
+};
+
+const STATUS_COLORS: Record = {
+ active: "bg-emerald-400",
+ inactive: "bg-slate-500",
+ suspended: "bg-yellow-400",
+ offline: "bg-slate-600",
+};
+
+export function MicrodaoAgentsSection({ agents, microdaoSlug }: MicrodaoAgentsSectionProps) {
+ if (!agents || agents.length === 0) {
+ return (
+
+
+
+ Агенти MicroDAO
+
+
+ Для цього MicroDAO ще не призначені агенти.
+
+
+ );
+ }
+
+ // Group agents by role
+ const orchestrators = agents.filter(a => a.role === "orchestrator" || a.role === "district_lead");
+ const coreTeam = agents.filter(a => a.role === "core_team" || a.is_core);
+ const others = agents.filter(a => !orchestrators.includes(a) && !coreTeam.includes(a));
+
+ return (
+
+
+
+
+ Агенти MicroDAO
+ ({agents.length})
+
+
+
+ {/* Orchestrator(s) */}
+ {orchestrators.length > 0 && (
+
+
+ Orchestrator
+
+
+ {orchestrators.map(agent => (
+
+ ))}
+
+
+ )}
+
+ {/* Core Team */}
+ {coreTeam.length > 0 && (
+
+
+ Core Team
+
+
+ {coreTeam.map(agent => (
+
+ ))}
+
+
+ )}
+
+ {/* Other Members */}
+ {others.length > 0 && (
+
+
+ Members
+
+
+ {others.map(agent => (
+
+ ))}
+
+
+ )}
+
+ );
+}
+
+interface AgentCardProps {
+ agent: MicrodaoAgent;
+ featured?: boolean;
+ compact?: boolean;
+}
+
+function AgentCard({ agent, featured = false, compact = false }: AgentCardProps) {
+ const roleMeta = ROLE_META[agent.role] || ROLE_META.member;
+ const statusColor = STATUS_COLORS[agent.status] || STATUS_COLORS.offline;
+
+ if (compact) {
+ return (
+
+
+
+
+ {agent.avatar_url ? (
+

+ ) : (
+
+ )}
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {/* Avatar */}
+
+
+ {agent.avatar_url ? (
+

+ ) : (
+
+ )}
+
+
+
+
+ {/* Info */}
+
+
+
+ {agent.name}
+
+ {agent.is_core && (
+
+ Core
+
+ )}
+
+
+ {/* Role badge */}
+
+ {roleMeta.icon}
+ {roleMeta.label}
+
+
+ {/* Kind and Gov Level */}
+ {(agent.kind || agent.gov_level) && (
+
+ {agent.kind && {agent.kind}}
+ {agent.kind && agent.gov_level && •}
+ {agent.gov_level && {agent.gov_level}}
+
+ )}
+
+
+
+
+ );
+}
+
diff --git a/apps/web/src/components/microdao/MicrodaoRoomsSection.tsx b/apps/web/src/components/microdao/MicrodaoRoomsSection.tsx
index 4a9f12d8..b4d52fc9 100644
--- a/apps/web/src/components/microdao/MicrodaoRoomsSection.tsx
+++ b/apps/web/src/components/microdao/MicrodaoRoomsSection.tsx
@@ -1,7 +1,7 @@
"use client";
import Link from "next/link";
-import { MessageCircle, Home, Users, FlaskConical, Shield, Gavel, Hash, Users2, Bot, PlusCircle } from "lucide-react";
+import { MessageCircle, Home, Users, FlaskConical, Shield, Gavel, Hash, Users2, Bot, PlusCircle, Crown } from "lucide-react";
import { CityRoomSummary } from "@/lib/types/microdao";
import { CityChatWidget } from "@/components/city/CityChatWidget";
import { Button } from "@/components/ui/button";
@@ -51,6 +51,58 @@ const ROLE_META: Record,
},
+ // New room roles for MicroDAO
+ operations: {
+ label: "Operations",
+ chipClass: "bg-orange-500/10 text-orange-300 border-orange-500/30",
+ icon: ,
+ },
+ knowledge: {
+ label: "Knowledge Base",
+ chipClass: "bg-cyan-500/10 text-cyan-300 border-cyan-500/30",
+ icon: ,
+ },
+ treasury: {
+ label: "Treasury",
+ chipClass: "bg-yellow-500/10 text-yellow-300 border-yellow-500/30",
+ icon: ,
+ },
+ "ai-core": {
+ label: "AI Core",
+ chipClass: "bg-purple-500/10 text-purple-300 border-purple-500/30",
+ icon: ,
+ },
+ // District-specific
+ events: {
+ label: "Events",
+ chipClass: "bg-pink-500/10 text-pink-300 border-pink-500/30",
+ icon: ,
+ },
+ masters: {
+ label: "Masters",
+ chipClass: "bg-indigo-500/10 text-indigo-300 border-indigo-500/30",
+ icon: ,
+ },
+ supply: {
+ label: "Supply Chain",
+ chipClass: "bg-green-500/10 text-green-300 border-green-500/30",
+ icon: ,
+ },
+ producers: {
+ label: "Producers",
+ chipClass: "bg-lime-500/10 text-lime-300 border-lime-500/30",
+ icon: ,
+ },
+ compute: {
+ label: "Compute Grid",
+ chipClass: "bg-amber-500/10 text-amber-300 border-amber-500/30",
+ icon: ,
+ },
+ providers: {
+ label: "Providers",
+ chipClass: "bg-orange-500/10 text-orange-300 border-orange-500/30",
+ icon: ,
+ },
};
export function MicrodaoRoomsSection({
diff --git a/apps/web/src/hooks/useMicrodao.ts b/apps/web/src/hooks/useMicrodao.ts
index f7d7f60c..7e4fa04c 100644
--- a/apps/web/src/hooks/useMicrodao.ts
+++ b/apps/web/src/hooks/useMicrodao.ts
@@ -215,3 +215,90 @@ export function useMicrodaoRooms(
mutate: fetchData,
};
}
+
+
+// =============================================================================
+// useMicrodaoAgents - fetch all agents for a MicroDAO
+// =============================================================================
+
+export interface MicrodaoAgent {
+ id: string;
+ name: string;
+ kind: string;
+ status: string;
+ avatar_url?: string;
+ gov_level?: string;
+ role: string;
+ is_core: boolean;
+}
+
+interface UseMicrodaoAgentsOptions {
+ refreshInterval?: number;
+ enabled?: boolean;
+}
+
+interface UseMicrodaoAgentsResult {
+ agents: MicrodaoAgent[];
+ microdaoId: string | null;
+ microdaoSlug: string | null;
+ isLoading: boolean;
+ error: Error | null;
+ mutate: () => Promise;
+}
+
+export function useMicrodaoAgents(
+ slug: string | undefined,
+ options: UseMicrodaoAgentsOptions = {}
+): UseMicrodaoAgentsResult {
+ const { refreshInterval = 60000, enabled = true } = options;
+
+ const [agents, setAgents] = useState([]);
+ const [microdaoId, setMicrodaoId] = useState(null);
+ const [microdaoSlug, setMicrodaoSlug] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const fetchData = useCallback(async () => {
+ if (!slug || !enabled) return;
+
+ try {
+ setIsLoading(true);
+ const res = await fetch(`/api/microdao/${encodeURIComponent(slug)}/agents`);
+
+ if (!res.ok) {
+ throw new Error(`Failed to fetch agents: ${res.status}`);
+ }
+
+ const data = await res.json();
+ setAgents(data.agents || []);
+ setMicrodaoId(data.microdao_id);
+ setMicrodaoSlug(data.microdao_slug);
+ } catch (err) {
+ setError(err instanceof Error ? err : new Error(String(err)));
+ } finally {
+ setIsLoading(false);
+ }
+ }, [slug, enabled]);
+
+ // Initial fetch
+ useEffect(() => {
+ fetchData();
+ }, [fetchData]);
+
+ // Auto-refresh
+ useEffect(() => {
+ if (!enabled || refreshInterval <= 0) return;
+
+ const interval = setInterval(fetchData, refreshInterval);
+ return () => clearInterval(interval);
+ }, [fetchData, refreshInterval, enabled]);
+
+ return {
+ agents,
+ microdaoId,
+ microdaoSlug,
+ isLoading,
+ error,
+ mutate: fetchData,
+ };
+}