feat: implement Task 029 (Agent Orchestrator & Visibility Flow)
This commit is contained in:
@@ -440,3 +440,29 @@ class MicrodaoOption(BaseModel):
|
||||
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
|
||||
|
||||
|
||||
@@ -453,11 +453,47 @@ async def get_all_agents() -> List[dict]:
|
||||
|
||||
|
||||
async def update_agent_visibility(
|
||||
agent_id: str,
|
||||
*,
|
||||
is_public: bool,
|
||||
visibility_scope: Optional[str] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Оновити налаштування видимості агента.
|
||||
Returns updated agent data or None if not found.
|
||||
"""
|
||||
pool = await get_pool()
|
||||
|
||||
# Build dynamic update
|
||||
set_parts = ["is_public = $2", "updated_at = NOW()"]
|
||||
params = [agent_id, is_public]
|
||||
|
||||
if visibility_scope is not None:
|
||||
params.append(visibility_scope)
|
||||
set_parts.append(f"visibility_scope = ${len(params)}")
|
||||
|
||||
# Also update is_listed_in_directory based on is_public
|
||||
set_parts.append("is_listed_in_directory = $2") # same as is_public
|
||||
|
||||
query = f"""
|
||||
UPDATE agents
|
||||
SET {', '.join(set_parts)}
|
||||
WHERE id = $1
|
||||
AND COALESCE(is_archived, false) = false
|
||||
AND COALESCE(is_test, false) = false
|
||||
RETURNING id, display_name, is_public, visibility_scope, is_listed_in_directory
|
||||
"""
|
||||
|
||||
result = await pool.fetchrow(query, *params)
|
||||
return dict(result) if result else None
|
||||
|
||||
|
||||
async def update_agent_visibility_legacy(
|
||||
agent_id: str,
|
||||
visibility_scope: str,
|
||||
is_listed_in_directory: bool
|
||||
) -> bool:
|
||||
"""Оновити налаштування видимості агента"""
|
||||
"""Legacy: Оновити налаштування видимості агента (backward compatibility)"""
|
||||
pool = await get_pool()
|
||||
|
||||
query = """
|
||||
@@ -1515,3 +1551,105 @@ async def get_node_by_id(node_id: str) -> Optional[dict]:
|
||||
row = await pool.fetchrow(query, node_id)
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MicroDAO Visibility & Creation (Task 029)
|
||||
# =============================================================================
|
||||
|
||||
async def update_microdao_visibility(
|
||||
microdao_id: str,
|
||||
*,
|
||||
is_public: bool,
|
||||
is_platform: Optional[bool] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Оновити налаштування видимості MicroDAO.
|
||||
Returns updated MicroDAO data or None if not found.
|
||||
"""
|
||||
pool = await get_pool()
|
||||
|
||||
set_parts = ["is_public = $2", "updated_at = NOW()"]
|
||||
params = [microdao_id, is_public]
|
||||
|
||||
if is_platform is not None:
|
||||
params.append(is_platform)
|
||||
set_parts.append(f"is_platform = ${len(params)}")
|
||||
|
||||
query = f"""
|
||||
UPDATE microdaos
|
||||
SET {', '.join(set_parts)}
|
||||
WHERE id = $1
|
||||
AND COALESCE(is_archived, false) = false
|
||||
AND COALESCE(is_test, false) = false
|
||||
RETURNING id, slug, name, is_public, is_platform
|
||||
"""
|
||||
|
||||
result = await pool.fetchrow(query, *params)
|
||||
return dict(result) if result else None
|
||||
|
||||
|
||||
async def create_microdao_for_agent(
|
||||
orchestrator_agent_id: str,
|
||||
*,
|
||||
name: str,
|
||||
slug: str,
|
||||
description: Optional[str] = None,
|
||||
make_platform: bool = False,
|
||||
is_public: bool = True,
|
||||
parent_microdao_id: Optional[str] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Створює microDAO, прив'язує його до агента-оркестратора.
|
||||
|
||||
1. INSERT новий microDAO
|
||||
2. Додати агента в microdao_agents
|
||||
3. Оновити агента: primary_microdao_id, is_orchestrator = true
|
||||
4. Повернути створений microDAO
|
||||
"""
|
||||
pool = await get_pool()
|
||||
|
||||
import uuid
|
||||
microdao_id = str(uuid.uuid4())
|
||||
|
||||
async with pool.acquire() as conn:
|
||||
async with conn.transaction():
|
||||
# 1. Create microDAO
|
||||
insert_dao_query = """
|
||||
INSERT INTO microdaos (
|
||||
id, slug, name, description,
|
||||
orchestrator_agent_id, is_public, is_platform,
|
||||
parent_microdao_id, is_active, created_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true, NOW())
|
||||
RETURNING id, slug, name, description, is_public, is_platform
|
||||
"""
|
||||
dao_row = await conn.fetchrow(
|
||||
insert_dao_query,
|
||||
microdao_id, slug, name, description,
|
||||
orchestrator_agent_id, is_public, make_platform,
|
||||
parent_microdao_id
|
||||
)
|
||||
|
||||
if not dao_row:
|
||||
return None
|
||||
|
||||
# 2. Add agent to microdao_agents as orchestrator
|
||||
insert_member_query = """
|
||||
INSERT INTO microdao_agents (microdao_id, agent_id, role, is_core, joined_at)
|
||||
VALUES ($1, $2, 'orchestrator', true, NOW())
|
||||
ON CONFLICT (microdao_id, agent_id) DO UPDATE SET role = 'orchestrator', is_core = true
|
||||
"""
|
||||
await conn.execute(insert_member_query, microdao_id, orchestrator_agent_id)
|
||||
|
||||
# 3. Update agent: set primary_microdao_id if empty, set is_orchestrator = true
|
||||
update_agent_query = """
|
||||
UPDATE agents
|
||||
SET is_orchestrator = true,
|
||||
primary_microdao_id = COALESCE(primary_microdao_id, $2),
|
||||
updated_at = NOW()
|
||||
WHERE id = $1
|
||||
"""
|
||||
await conn.execute(update_agent_query, orchestrator_agent_id, microdao_id)
|
||||
|
||||
return dict(dao_row)
|
||||
|
||||
|
||||
@@ -147,35 +147,49 @@ async def list_agents(
|
||||
|
||||
|
||||
class AgentVisibilityPayload(BaseModel):
|
||||
visibility_scope: str # city, microdao, owner_only
|
||||
is_listed_in_directory: bool = True
|
||||
"""Agent visibility update payload (Task 029)"""
|
||||
is_public: bool
|
||||
visibility_scope: Optional[str] = None # 'global' | 'microdao' | 'private'
|
||||
|
||||
|
||||
@router.put("/agents/{agent_id}/visibility")
|
||||
async def update_agent_visibility(
|
||||
async def update_agent_visibility_endpoint(
|
||||
agent_id: str,
|
||||
payload: AgentVisibilityPayload
|
||||
):
|
||||
"""Оновити налаштування видимості агента"""
|
||||
"""Оновити налаштування видимості агента (Task 029)"""
|
||||
try:
|
||||
# Validate visibility_scope
|
||||
if payload.visibility_scope not in ("city", "microdao", "owner_only"):
|
||||
# Validate visibility_scope if provided
|
||||
valid_scopes = ("global", "microdao", "private", "city", "owner_only") # support legacy too
|
||||
if payload.visibility_scope and payload.visibility_scope not in valid_scopes:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="visibility_scope must be one of: city, microdao, owner_only"
|
||||
detail=f"visibility_scope must be one of: {', '.join(valid_scopes)}"
|
||||
)
|
||||
|
||||
# Normalize legacy values
|
||||
scope = payload.visibility_scope
|
||||
if scope == "city":
|
||||
scope = "global"
|
||||
elif scope == "owner_only":
|
||||
scope = "private"
|
||||
|
||||
# Update in database
|
||||
success = await repo_city.update_agent_visibility(
|
||||
result = await repo_city.update_agent_visibility(
|
||||
agent_id=agent_id,
|
||||
visibility_scope=payload.visibility_scope,
|
||||
is_listed_in_directory=payload.is_listed_in_directory
|
||||
is_public=payload.is_public,
|
||||
visibility_scope=scope,
|
||||
)
|
||||
|
||||
if not success:
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
return {"status": "ok", "agent_id": agent_id}
|
||||
return {
|
||||
"status": "ok",
|
||||
"agent_id": agent_id,
|
||||
"is_public": result.get("is_public"),
|
||||
"visibility_scope": result.get("visibility_scope"),
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
@@ -1333,9 +1347,10 @@ 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_public: Optional[bool] = Query(True, description="Filter by public status (default: True)"),
|
||||
is_platform: Optional[bool] = Query(None, description="Filter by platform status"),
|
||||
q: Optional[str] = Query(None, description="Search by name/description"),
|
||||
include_all: bool = Query(False, description="Include non-public (admin only)"),
|
||||
limit: int = Query(50, le=100),
|
||||
offset: int = Query(0, ge=0)
|
||||
):
|
||||
@@ -1343,14 +1358,18 @@ async def get_microdaos(
|
||||
Отримати список MicroDAOs.
|
||||
|
||||
- **district**: фільтр по дістрікту (Core, Energy, Green, Labs, etc.)
|
||||
- **is_public**: фільтр по публічності
|
||||
- **is_public**: фільтр по публічності (за замовчуванням True)
|
||||
- **is_platform**: фільтр по типу (платформа/дістрікт)
|
||||
- **q**: пошук по назві або опису
|
||||
- **include_all**: включити всі (для адмінів)
|
||||
"""
|
||||
try:
|
||||
# If include_all is True (admin mode), don't filter by is_public
|
||||
public_filter = None if include_all else is_public
|
||||
|
||||
daos = await repo_city.list_microdao_summaries(
|
||||
district=district,
|
||||
is_public=is_public,
|
||||
is_public=public_filter,
|
||||
is_platform=is_platform,
|
||||
q=q,
|
||||
limit=limit,
|
||||
@@ -1475,3 +1494,106 @@ async def get_microdao_by_slug(slug: str):
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail="Failed to get microdao")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MicroDAO Visibility & Creation (Task 029)
|
||||
# =============================================================================
|
||||
|
||||
class MicrodaoVisibilityPayload(BaseModel):
|
||||
"""MicroDAO visibility update payload"""
|
||||
is_public: bool
|
||||
is_platform: Optional[bool] = None
|
||||
|
||||
|
||||
@router.put("/microdao/{microdao_id}/visibility")
|
||||
async def update_microdao_visibility_endpoint(
|
||||
microdao_id: str,
|
||||
payload: MicrodaoVisibilityPayload
|
||||
):
|
||||
"""Оновити налаштування видимості MicroDAO (Task 029)"""
|
||||
try:
|
||||
result = await repo_city.update_microdao_visibility(
|
||||
microdao_id=microdao_id,
|
||||
is_public=payload.is_public,
|
||||
is_platform=payload.is_platform,
|
||||
)
|
||||
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="MicroDAO not found")
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"microdao_id": result.get("id"),
|
||||
"slug": result.get("slug"),
|
||||
"is_public": result.get("is_public"),
|
||||
"is_platform": result.get("is_platform"),
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update microdao visibility: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to update visibility")
|
||||
|
||||
|
||||
class MicrodaoCreatePayload(BaseModel):
|
||||
"""Create MicroDAO from agent (orchestrator flow)"""
|
||||
name: str
|
||||
slug: str
|
||||
description: Optional[str] = None
|
||||
make_platform: bool = False
|
||||
is_public: bool = True
|
||||
parent_microdao_id: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/agents/{agent_id}/microdao", response_model=dict)
|
||||
async def create_microdao_for_agent_endpoint(
|
||||
agent_id: str,
|
||||
payload: MicrodaoCreatePayload
|
||||
):
|
||||
"""
|
||||
Створити MicroDAO для агента (зробити його оркестратором).
|
||||
|
||||
Цей endpoint:
|
||||
1. Створює новий MicroDAO
|
||||
2. Призначає агента оркестратором
|
||||
3. Додає агента як члена DAO
|
||||
4. Встановлює primary_microdao_id якщо порожній
|
||||
"""
|
||||
try:
|
||||
# Check if agent exists and is not archived
|
||||
agent = await repo_city.get_agent_by_id(agent_id)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail="Agent not found")
|
||||
|
||||
# Check if slug is unique
|
||||
existing = await repo_city.get_microdao_by_slug(payload.slug)
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail=f"MicroDAO with slug '{payload.slug}' already exists")
|
||||
|
||||
# Create MicroDAO
|
||||
result = await repo_city.create_microdao_for_agent(
|
||||
orchestrator_agent_id=agent_id,
|
||||
name=payload.name,
|
||||
slug=payload.slug,
|
||||
description=payload.description,
|
||||
make_platform=payload.make_platform,
|
||||
is_public=payload.is_public,
|
||||
parent_microdao_id=payload.parent_microdao_id,
|
||||
)
|
||||
|
||||
if not result:
|
||||
raise HTTPException(status_code=500, detail="Failed to create MicroDAO")
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"microdao": result,
|
||||
"agent_id": agent_id,
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create microdao for agent {agent_id}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise HTTPException(status_code=500, detail="Failed to create MicroDAO")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user