Add agent role management system for Bees-AgenticWorkers integration

Backend:
- Database migration for agent role fields and predefined roles
- AgentRole and AgentCollaboration models
- Updated Agent model with role-based fields

Frontend:
- AgentRoleSelector component for role assignment
- CollaborationDashboard for monitoring agent interactions
- AgentManagement interface with role analytics

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-27 15:24:53 +10:00
parent ef3b61740b
commit 9262e63374
7 changed files with 1137 additions and 2 deletions

View File

@@ -1,4 +1,5 @@
from . import agent from . import agent
from . import agent_role
from . import project from . import project
from . import task from . import task
from . import sqlalchemy_models from . import sqlalchemy_models

View File

@@ -1,4 +1,4 @@
from sqlalchemy import Column, Integer, String, DateTime, JSON from sqlalchemy import Column, Integer, String, DateTime, JSON, Text
from sqlalchemy.sql import func from sqlalchemy.sql import func
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from ..core.database import Base from ..core.database import Base
@@ -24,6 +24,14 @@ class Agent(Base):
updated_at = Column(DateTime(timezone=True), onupdate=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now())
last_seen = Column(DateTime(timezone=True), nullable=True) last_seen = Column(DateTime(timezone=True), nullable=True)
# Role-based collaboration fields
role = Column(String, nullable=True) # Role from Bees-AgenticWorkers
system_prompt = Column(Text, nullable=True) # Role-specific system prompt
reports_to = Column(JSON, nullable=True) # Array of roles this agent reports to
expertise = Column(JSON, nullable=True) # Array of expertise areas
deliverables = Column(JSON, nullable=True) # Array of deliverables
collaboration_settings = Column(JSON, nullable=True) # Collaboration preferences
# Relationships # Relationships
tasks = relationship("Task", back_populates="assigned_agent") tasks = relationship("Task", back_populates="assigned_agent")
@@ -45,5 +53,13 @@ class Agent(Base):
"performance_targets": self.performance_targets, "performance_targets": self.performance_targets,
"created_at": self.created_at.isoformat() if self.created_at else None, "created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None,
"last_seen": self.last_seen.isoformat() if self.last_seen else None "last_seen": self.last_seen.isoformat() if self.last_seen else None,
# Role-based fields
"role": self.role,
"system_prompt": self.system_prompt,
"reports_to": self.reports_to,
"expertise": self.expertise,
"deliverables": self.deliverables,
"collaboration_settings": self.collaboration_settings
} }

View File

@@ -0,0 +1,69 @@
from sqlalchemy import Column, String, DateTime, JSON, Text
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from ..core.database import Base
class AgentRole(Base):
__tablename__ = "agent_roles"
id = Column(String, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False, index=True) # Role identifier (e.g., "senior_software_architect")
display_name = Column(String, nullable=False) # Human-readable name
system_prompt = Column(Text, nullable=False) # Role-specific system prompt
reports_to = Column(JSON, nullable=True) # Array of roles this role reports to
expertise = Column(JSON, nullable=True, index=True) # Array of expertise areas
deliverables = Column(JSON, nullable=True) # Array of deliverables
capabilities = Column(JSON, nullable=True) # Array of capabilities
collaboration_defaults = Column(JSON, nullable=True) # Default collaboration settings
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"display_name": self.display_name,
"system_prompt": self.system_prompt,
"reports_to": self.reports_to,
"expertise": self.expertise,
"deliverables": self.deliverables,
"capabilities": self.capabilities,
"collaboration_defaults": self.collaboration_defaults,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None
}
class AgentCollaboration(Base):
__tablename__ = "agent_collaborations"
id = Column(String, primary_key=True, index=True)
from_agent_id = Column(String, nullable=False, index=True) # References agents(id)
to_agent_id = Column(String, nullable=True, index=True) # References agents(id), can be null for broadcasts
message_type = Column(String, nullable=False) # Type of collaboration message
thread_id = Column(String, nullable=True, index=True) # Conversation thread ID
project_id = Column(String, nullable=True, index=True) # Associated project
message_data = Column(JSON, nullable=True) # Original message data
response_data = Column(JSON, nullable=True) # Response data
status = Column(String, default="pending", index=True) # pending, responded, escalated, resolved
priority = Column(String, default="medium", index=True) # low, medium, high, urgent
created_at = Column(DateTime(timezone=True), server_default=func.now())
responded_at = Column(DateTime(timezone=True), nullable=True)
resolved_at = Column(DateTime(timezone=True), nullable=True)
def to_dict(self):
return {
"id": self.id,
"from_agent_id": self.from_agent_id,
"to_agent_id": self.to_agent_id,
"message_type": self.message_type,
"thread_id": self.thread_id,
"project_id": self.project_id,
"message_data": self.message_data,
"response_data": self.response_data,
"status": self.status,
"priority": self.priority,
"created_at": self.created_at.isoformat() if self.created_at else None,
"responded_at": self.responded_at.isoformat() if self.responded_at else None,
"resolved_at": self.resolved_at.isoformat() if self.resolved_at else None
}

