"""AIOS Task Center domain model. The Task Center gives owners and authorized users a single Web UI page showing: who assigned a task, who is executing it, progress, elapsed time, remaining work, and whether completion notifications were sent. """ from __future__ import annotations from dataclasses import asdict, dataclass, field from datetime import datetime, timezone from typing import Any def utc_now() -> str: return datetime.now(timezone.utc).isoformat() def parse_time(value: str | None) -> datetime | None: if not value: return None return datetime.fromisoformat(value.replace("Z", "+00:00")) @dataclass(frozen=True) class TaskNotificationStatus: required: bool = True sent: bool = False channel: str | None = None sent_at: str | None = None recipient_scope: str | None = None last_error: str | None = None def to_dict(self) -> dict[str, Any]: return asdict(self) @dataclass(frozen=True) class TaskProgress: percent: int = 0 current_step: str = "pending" completed_steps: list[str] = field(default_factory=list) remaining_steps: list[str] = field(default_factory=list) blocked_reason: str | None = None def to_dict(self) -> dict[str, Any]: return asdict(self) @dataclass(frozen=True) class AIOSTaskRecord: task_id: str title: str assigned_by: str executor: str source: str status: str = "pending" priority: str = "normal" created_at: str = field(default_factory=utc_now) started_at: str | None = None updated_at: str = field(default_factory=utc_now) completed_at: str | None = None progress: TaskProgress = field(default_factory=TaskProgress) notification: TaskNotificationStatus = field(default_factory=TaskNotificationStatus) owner_gate_required: bool = False owner_gate_status: str | None = None evidence: list[str] = field(default_factory=list) def elapsed_seconds(self) -> int: start = parse_time(self.started_at) or parse_time(self.created_at) end = parse_time(self.completed_at) or datetime.now(timezone.utc) return int((end - start).total_seconds()) if start else 0 def to_dict(self) -> dict[str, Any]: data = asdict(self) data["elapsed_seconds"] = self.elapsed_seconds() data["progress"] = self.progress.to_dict() data["notification"] = self.notification.to_dict() return data class TaskCenter: def summarize(self, tasks: list[AIOSTaskRecord] | list[dict[str, Any]] | None = None) -> dict[str, Any]: raw_tasks = tasks or [] task_dicts = [t.to_dict() if hasattr(t, "to_dict") else dict(t) for t in raw_tasks] active = [t for t in task_dicts if t.get("status") in {"running", "pending", "blocked"}] blocked = [t for t in task_dicts if t.get("status") == "blocked"] completed = [t for t in task_dicts if t.get("status") == "completed"] notification_pending = [ t for t in completed if t.get("notification", {}).get("required") and not t.get("notification", {}).get("sent") ] return { "module": "task-center", "route": "/admin/ai-evolution/tasks", "title": "AIOS 任务中心", "status": "attention_required" if blocked or notification_pending else "healthy", "generated_at": utc_now(), "summary": { "total": len(task_dicts), "active": len(active), "blocked": len(blocked), "completed": len(completed), "notification_pending": len(notification_pending), }, "tasks": sorted(task_dicts, key=lambda t: t.get("updated_at", ""), reverse=True), } __all__ = [ "TaskNotificationStatus", "TaskProgress", "AIOSTaskRecord", "TaskCenter", ]