Files
aios/active-runtime/app/task_center.py
T

116 lines
3.8 KiB
Python

"""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",
]