View File

@@ -0,0 +1,171 @@
-- Migration to add role-based collaboration fields to agents table
-- Add role-based fields to agents table
ALTER TABLE agents ADD COLUMN role VARCHAR(255);
ALTER TABLE agents ADD COLUMN system_prompt TEXT;
ALTER TABLE agents ADD COLUMN reports_to JSONB; -- Array of roles this agent reports to
ALTER TABLE agents ADD COLUMN expertise JSONB; -- Array of expertise areas
ALTER TABLE agents ADD COLUMN deliverables JSONB; -- Array of deliverables this agent produces
ALTER TABLE agents ADD COLUMN collaboration_settings JSONB; -- Collaboration preferences
-- Add indexes for role-based queries
CREATE INDEX idx_agents_role ON agents(role);
CREATE INDEX idx_agents_expertise ON agents USING GIN(expertise);
CREATE INDEX idx_agents_reports_to ON agents USING GIN(reports_to);
-- Create agent_roles table for predefined role definitions
CREATE TABLE agent_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) UNIQUE NOT NULL,
display_name VARCHAR(255) NOT NULL,
system_prompt TEXT NOT NULL,
reports_to JSONB, -- Array of roles this role reports to
expertise JSONB, -- Array of expertise areas
deliverables JSONB, -- Array of deliverables
capabilities JSONB, -- Array of capabilities
collaboration_defaults JSONB, -- Default collaboration settings
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Create index for agent_roles
CREATE INDEX idx_agent_roles_name ON agent_roles(name);
CREATE INDEX idx_agent_roles_expertise ON agent_roles USING GIN(expertise);
-- Insert predefined roles from Bees-AgenticWorkers.md
INSERT INTO agent_roles (name, display_name, system_prompt, reports_to, expertise, deliverables, capabilities, collaboration_defaults) VALUES
(
'senior_software_architect',
'Senior Software Architect',
'You are the **Senior Software Architect**. You define the system''s overall structure, select tech stacks, and ensure long-term maintainability.
* **Responsibilities:** Draft high-level architecture diagrams, define API contracts, set coding standards, mentor engineering leads.
* **Expertise:** Deep experience in multiple programming paradigms, distributed systems, security models, and cloud architectures.
* **Reports To:** Product Owner / Technical Director.
* **Deliverables:** Architecture blueprints, tech stack decisions, integration strategies, and review sign-offs on major design changes.',
'["product_owner", "technical_director"]'::jsonb,
'["architecture", "distributed_systems", "security", "cloud_architectures", "api_design"]'::jsonb,
'["architecture_blueprints", "tech_stack_decisions", "integration_strategies", "design_reviews"]'::jsonb,
'["task-coordination", "meta-discussion", "architecture", "code-review", "mentoring"]'::jsonb,
'{
"preferred_message_types": ["coordination_request", "meta_discussion", "escalation_trigger"],
"auto_subscribe_to_roles": ["lead_designer", "security_expert", "systems_engineer"],
"auto_subscribe_to_expertise": ["architecture", "security", "infrastructure"],
"response_timeout_seconds": 300,
"max_collaboration_depth": 5,
"escalation_threshold": 3
}'::jsonb
),
(
'lead_designer',
'Lead Designer',
'You are the **Lead Designer**. You guide the creative vision and maintain design cohesion across the product.
* **Responsibilities:** Oversee UX flow, wireframes, and feature design; ensure consistency of theme and style; mediate between product vision and technical constraints.
* **Expertise:** UI/UX principles, accessibility, information architecture, Figma/Sketch proficiency.
* **Reports To:** Product Owner.
* **Deliverables:** Style guides, wireframes, feature specs, and iterative design documentation.',
'["product_owner"]'::jsonb,
'["ui_ux", "accessibility", "information_architecture", "design_systems", "user_research"]'::jsonb,
'["style_guides", "wireframes", "feature_specs", "design_documentation"]'::jsonb,
'["task-coordination", "meta-discussion", "design", "user_experience"]'::jsonb,
'{
"preferred_message_types": ["task_help_request", "coordination_request", "meta_discussion"],
"auto_subscribe_to_roles": ["ui_ux_designer", "frontend_developer"],
"auto_subscribe_to_expertise": ["design", "frontend", "user_experience"],
"response_timeout_seconds": 180,
"max_collaboration_depth": 3,
"escalation_threshold": 2
}'::jsonb
),
(
'security_expert',
'Security Expert',
'You are the **Security Expert**. You ensure the system is hardened against vulnerabilities.
* **Responsibilities:** Conduct threat modeling, penetration tests, code reviews for security flaws, and define access control policies.
* **Expertise:** Cybersecurity frameworks (OWASP, NIST), encryption, key management, zero-trust systems.
* **Reports To:** Senior Software Architect.
* **Deliverables:** Security audits, vulnerability reports, risk mitigation plans, compliance documentation.',
'["senior_software_architect"]'::jsonb,
'["cybersecurity", "owasp", "nist", "encryption", "key_management", "zero_trust", "penetration_testing"]'::jsonb,
'["security_audits", "vulnerability_reports", "risk_mitigation_plans", "compliance_documentation"]'::jsonb,
'["task-coordination", "meta-discussion", "security-analysis", "code-review", "threat-modeling"]'::jsonb,
'{
"preferred_message_types": ["dependency_alert", "task_help_request", "escalation_trigger"],
"auto_subscribe_to_roles": ["backend_developer", "devops_engineer", "senior_software_architect"],
"auto_subscribe_to_expertise": ["security", "backend", "infrastructure"],
"response_timeout_seconds": 120,
"max_collaboration_depth": 4,
"escalation_threshold": 1
}'::jsonb
),
(
'frontend_developer',
'Frontend Developer',
'You are the **Frontend Developer**. You turn designs into interactive interfaces.
* **Responsibilities:** Build UI components, optimize performance, ensure cross-browser/device compatibility, and integrate frontend with backend APIs.
* **Expertise:** HTML, CSS, JavaScript/TypeScript, React/Vue/Angular, accessibility standards.
* **Reports To:** Frontend Lead or Senior Architect.
* **Deliverables:** Functional UI screens, reusable components, and documented frontend code.',
'["frontend_lead", "senior_software_architect"]'::jsonb,
'["html", "css", "javascript", "typescript", "react", "vue", "angular", "accessibility"]'::jsonb,
'["ui_screens", "reusable_components", "frontend_code", "documentation"]'::jsonb,
'["task-coordination", "meta-discussion", "frontend", "ui_development", "component_design"]'::jsonb,
'{
"preferred_message_types": ["task_help_request", "coordination_request", "task_help_response"],
"auto_subscribe_to_roles": ["ui_ux_designer", "backend_developer", "lead_designer"],
"auto_subscribe_to_expertise": ["design", "backend", "api_integration"],
"response_timeout_seconds": 180,
"max_collaboration_depth": 3,
"escalation_threshold": 2
}'::jsonb
),
(
'backend_developer',
'Backend Developer',
'You are the **Backend Developer**. You create APIs, logic, and server-side integrations.
* **Responsibilities:** Implement core logic, manage data pipelines, enforce security, and support scaling strategies.
* **Expertise:** Server frameworks, REST/GraphQL APIs, authentication, caching, microservices.
* **Reports To:** Backend Lead or Senior Architect.
* **Deliverables:** API endpoints, backend services, unit tests, and deployment-ready server code.',
'["backend_lead", "senior_software_architect"]'::jsonb,
'["server_frameworks", "rest_api", "graphql", "authentication", "caching", "microservices", "databases"]'::jsonb,
'["api_endpoints", "backend_services", "unit_tests", "server_code"]'::jsonb,
'["task-coordination", "meta-discussion", "backend", "api_development", "database_design"]'::jsonb,
'{
"preferred_message_types": ["task_help_request", "coordination_request", "dependency_alert"],
"auto_subscribe_to_roles": ["database_engineer", "frontend_developer", "security_expert"],
"auto_subscribe_to_expertise": ["database", "frontend", "security"],
"response_timeout_seconds": 200,
"max_collaboration_depth": 4,
"escalation_threshold": 2
}'::jsonb
);
-- Create agent_collaborations table to track collaboration history
CREATE TABLE agent_collaborations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
from_agent_id UUID REFERENCES agents(id),
to_agent_id UUID REFERENCES agents(id),
message_type VARCHAR(100) NOT NULL,
thread_id UUID,
project_id INTEGER REFERENCES projects(id),
message_data JSONB,
response_data JSONB,
status VARCHAR(50) DEFAULT 'pending', -- pending, responded, escalated, resolved
priority VARCHAR(20) DEFAULT 'medium', -- low, medium, high, urgent
created_at TIMESTAMP DEFAULT NOW(),
responded_at TIMESTAMP,
resolved_at TIMESTAMP
);
-- Indexes for collaboration tracking
CREATE INDEX idx_agent_collaborations_from_agent ON agent_collaborations(from_agent_id);
CREATE INDEX idx_agent_collaborations_to_agent ON agent_collaborations(to_agent_id);
CREATE INDEX idx_agent_collaborations_thread ON agent_collaborations(thread_id);
CREATE INDEX idx_agent_collaborations_project ON agent_collaborations(project_id);
CREATE INDEX idx_agent_collaborations_status ON agent_collaborations(status);
CREATE INDEX idx_agent_collaborations_priority ON agent_collaborations(priority);

