feat: align agent/microdao model - add is_orchestrator, is_platform, hierarchy
This commit is contained in:
@@ -233,6 +233,8 @@ class MicrodaoBadge(BaseModel):
|
||||
name: str
|
||||
slug: Optional[str] = None
|
||||
role: Optional[str] = None # orchestrator, member, etc.
|
||||
is_public: bool = True
|
||||
is_platform: bool = False
|
||||
|
||||
|
||||
class AgentSummary(BaseModel):
|
||||
@@ -251,11 +253,12 @@ class AgentSummary(BaseModel):
|
||||
node_label: Optional[str] = None # "НОДА1" / "НОДА2"
|
||||
home_node: Optional[HomeNodeView] = None
|
||||
|
||||
# Visibility
|
||||
visibility_scope: str = "city" # city, microdao, owner_only
|
||||
# Visibility & roles
|
||||
visibility_scope: str = "city" # global, microdao, private
|
||||
is_listed_in_directory: bool = True
|
||||
is_system: bool = False
|
||||
is_public: bool = False # backward compatibility
|
||||
is_public: bool = False
|
||||
is_orchestrator: bool = False # Can create/manage microDAOs
|
||||
|
||||
# MicroDAO
|
||||
primary_microdao_id: Optional[str] = None
|
||||
@@ -354,12 +357,27 @@ class MicrodaoSummary(BaseModel):
|
||||
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
|
||||
is_active: bool
|
||||
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
|
||||
agents_count: int
|
||||
rooms_count: int
|
||||
channels_count: int
|
||||
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):
|
||||
@@ -385,13 +403,25 @@ class MicrodaoDetail(BaseModel):
|
||||
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
|
||||
is_active: bool
|
||||
is_public: bool
|
||||
|
||||
# Hierarchy
|
||||
parent_microdao_id: Optional[str] = None
|
||||
parent_microdao_slug: Optional[str] = None
|
||||
child_microdaos: List["MicrodaoSummary"] = []
|
||||
|
||||
# Content
|
||||
logo_url: Optional[str] = None
|
||||
agents: List[MicrodaoAgentView]
|
||||
channels: List[MicrodaoChannelView]
|
||||
agents: List[MicrodaoAgentView] = []
|
||||
channels: List[MicrodaoChannelView] = []
|
||||
public_citizens: List[MicrodaoCitizenView] = []
|
||||
|
||||
|
||||
|
||||
@@ -295,6 +295,8 @@ async def get_rooms_for_map() -> List[dict]:
|
||||
async def list_agent_summaries(
|
||||
*,
|
||||
node_id: Optional[str] = None,
|
||||
microdao_id: Optional[str] = None,
|
||||
is_public: Optional[bool] = None,
|
||||
visibility_scope: Optional[str] = None,
|
||||
listed_only: Optional[bool] = None,
|
||||
kinds: Optional[List[str]] = None,
|
||||
@@ -322,6 +324,14 @@ async def list_agent_summaries(
|
||||
params.append(node_id)
|
||||
where_clauses.append(f"a.node_id = ${len(params)}")
|
||||
|
||||
if microdao_id:
|
||||
params.append(microdao_id)
|
||||
where_clauses.append(f"EXISTS (SELECT 1 FROM microdao_agents ma WHERE ma.agent_id = a.id AND ma.microdao_id = ${len(params)})")
|
||||
|
||||
if is_public is not None:
|
||||
params.append(is_public)
|
||||
where_clauses.append(f"COALESCE(a.is_public, false) = ${len(params)}")
|
||||
|
||||
if visibility_scope:
|
||||
params.append(visibility_scope)
|
||||
where_clauses.append(f"COALESCE(a.visibility_scope, 'city') = ${len(params)}")
|
||||
@@ -359,6 +369,7 @@ async def list_agent_summaries(
|
||||
COALESCE(a.is_listed_in_directory, true) AS is_listed_in_directory,
|
||||
COALESCE(a.is_system, false) AS is_system,
|
||||
COALESCE(a.is_public, false) AS is_public,
|
||||
COALESCE(a.is_orchestrator, false) AS is_orchestrator,
|
||||
a.primary_microdao_id,
|
||||
pm.name AS primary_microdao_name,
|
||||
pm.slug AS primary_microdao_slug,
|
||||
@@ -1246,17 +1257,26 @@ async def get_microdaos(district: Optional[str] = None, q: Optional[str] = None,
|
||||
m.name,
|
||||
m.description,
|
||||
m.district,
|
||||
m.owner_agent_id as orchestrator_agent_id,
|
||||
COALESCE(m.orchestrator_agent_id, m.owner_agent_id) as orchestrator_agent_id,
|
||||
oa.display_name as orchestrator_agent_name,
|
||||
m.is_active,
|
||||
COALESCE(m.is_public, true) as is_public,
|
||||
COALESCE(m.is_platform, false) as is_platform,
|
||||
m.parent_microdao_id,
|
||||
pm.slug as parent_microdao_slug,
|
||||
m.logo_url,
|
||||
COUNT(DISTINCT ma.agent_id) AS agents_count,
|
||||
COUNT(DISTINCT ma.agent_id) AS member_count,
|
||||
COUNT(DISTINCT mc.id) AS channels_count,
|
||||
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS rooms_count
|
||||
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS rooms_count,
|
||||
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS room_count
|
||||
FROM microdaos m
|
||||
LEFT JOIN microdao_agents ma ON ma.microdao_id = m.id
|
||||
LEFT JOIN microdao_channels mc ON mc.microdao_id = m.id
|
||||
LEFT JOIN agents oa ON COALESCE(m.orchestrator_agent_id, m.owner_agent_id) = oa.id
|
||||
LEFT JOIN microdaos pm ON m.parent_microdao_id = pm.id
|
||||
WHERE {where_sql}
|
||||
GROUP BY m.id
|
||||
GROUP BY m.id, oa.display_name, pm.slug
|
||||
ORDER BY m.name
|
||||
LIMIT ${len(params) + 1} OFFSET ${len(params) + 2}
|
||||
"""
|
||||
@@ -1269,6 +1289,93 @@ async def get_microdaos(district: Optional[str] = None, q: Optional[str] = None,
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
async def list_microdao_summaries(
|
||||
*,
|
||||
is_public: Optional[bool] = None,
|
||||
is_platform: Optional[bool] = None,
|
||||
district: Optional[str] = None,
|
||||
q: Optional[str] = None,
|
||||
limit: int = 50,
|
||||
offset: int = 0
|
||||
) -> List[dict]:
|
||||
"""
|
||||
Unified method to list microDAOs.
|
||||
Wraps get_microdaos with additional filtering.
|
||||
"""
|
||||
pool = await get_pool()
|
||||
|
||||
params = []
|
||||
where_clauses = [
|
||||
"COALESCE(m.is_archived, false) = false",
|
||||
"COALESCE(m.is_test, false) = false",
|
||||
"m.deleted_at IS NULL",
|
||||
"m.is_active = true"
|
||||
]
|
||||
|
||||
if is_public is not None:
|
||||
params.append(is_public)
|
||||
where_clauses.append(f"COALESCE(m.is_public, true) = ${len(params)}")
|
||||
|
||||
if is_platform is not None:
|
||||
params.append(is_platform)
|
||||
where_clauses.append(f"COALESCE(m.is_platform, false) = ${len(params)}")
|
||||
|
||||
if district:
|
||||
params.append(district)
|
||||
where_clauses.append(f"m.district = ${len(params)}")
|
||||
|
||||
if q:
|
||||
params.append(f"%{q}%")
|
||||
where_clauses.append(f"(m.name ILIKE ${len(params)} OR m.description ILIKE ${len(params)})")
|
||||
|
||||
where_sql = " AND ".join(where_clauses)
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
m.id,
|
||||
m.slug,
|
||||
m.name,
|
||||
m.description,
|
||||
m.district,
|
||||
COALESCE(m.orchestrator_agent_id, m.owner_agent_id) as orchestrator_agent_id,
|
||||
oa.display_name as orchestrator_agent_name,
|
||||
m.is_active,
|
||||
COALESCE(m.is_public, true) as is_public,
|
||||
COALESCE(m.is_platform, false) as is_platform,
|
||||
m.parent_microdao_id,
|
||||
pm.slug as parent_microdao_slug,
|
||||
m.logo_url,
|
||||
COUNT(DISTINCT ma.agent_id) AS agents_count,
|
||||
COUNT(DISTINCT ma.agent_id) AS member_count,
|
||||
COUNT(DISTINCT mc.id) AS channels_count,
|
||||
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS rooms_count,
|
||||
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS room_count
|
||||
FROM microdaos m
|
||||
LEFT JOIN microdao_agents ma ON ma.microdao_id = m.id
|
||||
LEFT JOIN microdao_channels mc ON mc.microdao_id = m.id
|
||||
LEFT JOIN agents oa ON COALESCE(m.orchestrator_agent_id, m.owner_agent_id) = oa.id
|
||||
LEFT JOIN microdaos pm ON m.parent_microdao_id = pm.id
|
||||
WHERE {where_sql}
|
||||
GROUP BY m.id, oa.display_name, pm.slug
|
||||
ORDER BY m.name
|
||||
LIMIT ${len(params) + 1} OFFSET ${len(params) + 2}
|
||||
"""
|
||||
|
||||
params.append(limit)
|
||||
params.append(offset)
|
||||
|
||||
rows = await pool.fetch(query, *params)
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
async def get_microdao_detail(slug: str) -> Optional[dict]:
|
||||
"""
|
||||
Get detailed microDAO info including agents, channels, children.
|
||||
Alias for get_microdao_by_slug with clearer naming.
|
||||
"""
|
||||
return await get_microdao_by_slug(slug)
|
||||
|
||||
|
||||
async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
||||
"""Отримати детальну інформацію про MicroDAO"""
|
||||
pool = await get_pool()
|
||||
@@ -1281,14 +1388,21 @@ async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
||||
m.name,
|
||||
m.description,
|
||||
m.district,
|
||||
m.owner_agent_id as orchestrator_agent_id,
|
||||
COALESCE(m.orchestrator_agent_id, m.owner_agent_id) as orchestrator_agent_id,
|
||||
a.display_name as orchestrator_display_name,
|
||||
m.is_active,
|
||||
m.is_public,
|
||||
m.logo_url,
|
||||
a.display_name as orchestrator_display_name
|
||||
COALESCE(m.is_public, true) as is_public,
|
||||
COALESCE(m.is_platform, false) as is_platform,
|
||||
m.parent_microdao_id,
|
||||
pm.slug as parent_microdao_slug,
|
||||
m.logo_url
|
||||
FROM microdaos m
|
||||
LEFT JOIN agents a ON m.owner_agent_id = a.id
|
||||
WHERE m.slug = $1 AND COALESCE(m.is_archived, false) = false
|
||||
LEFT JOIN agents a ON COALESCE(m.orchestrator_agent_id, m.owner_agent_id) = a.id
|
||||
LEFT JOIN microdaos pm ON m.parent_microdao_id = pm.id
|
||||
WHERE m.slug = $1
|
||||
AND COALESCE(m.is_archived, false) = false
|
||||
AND COALESCE(m.is_test, false) = false
|
||||
AND m.deleted_at IS NULL
|
||||
"""
|
||||
|
||||
dao_row = await pool.fetchrow(query_dao, slug)
|
||||
@@ -1308,6 +1422,9 @@ async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
||||
FROM microdao_agents ma
|
||||
JOIN agents a ON ma.agent_id = a.id
|
||||
WHERE ma.microdao_id = $1
|
||||
AND COALESCE(a.is_archived, false) = false
|
||||
AND COALESCE(a.is_test, false) = false
|
||||
AND a.deleted_at IS NULL
|
||||
ORDER BY ma.is_core DESC, ma.role
|
||||
"""
|
||||
agents_rows = await pool.fetch(query_agents, dao_id)
|
||||
@@ -1327,6 +1444,20 @@ async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
||||
channels_rows = await pool.fetch(query_channels, dao_id)
|
||||
result["channels"] = [dict(row) for row in channels_rows]
|
||||
|
||||
# 4. Get child microDAOs
|
||||
query_children = """
|
||||
SELECT id, slug, name, COALESCE(is_public, true) as is_public,
|
||||
COALESCE(is_platform, false) as is_platform
|
||||
FROM microdaos
|
||||
WHERE parent_microdao_id = $1
|
||||
AND COALESCE(is_archived, false) = false
|
||||
AND COALESCE(is_test, false) = false
|
||||
AND deleted_at IS NULL
|
||||
ORDER BY name
|
||||
"""
|
||||
children_rows = await pool.fetch(query_children, dao_id)
|
||||
result["child_microdaos"] = [dict(row) for row in children_rows]
|
||||
|
||||
public_citizens = await get_microdao_public_citizens(dao_id)
|
||||
result["public_citizens"] = public_citizens
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ class MicrodaoMembershipPayload(BaseModel):
|
||||
async def list_agents(
|
||||
kind: Optional[str] = Query(None, description="Filter by agent kind"),
|
||||
node_id: Optional[str] = Query(None, description="Filter by node_id"),
|
||||
visibility_scope: Optional[str] = Query(None, description="Filter by visibility: city, microdao, owner_only"),
|
||||
microdao_id: Optional[str] = Query(None, description="Filter by microDAO id"),
|
||||
is_public: Optional[bool] = Query(None, description="Filter by public status"),
|
||||
visibility_scope: Optional[str] = Query(None, description="Filter by visibility: global, microdao, private"),
|
||||
include_system: bool = Query(True, description="Include system agents"),
|
||||
limit: int = Query(100, le=200),
|
||||
offset: int = Query(0, ge=0)
|
||||
@@ -78,6 +80,8 @@ async def list_agents(
|
||||
kinds_list = [kind] if kind else None
|
||||
agents, total = await repo_city.list_agent_summaries(
|
||||
node_id=node_id,
|
||||
microdao_id=microdao_id,
|
||||
is_public=is_public,
|
||||
visibility_scope=visibility_scope,
|
||||
kinds=kinds_list,
|
||||
include_system=include_system,
|
||||
@@ -126,6 +130,7 @@ async def list_agents(
|
||||
is_listed_in_directory=agent.get("is_listed_in_directory", True),
|
||||
is_system=agent.get("is_system", False),
|
||||
is_public=agent.get("is_public", False),
|
||||
is_orchestrator=agent.get("is_orchestrator", False),
|
||||
primary_microdao_id=agent.get("primary_microdao_id"),
|
||||
primary_microdao_name=agent.get("primary_microdao_name"),
|
||||
primary_microdao_slug=agent.get("primary_microdao_slug"),
|
||||
@@ -1320,6 +1325,8 @@ async def get_agents_presence_snapshot():
|
||||
@router.get("/microdao", response_model=List[MicrodaoSummary])
|
||||
async def get_microdaos(
|
||||
district: Optional[str] = Query(None, description="Filter by district"),
|
||||
is_public: Optional[bool] = Query(None, description="Filter by public status"),
|
||||
is_platform: Optional[bool] = Query(None, description="Filter by platform status"),
|
||||
q: Optional[str] = Query(None, description="Search by name/description"),
|
||||
limit: int = Query(50, le=100),
|
||||
offset: int = Query(0, ge=0)
|
||||
@@ -1328,10 +1335,19 @@ async def get_microdaos(
|
||||
Отримати список MicroDAOs.
|
||||
|
||||
- **district**: фільтр по дістрікту (Core, Energy, Green, Labs, etc.)
|
||||
- **is_public**: фільтр по публічності
|
||||
- **is_platform**: фільтр по типу (платформа/дістрікт)
|
||||
- **q**: пошук по назві або опису
|
||||
"""
|
||||
try:
|
||||
daos = await repo_city.get_microdaos(district=district, q=q, limit=limit, offset=offset)
|
||||
daos = await repo_city.list_microdao_summaries(
|
||||
district=district,
|
||||
is_public=is_public,
|
||||
is_platform=is_platform,
|
||||
q=q,
|
||||
limit=limit,
|
||||
offset=offset
|
||||
)
|
||||
|
||||
result = []
|
||||
for dao in daos:
|
||||
@@ -1341,10 +1357,17 @@ async def get_microdaos(
|
||||
name=dao["name"],
|
||||
description=dao.get("description"),
|
||||
district=dao.get("district"),
|
||||
orchestrator_agent_id=dao.get("orchestrator_agent_id"),
|
||||
is_public=dao.get("is_public", True),
|
||||
is_platform=dao.get("is_platform", False),
|
||||
is_active=dao.get("is_active", True),
|
||||
orchestrator_agent_id=dao.get("orchestrator_agent_id"),
|
||||
orchestrator_agent_name=dao.get("orchestrator_agent_name"),
|
||||
parent_microdao_id=dao.get("parent_microdao_id"),
|
||||
parent_microdao_slug=dao.get("parent_microdao_slug"),
|
||||
logo_url=dao.get("logo_url"),
|
||||
member_count=dao.get("member_count", 0),
|
||||
agents_count=dao.get("agents_count", 0),
|
||||
room_count=dao.get("room_count", 0),
|
||||
rooms_count=dao.get("rooms_count", 0),
|
||||
channels_count=dao.get("channels_count", 0)
|
||||
))
|
||||
@@ -1405,16 +1428,31 @@ async def get_microdao_by_slug(slug: str):
|
||||
primary_room_slug=citizen.get("public_primary_room_slug")
|
||||
))
|
||||
|
||||
# Build child microDAOs list
|
||||
child_microdaos = []
|
||||
for child in dao.get("child_microdaos", []):
|
||||
child_microdaos.append(MicrodaoSummary(
|
||||
id=child["id"],
|
||||
slug=child["slug"],
|
||||
name=child["name"],
|
||||
is_public=child.get("is_public", True),
|
||||
is_platform=child.get("is_platform", False)
|
||||
))
|
||||
|
||||
return MicrodaoDetail(
|
||||
id=dao["id"],
|
||||
slug=dao["slug"],
|
||||
name=dao["name"],
|
||||
description=dao.get("description"),
|
||||
district=dao.get("district"),
|
||||
is_public=dao.get("is_public", True),
|
||||
is_platform=dao.get("is_platform", False),
|
||||
is_active=dao.get("is_active", True),
|
||||
orchestrator_agent_id=dao.get("orchestrator_agent_id"),
|
||||
orchestrator_display_name=dao.get("orchestrator_display_name"),
|
||||
is_active=dao.get("is_active", True),
|
||||
is_public=dao.get("is_public", True),
|
||||
parent_microdao_id=dao.get("parent_microdao_id"),
|
||||
parent_microdao_slug=dao.get("parent_microdao_slug"),
|
||||
child_microdaos=child_microdaos,
|
||||
logo_url=dao.get("logo_url"),
|
||||
agents=agents,
|
||||
channels=channels,
|
||||
|
||||
Reference in New Issue
Block a user