- Migrated from HIVE branding to WHOOSH across all components - Enhanced backend API with new services: AI models, BZZZ integration, templates, members - Added comprehensive testing suite with security, performance, and integration tests - Improved frontend with new components for project setup, AI models, and team management - Updated MCP server implementation with WHOOSH-specific tools and resources - Enhanced deployment configurations with production-ready Docker setups - Added comprehensive documentation and setup guides - Implemented age encryption service and UCXL integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
266 lines
9.3 KiB
Python
266 lines
9.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
BZZZ Integration API for WHOOSH
|
|
API endpoints for team collaboration, decision publishing, and consensus mechanisms
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
from typing import Dict, List, Optional, Any
|
|
from pydantic import BaseModel, Field
|
|
from datetime import datetime
|
|
|
|
from ..services.bzzz_integration_service import bzzz_service, AgentRole
|
|
from ..core.auth_deps import get_current_user
|
|
from ..models.user import User
|
|
|
|
router = APIRouter(prefix="/api/bzzz", tags=["BZZZ Integration"])
|
|
|
|
# Pydantic models for API requests/responses
|
|
|
|
class DecisionRequest(BaseModel):
|
|
title: str = Field(..., description="Decision title")
|
|
description: str = Field(..., description="Detailed decision description")
|
|
context: Dict[str, Any] = Field(default_factory=dict, description="Decision context data")
|
|
ucxl_address: Optional[str] = Field(None, description="Related UCXL address")
|
|
|
|
class DecisionResponse(BaseModel):
|
|
decision_id: str
|
|
title: str
|
|
description: str
|
|
author_role: str
|
|
timestamp: datetime
|
|
ucxl_address: Optional[str] = None
|
|
|
|
class TaskAssignmentRequest(BaseModel):
|
|
task_description: str = Field(..., description="Task description")
|
|
required_capabilities: List[str] = Field(..., description="Required capabilities")
|
|
priority: str = Field("medium", description="Task priority (low, medium, high, urgent)")
|
|
|
|
class TaskAssignmentResponse(BaseModel):
|
|
decision_id: Optional[str]
|
|
assigned_to: str
|
|
assignment_score: float
|
|
alternatives: List[Dict[str, Any]]
|
|
|
|
class TeamMemberInfo(BaseModel):
|
|
agent_id: str
|
|
role: str
|
|
endpoint: str
|
|
capabilities: List[str]
|
|
status: str
|
|
|
|
class TeamStatusResponse(BaseModel):
|
|
total_members: int
|
|
online_members: int
|
|
offline_members: int
|
|
role_distribution: Dict[str, int]
|
|
active_decisions: int
|
|
recent_decisions: List[Dict[str, Any]]
|
|
network_health: float
|
|
|
|
class ConsensusResponse(BaseModel):
|
|
decision_id: str
|
|
total_votes: int
|
|
approvals: int
|
|
approval_rate: float
|
|
consensus_reached: bool
|
|
details: Dict[str, Any]
|
|
|
|
@router.get("/status", response_model=TeamStatusResponse)
|
|
async def get_team_status(
|
|
current_user: User = Depends(get_current_user)
|
|
) -> TeamStatusResponse:
|
|
"""Get current BZZZ team status and network health"""
|
|
try:
|
|
status = await bzzz_service.get_team_status()
|
|
return TeamStatusResponse(**status)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get team status: {str(e)}")
|
|
|
|
@router.get("/members", response_model=List[TeamMemberInfo])
|
|
async def get_team_members(
|
|
current_user: User = Depends(get_current_user)
|
|
) -> List[TeamMemberInfo]:
|
|
"""Get list of active team members in BZZZ network"""
|
|
try:
|
|
members = []
|
|
for member in bzzz_service.team_members.values():
|
|
members.append(TeamMemberInfo(
|
|
agent_id=member.agent_id,
|
|
role=member.role.value,
|
|
endpoint=member.endpoint,
|
|
capabilities=member.capabilities,
|
|
status=member.status
|
|
))
|
|
return members
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get team members: {str(e)}")
|
|
|
|
@router.post("/decisions", response_model=Dict[str, str])
|
|
async def publish_decision(
|
|
decision: DecisionRequest,
|
|
current_user: User = Depends(get_current_user)
|
|
) -> Dict[str, str]:
|
|
"""
|
|
Publish a decision to the BZZZ network for team consensus
|
|
"""
|
|
try:
|
|
decision_id = await bzzz_service.publish_decision(
|
|
title=decision.title,
|
|
description=decision.description,
|
|
context=decision.context,
|
|
ucxl_address=decision.ucxl_address
|
|
)
|
|
|
|
if decision_id:
|
|
return {"decision_id": decision_id, "status": "published"}
|
|
else:
|
|
raise HTTPException(status_code=500, detail="Failed to publish decision")
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to publish decision: {str(e)}")
|
|
|
|
@router.get("/decisions", response_model=List[DecisionResponse])
|
|
async def get_recent_decisions(
|
|
limit: int = Query(10, ge=1, le=100),
|
|
current_user: User = Depends(get_current_user)
|
|
) -> List[DecisionResponse]:
|
|
"""Get recent decisions from BZZZ network"""
|
|
try:
|
|
decisions = sorted(
|
|
bzzz_service.active_decisions.values(),
|
|
key=lambda d: d.timestamp,
|
|
reverse=True
|
|
)[:limit]
|
|
|
|
return [
|
|
DecisionResponse(
|
|
decision_id=decision.id,
|
|
title=decision.title,
|
|
description=decision.description,
|
|
author_role=decision.author_role,
|
|
timestamp=decision.timestamp,
|
|
ucxl_address=decision.ucxl_address
|
|
)
|
|
for decision in decisions
|
|
]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get decisions: {str(e)}")
|
|
|
|
@router.get("/decisions/{decision_id}/consensus", response_model=Optional[ConsensusResponse])
|
|
async def get_decision_consensus(
|
|
decision_id: str,
|
|
current_user: User = Depends(get_current_user)
|
|
) -> Optional[ConsensusResponse]:
|
|
"""Get consensus status for a specific decision"""
|
|
try:
|
|
consensus = await bzzz_service.get_team_consensus(decision_id)
|
|
|
|
if consensus:
|
|
return ConsensusResponse(**consensus)
|
|
else:
|
|
raise HTTPException(status_code=404, detail="Decision not found or no consensus data available")
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get consensus: {str(e)}")
|
|
|
|
@router.post("/tasks/assign", response_model=TaskAssignmentResponse)
|
|
async def coordinate_task_assignment(
|
|
task: TaskAssignmentRequest,
|
|
current_user: User = Depends(get_current_user)
|
|
) -> TaskAssignmentResponse:
|
|
"""
|
|
Coordinate task assignment across team members based on capabilities and availability
|
|
"""
|
|
try:
|
|
assignment = await bzzz_service.coordinate_task_assignment(
|
|
task_description=task.task_description,
|
|
required_capabilities=task.required_capabilities,
|
|
priority=task.priority
|
|
)
|
|
|
|
if assignment:
|
|
return TaskAssignmentResponse(**assignment)
|
|
else:
|
|
raise HTTPException(status_code=404, detail="No suitable team members found for task")
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to coordinate task assignment: {str(e)}")
|
|
|
|
@router.post("/network/discover")
|
|
async def rediscover_network(
|
|
current_user: User = Depends(get_current_user)
|
|
) -> Dict[str, Any]:
|
|
"""Manually trigger team member discovery"""
|
|
try:
|
|
await bzzz_service._discover_team_members()
|
|
|
|
return {
|
|
"status": "success",
|
|
"members_discovered": len(bzzz_service.team_members),
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to rediscover network: {str(e)}")
|
|
|
|
@router.get("/roles", response_model=List[str])
|
|
async def get_available_roles() -> List[str]:
|
|
"""Get list of available agent roles in BZZZ system"""
|
|
return [role.value for role in AgentRole]
|
|
|
|
@router.get("/capabilities/{agent_id}", response_model=Dict[str, Any])
|
|
async def get_agent_capabilities(
|
|
agent_id: str,
|
|
current_user: User = Depends(get_current_user)
|
|
) -> Dict[str, Any]:
|
|
"""Get detailed capabilities of a specific team member"""
|
|
try:
|
|
if agent_id not in bzzz_service.team_members:
|
|
raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
|
|
|
|
member = bzzz_service.team_members[agent_id]
|
|
|
|
return {
|
|
"agent_id": member.agent_id,
|
|
"role": member.role.value,
|
|
"capabilities": member.capabilities,
|
|
"status": member.status,
|
|
"endpoint": member.endpoint,
|
|
"last_seen": datetime.utcnow().isoformat() # Placeholder
|
|
}
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get agent capabilities: {str(e)}")
|
|
|
|
@router.get("/health")
|
|
async def bzzz_health_check() -> Dict[str, Any]:
|
|
"""BZZZ integration health check endpoint"""
|
|
try:
|
|
total_members = len(bzzz_service.team_members)
|
|
online_members = sum(1 for m in bzzz_service.team_members.values() if m.status == "online")
|
|
|
|
health_status = "healthy" if online_members >= total_members * 0.5 else "degraded"
|
|
if online_members == 0:
|
|
health_status = "offline"
|
|
|
|
return {
|
|
"status": health_status,
|
|
"bzzz_endpoints": len(bzzz_service.bzzz_endpoints),
|
|
"team_members": total_members,
|
|
"online_members": online_members,
|
|
"active_decisions": len(bzzz_service.active_decisions),
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"status": "error",
|
|
"error": str(e),
|
|
"timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
# Note: Exception handlers are registered at the app level, not router level |