View File

@@ -0,0 +1,376 @@
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { DataTable } from '../ui/DataTable';
import { Alert, AlertDescription } from '../ui/alert';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
import AgentRoleSelector from './AgentRoleSelector';
import CollaborationDashboard from './CollaborationDashboard';
interface Agent {
id: string;
name: string;
endpoint: string;
model: string;
status: string;
role?: string;
expertise?: string[];
reports_to?: string[];
deliverables?: string[];
capabilities?: string[];
collaboration_settings?: any;
last_seen?: string;
}
interface RoleDefinition {
id: string;
name: string;
display_name: string;
system_prompt: string;
reports_to: string[];
expertise: string[];
deliverables: string[];
capabilities: string[];
collaboration_defaults: any;
}
export const AgentManagement: React.FC = () => {
const [agents, setAgents] = useState<Agent[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
const [editDialogOpen, setEditDialogOpen] = useState(false);
useEffect(() => {
fetchAgents();
}, []);
const fetchAgents = async () => {
try {
setLoading(true);
const response = await fetch('/api/agents');
if (!response.ok) {
throw new Error('Failed to fetch agents');
}
const data = await response.json();
setAgents(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load agents');
} finally {
setLoading(false);
}
};
const handleRoleUpdate = async (agent: Agent, role: RoleDefinition) => {
try {
const response = await fetch(`/api/agents/${agent.id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
role: role.name,
system_prompt: role.system_prompt,
reports_to: role.reports_to,
expertise: role.expertise,
deliverables: role.deliverables,
capabilities: role.capabilities,
collaboration_settings: role.collaboration_defaults
}),
});
if (!response.ok) {
throw new Error('Failed to update agent role');
}
// Refresh agents list
await fetchAgents();
setEditDialogOpen(false);
setSelectedAgent(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to update agent');
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'online': return 'default';
case 'busy': return 'secondary';
case 'offline': return 'destructive';
default: return 'outline';
}
};
const agentColumns = [
{
accessorKey: 'name',
header: 'Agent Name',
cell: ({ row }: any) => (
<div>
<div className="font-medium">{row.original.name}</div>
<div className="text-sm text-gray-500">{row.original.id.substring(0, 8)}...</div>
</div>
)
},
{
accessorKey: 'role',
header: 'Role',
cell: ({ row }: any) => (
row.original.role ? (
<Badge variant="outline">
{row.original.role.replace(/_/g, ' ')}
</Badge>
) : (
<span className="text-gray-400">No role assigned</span>
)
)
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }: any) => (
<Badge variant={getStatusColor(row.original.status)}>
{row.original.status}
</Badge>
)
},
{
accessorKey: 'model',
header: 'Model',
cell: ({ row }: any) => (
<span className="font-mono text-sm">{row.original.model || 'N/A'}</span>
)
},
{
accessorKey: 'expertise',
header: 'Expertise',
cell: ({ row }: any) => (
<div className="flex flex-wrap gap-1">
{(row.original.expertise || []).slice(0, 3).map((exp: string) => (
<Badge key={exp} variant="secondary" className="text-xs">
{exp.replace(/_/g, ' ')}
</Badge>
))}
{(row.original.expertise || []).length > 3 && (
<Badge variant="outline" className="text-xs">
+{(row.original.expertise || []).length - 3} more
</Badge>
)}
</div>
)
},
{
accessorKey: 'last_seen',
header: 'Last Seen',
cell: ({ row }: any) => (
<div className="text-sm text-gray-500">
{row.original.last_seen ?
new Date(row.original.last_seen).toLocaleString() :
'Never'
}
</div>
)
},
{
id: 'actions',
header: 'Actions',
cell: ({ row }: any) => (
<Button
size="sm"
variant="outline"
onClick={() => {
setSelectedAgent(row.original);
setEditDialogOpen(true);
}}
>
Edit Role
</Button>
)
}
];
const getRoleDistribution = () => {
const distribution: Record<string, number> = {};
agents.forEach(agent => {
const role = agent.role || 'unassigned';
distribution[role] = (distribution[role] || 0) + 1;
});
return distribution;
};
const getStatusDistribution = () => {
const distribution: Record<string, number> = {};
agents.forEach(agent => {
distribution[agent.status] = (distribution[agent.status] || 0) + 1;
});
return distribution;
};
if (loading) {
return (
<div className="space-y-6">
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4"></div>
<div className="h-64 bg-gray-200 rounded"></div>
</div>
</div>
);
}
if (error) {
return (
<Alert>
<AlertDescription>{error}</AlertDescription>
<Button onClick={fetchAgents} className="mt-4">
Retry
</Button>
</Alert>
);
}
const roleDistribution = getRoleDistribution();
const statusDistribution = getStatusDistribution();
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-3xl font-bold">Agent Management</h1>
<Button onClick={fetchAgents}>Refresh</Button>
</div>
<Tabs defaultValue="agents" className="w-full">
<TabsList>
<TabsTrigger value="agents">Agents</TabsTrigger>
<TabsTrigger value="collaborations">Collaborations</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
</TabsList>
<TabsContent value="agents" className="space-y-6">
{/* Summary Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{agents.length}</div>
<div className="text-sm text-gray-500">Total Agents</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{statusDistribution.online || 0}</div>
<div className="text-sm text-gray-500">Online</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{agents.filter(a => a.role).length}</div>
<div className="text-sm text-gray-500">Role Assigned</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{Object.keys(roleDistribution).length}</div>
<div className="text-sm text-gray-500">Unique Roles</div>
</CardContent>
</Card>
</div>
{/* Agents Table */}
<Card>
<CardHeader>
<CardTitle>Registered Agents</CardTitle>
</CardHeader>
<CardContent>
<DataTable
columns={agentColumns}
data={agents}
pagination={true}
/>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="collaborations">
<CollaborationDashboard />
</TabsContent>
<TabsContent value="analytics" className="space-y-6">
{/* Role Distribution */}
<Card>
<CardHeader>
<CardTitle>Role Distribution</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{Object.entries(roleDistribution).map(([role, count]) => (
<div key={role} className="flex justify-between items-center">
<span className="font-medium">
{role === 'unassigned' ? 'Unassigned' : role.replace(/_/g, ' ')}
</span>
<div className="flex items-center gap-2">
<div className="w-32 h-2 bg-gray-200 rounded">
<div
className="h-full bg-blue-500 rounded"
style={{ width: `${(count / agents.length) * 100}%` }}
/>
</div>
<span className="text-sm text-gray-500">{count}</span>
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Status Distribution */}
<Card>
<CardHeader>
<CardTitle>Agent Status</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{Object.entries(statusDistribution).map(([status, count]) => (
<div key={status} className="flex justify-between items-center">
<div className="flex items-center gap-2">
<Badge variant={getStatusColor(status)}>{status}</Badge>
</div>
<div className="flex items-center gap-2">
<div className="w-32 h-2 bg-gray-200 rounded">
<div
className="h-full bg-green-500 rounded"
style={{ width: `${(count / agents.length) * 100}%` }}
/>
</div>
<span className="text-sm text-gray-500">{count}</span>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* Role Assignment Dialog */}
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
Assign Role to {selectedAgent?.name}
</DialogTitle>
</DialogHeader>
{selectedAgent && (
<AgentRoleSelector
agentId={selectedAgent.id}
currentRole={selectedAgent.role}
onRoleChange={(role) => handleRoleUpdate(selectedAgent, role)}
/>
)}
</DialogContent>
</Dialog>
</div>
);
};
export default AgentManagement;

View File

@@ -0,0 +1,231 @@
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { Button } from '../ui/button';
import { Select } from '../ui/select';
import { Badge } from '../ui/badge';
import { Input } from '../ui/input';
import { Textarea } from '../ui/textarea';
import { Label } from '../ui/label';
import { Alert, AlertDescription } from '../ui/alert';
interface RoleDefinition {
id: string;
name: string;
display_name: string;
system_prompt: string;
reports_to: string[];
expertise: string[];
deliverables: string[];
capabilities: string[];
collaboration_defaults: {
preferred_message_types: string[];
auto_subscribe_to_roles: string[];
auto_subscribe_to_expertise: string[];
response_timeout_seconds: number;
max_collaboration_depth: number;
escalation_threshold: number;
};
}
interface AgentRoleSelectorProps {
agentId?: string;
currentRole?: string;
onRoleChange: (role: RoleDefinition) => void;
onCustomPromptChange?: (prompt: string) => void;
className?: string;
}
export const AgentRoleSelector: React.FC<AgentRoleSelectorProps> = ({
agentId,
currentRole,
onRoleChange,
onCustomPromptChange,
className = ""
}) => {
const [roles, setRoles] = useState<RoleDefinition[]>([]);
const [selectedRole, setSelectedRole] = useState<RoleDefinition | null>(null);
const [customPrompt, setCustomPrompt] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchAvailableRoles();
}, []);
useEffect(() => {
if (currentRole && roles.length > 0) {
const role = roles.find(r => r.name === currentRole);
if (role) {
setSelectedRole(role);
}
}
}, [currentRole, roles]);
const fetchAvailableRoles = async () => {
try {
setLoading(true);
const response = await fetch('/api/agent-roles');
if (!response.ok) {
throw new Error('Failed to fetch agent roles');
}
const data = await response.json();
setRoles(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load roles');
} finally {
setLoading(false);
}
};
const handleRoleSelect = (roleName: string) => {
const role = roles.find(r => r.name === roleName);
if (role) {
setSelectedRole(role);
setCustomPrompt(role.system_prompt);
onRoleChange(role);
}
};
const handleCustomPromptChange = (prompt: string) => {
setCustomPrompt(prompt);
if (onCustomPromptChange) {
onCustomPromptChange(prompt);
}
};
if (loading) {
return (
<Card className={className}>
<CardHeader>
<CardTitle>Agent Role Configuration</CardTitle>
</CardHeader>
<CardContent>
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-4"></div>
<div className="h-32 bg-gray-200 rounded"></div>
</div>
</CardContent>
</Card>
);
}
if (error) {
return (
<Card className={className}>
<CardHeader>
<CardTitle>Agent Role Configuration</CardTitle>
</CardHeader>
<CardContent>
<Alert>
<AlertDescription>{error}</AlertDescription>
</Alert>
<Button onClick={fetchAvailableRoles} className="mt-4">
Retry
</Button>
</CardContent>
</Card>
);
}
return (
<Card className={className}>
<CardHeader>
<CardTitle>Agent Role Configuration</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Role Selection */}
<div className="space-y-2">
<Label htmlFor="role-select">Select Role</Label>
<Select onValueChange={handleRoleSelect} value={selectedRole?.name || ''}>
<option value="">Select a role...</option>
{roles.map(role => (
<option key={role.id} value={role.name}>
{role.display_name}
</option>
))}
</Select>
</div>
{/* Role Details */}
{selectedRole && (
<>
{/* Expertise Areas */}
<div className="space-y-2">
<Label>Expertise Areas</Label>
<div className="flex flex-wrap gap-2">
{selectedRole.expertise.map(exp => (
<Badge key={exp} variant="secondary">
{exp.replace(/_/g, ' ')}
</Badge>
))}
</div>
</div>
{/* Reports To */}
{selectedRole.reports_to.length > 0 && (
<div className="space-y-2">
<Label>Reports To</Label>
<div className="flex flex-wrap gap-2">
{selectedRole.reports_to.map(role => (
<Badge key={role} variant="outline">
{role.replace(/_/g, ' ')}
</Badge>
))}
</div>
</div>
)}
{/* Deliverables */}
<div className="space-y-2">
<Label>Key Deliverables</Label>
<div className="flex flex-wrap gap-2">
{selectedRole.deliverables.map(deliverable => (
<Badge key={deliverable} variant="default">
{deliverable.replace(/_/g, ' ')}
</Badge>
))}
</div>
</div>
{/* Capabilities */}
<div className="space-y-2">
<Label>Capabilities</Label>
<div className="flex flex-wrap gap-2">
{selectedRole.capabilities.map(capability => (
<Badge key={capability} variant="secondary">
{capability}
</Badge>
))}
</div>
</div>
{/* Collaboration Settings */}
<div className="space-y-2">
<Label>Collaboration Preferences</Label>
<div className="text-sm text-gray-600 space-y-1">
<div>Response Timeout: {selectedRole.collaboration_defaults.response_timeout_seconds}s</div>
<div>Max Collaboration Depth: {selectedRole.collaboration_defaults.max_collaboration_depth}</div>
<div>Escalation Threshold: {selectedRole.collaboration_defaults.escalation_threshold}</div>
</div>
</div>
{/* System Prompt */}
<div className="space-y-2">
<Label htmlFor="system-prompt">System Prompt</Label>
<Textarea
id="system-prompt"
value={customPrompt}
onChange={(e) => handleCustomPromptChange(e.target.value)}
placeholder="System prompt for this agent role..."
rows={8}
className="font-mono text-sm"
/>
</div>
</>
)}
</CardContent>
</Card>
);
};
export default AgentRoleSelector;

View File

@@ -0,0 +1,271 @@
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { DataTable } from '../ui/DataTable';
import { Alert, AlertDescription } from '../ui/alert';
import { Select } from '../ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
interface Collaboration {
id: string;
from_agent_id: string;
to_agent_id: string;
message_type: string;
thread_id: string;
project_id: string;
status: string;
priority: string;
created_at: string;
responded_at?: string;
resolved_at?: string;
message_data: any;
response_data?: any;
}
interface Agent {
id: string;
name: string;
role: string;
status: string;
expertise: string[];
}
interface CollaborationDashboardProps {
className?: string;
}
export const CollaborationDashboard: React.FC<CollaborationDashboardProps> = ({
className = ""
}) => {
const [collaborations, setCollaborations] = useState<Collaboration[]>([]);
const [agents, setAgents] = useState<Agent[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [selectedStatus, setSelectedStatus] = useState<string>('all');
const [selectedMessageType, setSelectedMessageType] = useState<string>('all');
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
const [collaborationsRes, agentsRes] = await Promise.all([
fetch('/api/agent-collaborations'),
fetch('/api/agents')
]);
if (!collaborationsRes.ok || !agentsRes.ok) {
throw new Error('Failed to fetch collaboration data');
}
const [collaborationsData, agentsData] = await Promise.all([
collaborationsRes.json(),
agentsRes.json()
]);
setCollaborations(collaborationsData);
setAgents(agentsData);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load data');
} finally {
setLoading(false);
}
};
const getAgentName = (agentId: string) => {
const agent = agents.find(a => a.id === agentId);
return agent ? agent.name : agentId;
};
const getAgentRole = (agentId: string) => {
const agent = agents.find(a => a.id === agentId);
return agent ? agent.role : 'Unknown';
};
const filteredCollaborations = collaborations.filter(collab => {
const statusMatch = selectedStatus === 'all' || collab.status === selectedStatus;
const typeMatch = selectedMessageType === 'all' || collab.message_type === selectedMessageType;
return statusMatch && typeMatch;
});
const collaborationColumns = [
{
accessorKey: 'from_agent_id',
header: 'From Agent',
cell: ({ row }: any) => (
<div>
<div className="font-medium">{getAgentName(row.original.from_agent_id)}</div>
<div className="text-sm text-gray-500">{getAgentRole(row.original.from_agent_id)}</div>
</div>
)
},
{
accessorKey: 'to_agent_id',
header: 'To Agent',
cell: ({ row }: any) => (
<div>
<div className="font-medium">{getAgentName(row.original.to_agent_id)}</div>
<div className="text-sm text-gray-500">{getAgentRole(row.original.to_agent_id)}</div>
</div>
)
},
{
accessorKey: 'message_type',
header: 'Message Type',
cell: ({ row }: any) => (
<Badge variant="outline">
{row.original.message_type.replace(/_/g, ' ')}
</Badge>
)
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }: any) => {
const status = row.original.status;
const variant =
status === 'resolved' ? 'default' :
status === 'responded' ? 'secondary' :
status === 'escalated' ? 'destructive' :
'outline';
return <Badge variant={variant}>{status}</Badge>;
}
},
{
accessorKey: 'priority',
header: 'Priority',
cell: ({ row }: any) => {
const priority = row.original.priority;
const variant =
priority === 'urgent' ? 'destructive' :
priority === 'high' ? 'default' :
priority === 'medium' ? 'secondary' :
'outline';
return <Badge variant={variant}>{priority}</Badge>;
}
},
{
accessorKey: 'created_at',
header: 'Created',
cell: ({ row }: any) => (
<div className="text-sm">
{new Date(row.original.created_at).toLocaleDateString()}
</div>
)
}
];
const getCollaborationStats = () => {
const total = collaborations.length;
const pending = collaborations.filter(c => c.status === 'pending').length;
const resolved = collaborations.filter(c => c.status === 'resolved').length;
const escalated = collaborations.filter(c => c.status === 'escalated').length;
return { total, pending, resolved, escalated };
};
const stats = getCollaborationStats();
if (loading) {
return (
<Card className={className}>
<CardHeader>
<CardTitle>Collaboration Dashboard</CardTitle>
</CardHeader>
<CardContent>
<div className="animate-pulse space-y-4">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="h-32 bg-gray-200 rounded"></div>
</div>
</CardContent>
</Card>
);
}
if (error) {
return (
<Card className={className}>
<CardHeader>
<CardTitle>Collaboration Dashboard</CardTitle>
</CardHeader>
<CardContent>
<Alert>
<AlertDescription>{error}</AlertDescription>
</Alert>
<Button onClick={fetchData} className="mt-4">
Retry
</Button>
</CardContent>
</Card>
);
}
return (
<div className={`space-y-6 ${className}`}>
{/* Stats Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{stats.total}</div>
<div className="text-sm text-gray-500">Total Collaborations</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{stats.pending}</div>
<div className="text-sm text-gray-500">Pending</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{stats.resolved}</div>
<div className="text-sm text-gray-500">Resolved</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="text-2xl font-bold">{stats.escalated}</div>
<div className="text-sm text-gray-500">Escalated</div>
</CardContent>
</Card>
</div>
{/* Main Dashboard */}
<Card>
<CardHeader>
<CardTitle>Agent Collaborations</CardTitle>
<div className="flex gap-4">
<Select onValueChange={setSelectedStatus} value={selectedStatus}>
<option value="all">All Statuses</option>
<option value="pending">Pending</option>
<option value="responded">Responded</option>
<option value="resolved">Resolved</option>
<option value="escalated">Escalated</option>
</Select>
<Select onValueChange={setSelectedMessageType} value={selectedMessageType}>
<option value="all">All Message Types</option>
<option value="task_help_request">Help Request</option>
<option value="coordination_request">Coordination Request</option>
<option value="expertise_request">Expertise Request</option>
<option value="mentorship_request">Mentorship Request</option>
<option value="dependency_alert">Dependency Alert</option>
<option value="escalation_trigger">Escalation</option>
</Select>
</div>
</CardHeader>
<CardContent>
<DataTable
columns={collaborationColumns}
data={filteredCollaborations}
pagination={true}
/>
</CardContent>
</Card>
</div>
);
};
export default CollaborationDashboard;