Backend:
- Added POST /city/agents endpoint for creating agents
- Added DELETE /city/agents/{id} endpoint for soft-deleting agents
- Added CreateAgentRequest, CreateAgentResponse, DeleteAgentResponse models
Frontend:
- Added '+ Новий агент' button on /agents page
- Created /agents/new page with full agent creation form
- Added 'Видалити агента' button in agent Identity tab (Danger Zone)
Features:
- Auto-generate slug from display_name
- Support for all agent fields: kind, role, model, node, district, microdao
- Color picker for agent color
- Visibility toggles (is_public, is_orchestrator)
- Soft delete with confirmation dialog
714 lines
20 KiB
Python
714 lines
20 KiB
Python
"""
|
||
Pydantic Models для City Backend
|
||
"""
|
||
|
||
from pydantic import BaseModel, Field
|
||
from typing import Optional, List, Dict, Any
|
||
from datetime import datetime
|
||
|
||
|
||
# =============================================================================
|
||
# City Rooms
|
||
# =============================================================================
|
||
|
||
class CityRoomBase(BaseModel):
|
||
slug: str
|
||
name: str
|
||
description: Optional[str] = None
|
||
|
||
|
||
class CityRoomCreate(CityRoomBase):
|
||
pass
|
||
|
||
|
||
class CityRoomRead(CityRoomBase):
|
||
id: str
|
||
is_default: bool
|
||
created_at: datetime
|
||
created_by: Optional[str] = None
|
||
members_online: int = 0
|
||
last_event: Optional[str] = None
|
||
# Branding
|
||
logo_url: Optional[str] = None
|
||
banner_url: Optional[str] = None
|
||
# Context
|
||
microdao_id: Optional[str] = None
|
||
microdao_name: Optional[str] = None
|
||
microdao_slug: Optional[str] = None
|
||
microdao_logo_url: Optional[str] = None
|
||
# Matrix integration
|
||
matrix_room_id: Optional[str] = None
|
||
matrix_room_alias: Optional[str] = None
|
||
|
||
|
||
# =============================================================================
|
||
# City Room Messages
|
||
# =============================================================================
|
||
|
||
class CityRoomMessageBase(BaseModel):
|
||
body: str = Field(..., min_length=1, max_length=10000)
|
||
|
||
|
||
class CityRoomMessageCreate(CityRoomMessageBase):
|
||
pass
|
||
|
||
|
||
class CityRoomMessageRead(CityRoomMessageBase):
|
||
id: str
|
||
room_id: str
|
||
author_user_id: Optional[str] = None
|
||
author_agent_id: Optional[str] = None
|
||
username: Optional[str] = "Anonymous" # Для frontend
|
||
created_at: datetime
|
||
|
||
|
||
# =============================================================================
|
||
# City Room Detail (з повідомленнями)
|
||
# =============================================================================
|
||
|
||
class CityRoomDetail(CityRoomRead):
|
||
messages: List[CityRoomMessageRead] = []
|
||
online_members: List[str] = [] # user_ids
|
||
|
||
|
||
# =============================================================================
|
||
# City Feed Events
|
||
# =============================================================================
|
||
|
||
class CityFeedEventRead(BaseModel):
|
||
id: str
|
||
kind: str # 'room_message', 'agent_reply', 'system', 'dao_event'
|
||
room_id: Optional[str] = None
|
||
user_id: Optional[str] = None
|
||
agent_id: Optional[str] = None
|
||
payload: dict
|
||
created_at: datetime
|
||
|
||
|
||
# =============================================================================
|
||
# Presence
|
||
# =============================================================================
|
||
|
||
class PresenceUpdate(BaseModel):
|
||
user_id: str
|
||
status: str # 'online', 'offline', 'away'
|
||
last_seen: Optional[datetime] = None
|
||
|
||
|
||
class PresenceBulkUpdate(BaseModel):
|
||
users: List[PresenceUpdate]
|
||
|
||
|
||
# =============================================================================
|
||
# WebSocket Messages
|
||
# =============================================================================
|
||
|
||
class WSRoomMessage(BaseModel):
|
||
event: str # 'room.message', 'room.join', 'room.leave'
|
||
room_id: Optional[str] = None
|
||
user_id: Optional[str] = None
|
||
message: Optional[CityRoomMessageRead] = None
|
||
|
||
|
||
class WSPresenceMessage(BaseModel):
|
||
event: str # 'presence.heartbeat', 'presence.update'
|
||
user_id: str
|
||
status: Optional[str] = None
|
||
|
||
|
||
# =============================================================================
|
||
# City Map (2D Map)
|
||
# =============================================================================
|
||
|
||
class CityMapRoom(BaseModel):
|
||
"""Room representation on 2D city map"""
|
||
id: str
|
||
slug: str
|
||
name: str
|
||
description: Optional[str] = None
|
||
room_type: str = "public"
|
||
zone: str = "central"
|
||
icon: Optional[str] = None
|
||
color: Optional[str] = None
|
||
# Map coordinates
|
||
x: int = 0
|
||
y: int = 0
|
||
w: int = 1
|
||
h: int = 1
|
||
# Matrix integration
|
||
matrix_room_id: Optional[str] = None
|
||
|
||
|
||
class CityMapConfig(BaseModel):
|
||
"""Global city map configuration"""
|
||
grid_width: int = 6
|
||
grid_height: int = 3
|
||
cell_size: int = 100
|
||
background_url: Optional[str] = None
|
||
|
||
|
||
class CityMapResponse(BaseModel):
|
||
"""Full city map response"""
|
||
config: CityMapConfig
|
||
rooms: List[CityMapRoom]
|
||
|
||
|
||
# =============================================================================
|
||
# Branding & Assets
|
||
# =============================================================================
|
||
|
||
class BrandingUpdatePayload(BaseModel):
|
||
logo_url: Optional[str] = None
|
||
banner_url: Optional[str] = None
|
||
|
||
|
||
class AssetUploadResponse(BaseModel):
|
||
original_url: str
|
||
processed_url: str
|
||
thumb_url: Optional[str] = None
|
||
|
||
|
||
# =============================================================================
|
||
# Agents (for Agent Presence)
|
||
# =============================================================================
|
||
|
||
class AgentRead(BaseModel):
|
||
"""Agent representation"""
|
||
id: str
|
||
display_name: str
|
||
kind: str = "assistant" # assistant, civic, oracle, builder
|
||
avatar_url: Optional[str] = None
|
||
color: str = "cyan"
|
||
status: str = "offline" # online, offline, busy
|
||
current_room_id: Optional[str] = None
|
||
capabilities: List[str] = []
|
||
|
||
|
||
class AgentPresence(BaseModel):
|
||
"""Agent presence in a room"""
|
||
agent_id: str
|
||
display_name: str
|
||
kind: str
|
||
status: str
|
||
room_id: Optional[str] = None
|
||
color: Optional[str] = None
|
||
node_id: Optional[str] = None
|
||
district: Optional[str] = None
|
||
model: Optional[str] = None
|
||
role: Optional[str] = None
|
||
avatar_url: Optional[str] = None
|
||
|
||
|
||
# =============================================================================
|
||
# Citizens
|
||
# =============================================================================
|
||
|
||
class CityPresenceRoomView(BaseModel):
|
||
room_id: Optional[str] = None
|
||
slug: Optional[str] = None
|
||
name: Optional[str] = None
|
||
|
||
|
||
class CityPresenceView(BaseModel):
|
||
primary_room_slug: Optional[str] = None
|
||
rooms: List[CityPresenceRoomView] = []
|
||
|
||
|
||
class HomeNodeView(BaseModel):
|
||
"""Home node information for agent/citizen"""
|
||
id: Optional[str] = None
|
||
name: Optional[str] = None
|
||
hostname: Optional[str] = None
|
||
roles: List[str] = []
|
||
environment: Optional[str] = None
|
||
|
||
|
||
class NodeAgentSummary(BaseModel):
|
||
"""Summary of a node agent (Guardian or Steward)"""
|
||
id: str
|
||
name: Optional[str] = None
|
||
kind: Optional[str] = None
|
||
slug: Optional[str] = None
|
||
|
||
|
||
class NodeMicrodaoSummary(BaseModel):
|
||
"""Summary of a MicroDAO hosted on a node (via orchestrator)"""
|
||
id: str
|
||
slug: str
|
||
name: str
|
||
rooms_count: int = 0
|
||
|
||
|
||
class NodeMetrics(BaseModel):
|
||
"""Node metrics for Node Directory cards"""
|
||
cpu_model: Optional[str] = None
|
||
cpu_cores: int = 0
|
||
cpu_usage: float = 0.0
|
||
gpu_model: Optional[str] = None
|
||
gpu_vram_total: int = 0
|
||
gpu_vram_used: int = 0
|
||
ram_total: int = 0
|
||
ram_used: int = 0
|
||
disk_total: int = 0
|
||
disk_used: int = 0
|
||
agent_count_router: int = 0
|
||
agent_count_system: int = 0
|
||
dagi_router_url: Optional[str] = None
|
||
|
||
|
||
class NodeProfile(BaseModel):
|
||
"""Node profile for Node Directory"""
|
||
node_id: str
|
||
name: str
|
||
hostname: Optional[str] = None
|
||
roles: List[str] = []
|
||
environment: str = "unknown"
|
||
status: str = "offline"
|
||
gpu_info: Optional[str] = None
|
||
agents_total: int = 0
|
||
agents_online: int = 0
|
||
last_heartbeat: Optional[str] = None
|
||
guardian_agent_id: Optional[str] = None
|
||
steward_agent_id: Optional[str] = None
|
||
guardian_agent: Optional[NodeAgentSummary] = None
|
||
steward_agent: Optional[NodeAgentSummary] = None
|
||
microdaos: List[NodeMicrodaoSummary] = []
|
||
metrics: Optional[NodeMetrics] = None
|
||
|
||
|
||
class ModelBindings(BaseModel):
|
||
"""Agent model bindings for AI capabilities"""
|
||
primary_model: Optional[str] = None # e.g., "qwen3:8b"
|
||
supported_kinds: List[str] = [] # e.g., ["text", "vision", "audio"]
|
||
|
||
|
||
class UsageStats(BaseModel):
|
||
"""Agent usage statistics"""
|
||
tokens_total_24h: Optional[int] = None
|
||
calls_total_24h: Optional[int] = None
|
||
last_active: Optional[str] = None
|
||
|
||
|
||
class MicrodaoBadge(BaseModel):
|
||
"""MicroDAO badge for agent display"""
|
||
id: str
|
||
name: str
|
||
slug: Optional[str] = None
|
||
role: Optional[str] = None # orchestrator, member, etc.
|
||
is_public: bool = True
|
||
is_platform: bool = False
|
||
logo_url: Optional[str] = None
|
||
banner_url: Optional[str] = None
|
||
|
||
|
||
class AgentCrewInfo(BaseModel):
|
||
"""Information about agent's CrewAI team"""
|
||
has_crew_team: bool
|
||
crew_team_key: Optional[str] = None
|
||
matrix_room_id: Optional[str] = None
|
||
|
||
|
||
class AgentSummary(BaseModel):
|
||
"""Unified Agent summary for Agent Console and Citizens"""
|
||
id: str
|
||
slug: Optional[str] = None
|
||
display_name: str
|
||
title: Optional[str] = None # public_title
|
||
tagline: Optional[str] = None # public_tagline
|
||
kind: str = "assistant"
|
||
avatar_url: Optional[str] = None
|
||
status: str = "offline"
|
||
|
||
# Node info
|
||
node_id: Optional[str] = None
|
||
node_label: Optional[str] = None # "НОДА1" / "НОДА2"
|
||
home_node: Optional[HomeNodeView] = None
|
||
|
||
# Governance & DAIS (A1, A2)
|
||
gov_level: Optional[str] = None # personal, core_team, orchestrator, district_lead, city_governance
|
||
dais_identity_id: Optional[str] = None # DAIS identity reference
|
||
|
||
# Visibility & roles
|
||
visibility_scope: str = "city" # global, microdao, private
|
||
is_listed_in_directory: bool = True
|
||
is_system: bool = False
|
||
is_public: bool = False
|
||
is_orchestrator: bool = False # Can create/manage microDAOs
|
||
|
||
# MicroDAO (A3)
|
||
primary_microdao_id: Optional[str] = None
|
||
primary_microdao_name: Optional[str] = None
|
||
primary_microdao_slug: Optional[str] = None
|
||
home_microdao_id: Optional[str] = None # Owner microDAO
|
||
home_microdao_name: Optional[str] = None
|
||
home_microdao_slug: Optional[str] = None
|
||
district: Optional[str] = None
|
||
microdaos: List[MicrodaoBadge] = []
|
||
microdao_memberships: List[Dict[str, Any]] = [] # backward compatibility
|
||
|
||
# Skills
|
||
public_skills: List[str] = []
|
||
|
||
# CrewAI
|
||
crew_info: Optional[AgentCrewInfo] = None
|
||
|
||
# Future: model bindings and usage stats
|
||
model_bindings: Optional[ModelBindings] = None
|
||
usage_stats: Optional[UsageStats] = None
|
||
|
||
|
||
class PublicCitizenSummary(BaseModel):
|
||
slug: str
|
||
display_name: str
|
||
public_title: Optional[str] = None
|
||
public_tagline: Optional[str] = None
|
||
avatar_url: Optional[str] = None
|
||
kind: Optional[str] = None
|
||
district: Optional[str] = None
|
||
primary_room_slug: Optional[str] = None
|
||
public_skills: List[str] = []
|
||
online_status: Optional[str] = "unknown"
|
||
status: Optional[str] = None # backward compatibility
|
||
# Home node info
|
||
home_node: Optional[HomeNodeView] = None
|
||
node_id: Optional[str] = None
|
||
|
||
# TASK 037A: Alignment
|
||
home_microdao_slug: Optional[str] = None
|
||
home_microdao_name: Optional[str] = None
|
||
primary_city_room: Optional["CityRoomSummary"] = None
|
||
|
||
|
||
class PublicCitizenProfile(BaseModel):
|
||
slug: str
|
||
display_name: str
|
||
kind: Optional[str] = None
|
||
public_title: Optional[str] = None
|
||
public_tagline: Optional[str] = None
|
||
district: Optional[str] = None
|
||
avatar_url: Optional[str] = None
|
||
status: Optional[str] = None
|
||
node_id: Optional[str] = None
|
||
public_skills: List[str] = []
|
||
city_presence: Optional[CityPresenceView] = None
|
||
dais_public: Dict[str, Any]
|
||
interaction: Dict[str, Any]
|
||
metrics_public: Dict[str, Any]
|
||
admin_panel_url: Optional[str] = None
|
||
microdao: Optional[Dict[str, Any]] = None
|
||
# Home node info
|
||
home_node: Optional[HomeNodeView] = None
|
||
|
||
|
||
class CitizenInteractionInfo(BaseModel):
|
||
slug: str
|
||
display_name: str
|
||
primary_room_slug: Optional[str] = None
|
||
primary_room_id: Optional[str] = None
|
||
primary_room_name: Optional[str] = None
|
||
matrix_user_id: Optional[str] = None
|
||
district: Optional[str] = None
|
||
microdao_slug: Optional[str] = None
|
||
microdao_name: Optional[str] = None
|
||
|
||
|
||
class CitizenAskRequest(BaseModel):
|
||
question: str
|
||
context: Optional[str] = None
|
||
|
||
|
||
class CitizenAskResponse(BaseModel):
|
||
answer: str
|
||
agent_display_name: str
|
||
agent_id: str
|
||
|
||
|
||
# =============================================================================
|
||
# MicroDAO
|
||
# =============================================================================
|
||
|
||
class MicrodaoCitizenView(BaseModel):
|
||
slug: str
|
||
display_name: str
|
||
public_title: Optional[str] = None
|
||
public_tagline: Optional[str] = None
|
||
avatar_url: Optional[str] = None
|
||
district: Optional[str] = None
|
||
primary_room_slug: Optional[str] = None
|
||
|
||
|
||
class MicrodaoSummary(BaseModel):
|
||
"""MicroDAO summary for list view"""
|
||
id: str
|
||
slug: str
|
||
name: str
|
||
description: Optional[str] = None
|
||
district: Optional[str] = None
|
||
|
||
# Visibility & type
|
||
is_public: bool = True
|
||
is_platform: bool = False # Is a platform/district
|
||
is_active: bool = True
|
||
|
||
# Pinning & ordering
|
||
is_pinned: bool = False # Pinned to top of list
|
||
pinned_weight: int = 0 # Order within pinned items (1-N)
|
||
|
||
# Orchestrator
|
||
orchestrator_agent_id: Optional[str] = None
|
||
orchestrator_agent_name: Optional[str] = None
|
||
|
||
# Hierarchy
|
||
parent_microdao_id: Optional[str] = None
|
||
parent_microdao_slug: Optional[str] = None
|
||
|
||
# Stats
|
||
logo_url: Optional[str] = None
|
||
banner_url: Optional[str] = None
|
||
member_count: int = 0 # alias for agents_count
|
||
agents_count: int = 0 # backward compatibility
|
||
room_count: int = 0 # alias for rooms_count
|
||
rooms_count: int = 0 # backward compatibility
|
||
channels_count: int = 0
|
||
|
||
|
||
class MicrodaoChannelView(BaseModel):
|
||
"""Channel/integration view for MicroDAO"""
|
||
kind: str # 'matrix' | 'telegram' | 'city_room' | 'crew'
|
||
ref_id: str
|
||
display_name: Optional[str] = None
|
||
is_primary: bool
|
||
|
||
|
||
class MicrodaoAgentView(BaseModel):
|
||
"""Agent view within MicroDAO"""
|
||
agent_id: str
|
||
display_name: str
|
||
role: Optional[str] = None
|
||
is_core: bool
|
||
|
||
|
||
class CityRoomSummary(BaseModel):
|
||
"""Summary of a city room for chat embedding and multi-room support"""
|
||
id: str
|
||
slug: str
|
||
name: str
|
||
matrix_room_id: Optional[str] = None
|
||
microdao_id: Optional[str] = None
|
||
microdao_slug: Optional[str] = None
|
||
room_role: Optional[str] = None # 'primary', 'lobby', 'team', 'research', 'security', 'governance', 'orchestrator_team'
|
||
is_public: bool = True
|
||
sort_order: int = 100
|
||
logo_url: Optional[str] = None
|
||
banner_url: Optional[str] = None
|
||
|
||
|
||
class MicrodaoRoomsList(BaseModel):
|
||
"""List of rooms belonging to a MicroDAO"""
|
||
microdao_id: str
|
||
microdao_slug: str
|
||
rooms: List[CityRoomSummary] = []
|
||
|
||
|
||
class MicrodaoRoomUpdate(BaseModel):
|
||
"""Update request for MicroDAO room settings"""
|
||
room_role: Optional[str] = None
|
||
is_public: Optional[bool] = None
|
||
sort_order: Optional[int] = None
|
||
set_primary: Optional[bool] = None # if true, mark as primary
|
||
|
||
|
||
class AttachExistingRoomRequest(BaseModel):
|
||
"""Request to attach an existing city room to a MicroDAO"""
|
||
room_id: str
|
||
room_role: Optional[str] = None
|
||
is_public: bool = True
|
||
sort_order: int = 100
|
||
|
||
|
||
class MicrodaoDetail(BaseModel):
|
||
"""Full MicroDAO detail view"""
|
||
id: str
|
||
slug: str
|
||
name: str
|
||
description: Optional[str] = None
|
||
district: Optional[str] = None
|
||
|
||
# Visibility & type
|
||
is_public: bool = True
|
||
is_platform: bool = False
|
||
is_active: bool = True
|
||
|
||
# Orchestrator
|
||
orchestrator_agent_id: Optional[str] = None
|
||
orchestrator_display_name: Optional[str] = None
|
||
|
||
# Hierarchy
|
||
parent_microdao_id: Optional[str] = None
|
||
parent_microdao_slug: Optional[str] = None
|
||
child_microdaos: List["MicrodaoSummary"] = []
|
||
|
||
# Content
|
||
logo_url: Optional[str] = None
|
||
banner_url: Optional[str] = None
|
||
agents: List[MicrodaoAgentView] = []
|
||
channels: List[MicrodaoChannelView] = []
|
||
|
||
# Multi-room support
|
||
rooms: List[CityRoomSummary] = []
|
||
public_citizens: List[MicrodaoCitizenView] = []
|
||
|
||
# Primary city room for chat
|
||
primary_city_room: Optional[CityRoomSummary] = None
|
||
|
||
|
||
class AgentMicrodaoMembership(BaseModel):
|
||
microdao_id: str
|
||
microdao_slug: str
|
||
microdao_name: str
|
||
logo_url: Optional[str] = None
|
||
role: Optional[str] = None
|
||
is_core: bool = False
|
||
|
||
|
||
class MicrodaoOption(BaseModel):
|
||
id: str
|
||
slug: str
|
||
name: str
|
||
district: Optional[str] = None
|
||
is_active: bool = True
|
||
|
||
|
||
# =============================================================================
|
||
# Agent Management (Create/Delete)
|
||
# =============================================================================
|
||
|
||
class CreateAgentRequest(BaseModel):
|
||
"""Request to create a new agent"""
|
||
slug: str
|
||
display_name: str
|
||
kind: str = "assistant" # assistant, orchestrator, specialist, civic
|
||
role: Optional[str] = None
|
||
model: Optional[str] = None
|
||
node_id: Optional[str] = None
|
||
home_node_id: Optional[str] = None
|
||
home_microdao_id: Optional[str] = None
|
||
district: Optional[str] = None
|
||
primary_room_slug: Optional[str] = None
|
||
avatar_url: Optional[str] = None
|
||
color_hint: Optional[str] = None
|
||
is_public: bool = False
|
||
is_orchestrator: bool = False
|
||
priority: str = "medium"
|
||
|
||
|
||
class CreateAgentResponse(BaseModel):
|
||
"""Response after creating an agent"""
|
||
id: str
|
||
slug: str
|
||
display_name: str
|
||
kind: str
|
||
node_id: Optional[str] = None
|
||
home_microdao_id: Optional[str] = None
|
||
district: Optional[str] = None
|
||
created_at: datetime
|
||
|
||
|
||
class DeleteAgentResponse(BaseModel):
|
||
"""Response after deleting an agent"""
|
||
ok: bool
|
||
message: str
|
||
agent_id: str
|
||
|
||
|
||
# =============================================================================
|
||
# Visibility Updates (Task 029)
|
||
# =============================================================================
|
||
|
||
class AgentVisibilityUpdate(BaseModel):
|
||
"""Update agent visibility settings"""
|
||
is_public: bool
|
||
visibility_scope: Optional[str] = None # 'global' | 'microdao' | 'private'
|
||
|
||
|
||
class MicrodaoVisibilityUpdate(BaseModel):
|
||
"""Update MicroDAO visibility settings"""
|
||
is_public: bool
|
||
is_platform: Optional[bool] = None # Upgrade to platform/district
|
||
|
||
|
||
class MicrodaoCreateRequest(BaseModel):
|
||
"""Request to create MicroDAO from agent (orchestrator flow)"""
|
||
name: str
|
||
slug: str
|
||
description: Optional[str] = None
|
||
make_platform: bool = False # If true -> is_platform = true
|
||
is_public: bool = True
|
||
parent_microdao_id: Optional[str] = None
|
||
|
||
|
||
class SwapperModel(BaseModel):
|
||
"""Model info from Swapper service"""
|
||
name: str
|
||
loaded: bool
|
||
type: Optional[str] = None
|
||
vram_gb: Optional[float] = None
|
||
|
||
|
||
class NodeSwapperDetail(BaseModel):
|
||
"""Detailed Swapper info for Node Cabinet"""
|
||
node_id: str
|
||
healthy: bool
|
||
models_loaded: int
|
||
models_total: int
|
||
models: List[SwapperModel] = []
|
||
|
||
|
||
# =============================================================================
|
||
# DAGI Router
|
||
# =============================================================================
|
||
|
||
class DagiRouterHealth(BaseModel):
|
||
"""DAGI Router health status"""
|
||
node_id: str
|
||
status: str # "up", "down", "degraded"
|
||
version: Optional[str] = None
|
||
agent_count: int = 0
|
||
latency_ms: Optional[float] = None
|
||
|
||
|
||
class DagiRouterAgent(BaseModel):
|
||
"""Agent info from DAGI Router"""
|
||
id: str
|
||
name: Optional[str] = None
|
||
kind: Optional[str] = None
|
||
runtime: Optional[str] = None # e.g. "NODE1-router", "NODE2-router"
|
||
node_id: str
|
||
last_seen_at: Optional[datetime] = None
|
||
status: str = "active" # "active", "phantom", "stale"
|
||
has_db_record: bool = False
|
||
|
||
|
||
class DagiRouterAgentsResponse(BaseModel):
|
||
"""Response for DAGI Router agents endpoint"""
|
||
node_id: str
|
||
total: int = 0
|
||
active: int = 0
|
||
phantom: int = 0
|
||
stale: int = 0
|
||
agents: List[DagiRouterAgent] = []
|
||
|
||
|
||
class DagiRouterSummary(BaseModel):
|
||
"""Summary of DAGI Router status for a node"""
|
||
node_id: str
|
||
status: str # "up", "down", "degraded"
|
||
version: Optional[str] = None
|
||
latency_ms: Optional[float] = None
|
||
router_agent_count: int = 0
|
||
db_agent_count: int = 0
|
||
active: int = 0
|
||
phantom: int = 0
|
||
stale: int = 0
|
||
last_audit_at: Optional[datetime] = None
|