"""Task Center action API model for Web UI buttons.""" from typing import Any try: from fastapi import APIRouter except Exception: APIRouter = None from .task_lifecycle import TaskLifecycle lifecycle = TaskLifecycle() # Role must come from an authenticated principal, never from the client body. _ROLE_WHITELIST = {"user", "agent", "executor", "dispatcher", "notifier", "group_admin", "owner"} _authenticator = None # callable(payload) -> {"role": ...} | None, injected by host def set_authenticator(fn): """Install a server-side authenticator used to derive the actor role. `fn` receives the raw request payload and must return either None (or a dict without a whitelisted "role") for anonymous, or a dict containing a server-verified "role". The client-supplied `payload["role"]` is ignored. """ global _authenticator _authenticator = fn def effective_role(payload: dict[str, Any]) -> str: if _authenticator is not None: principal = _authenticator(payload) if isinstance(principal, dict) and principal.get("role") in _ROLE_WHITELIST: return principal["role"] return "user" def get_task_actions(task: dict[str, Any], role: str) -> dict[str, Any]: return { "task_id": task.get("task_id"), "actions": lifecycle.available_actions(task, role), } def execute_task_action(task: dict[str, Any], action: str, role: str, reason: str | None = None): return lifecycle.transition(task, action, role, reason) def build_router(): if APIRouter is None: return None router = APIRouter(prefix="/admin/ai-evolution/tasks", tags=["task-actions"]) @router.post("/{task_id}/actions/{action}", operation_id="executeTaskAction") def action(task_id: str, action: str, payload: dict[str, Any]): # Role is derived from the authenticated principal; body role is ignored. return execute_task_action(payload.get("task", {}), action, effective_role(payload), payload.get("reason")) return router router = build_router() __all__ = ["router", "get_task_actions", "execute_task_action"]