TASK_PHASE_MVP_DAGI_INTEGRATION_FIX_20251201 A) Agents Layer: - A1: Added gov_level to API and UI (list + profile) - A2: Added dais_identity_id to API and UI - A3: Added home_microdao_id/name/slug for ownership display B) MicroDAO Layer: - B1/B2: Already implemented (agents, rooms, citizens, district badge) C) Nodes Layer: - C1: Node Dashboard already implemented - C2: Created nodes table migration with owner_microdao_id - C3: INSERT NODE1/NODE2 with dao_daarion ownership D) Backend Fixes: - D1: Extended /api/agents/* with DAIS/governance fields - D2/D3: Already implemented Files changed: - services/city-service/repo_city.py - services/city-service/models_city.py - services/city-service/routes_city.py - services/city-service/migrations.py - apps/web/src/lib/types/agents.ts - apps/web/src/lib/agent-dashboard.ts - apps/web/src/app/agents/page.tsx - apps/web/src/components/agent-dashboard/AgentSummaryCard.tsx Reports: - docs/debug/mvp_dagi_integration_fix_report_20251201.md - docs/tasks/TASK_PHASE_MVP_DAGI_INTEGRATION_FIX_20251201.md
584 lines
16 KiB
Python
584 lines
16 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 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] = []
|
||
|
||
|
||
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
|
||
|
||
# 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
|
||
|
||
|
||
# =============================================================================
|
||
# 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
|