Files
2026-07-13 13:32:32 +08:00

231 lines
5.9 KiB
Python

"""Tasks API — 任务的 CRUD 和状态流转。"""
from __future__ import annotations
import uuid
import logging
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from ..db import get_db
from ..models.task import TaskState
from ..services.event_bus import EventBus, get_event_bus
from ..services.task_service import TaskService
log = logging.getLogger("edict.api.tasks")
router = APIRouter()
# ── Schemas ──
class TaskCreate(BaseModel):
title: str
description: str = ""
priority: str = "中"
assignee_org: str | None = None
creator: str = "emperor"
tags: list[str] = []
meta: dict | None = None
class TaskTransition(BaseModel):
new_state: str
agent: str = "system"
reason: str = ""
class TaskProgress(BaseModel):
agent: str
content: str
class TaskTodoUpdate(BaseModel):
todos: list[dict]
class TaskSchedulerUpdate(BaseModel):
scheduler: dict
class TaskOut(BaseModel):
task_id: str
trace_id: str
title: str
description: str
priority: str
state: str
assignee_org: str | None
creator: str
tags: list[str]
flow_log: list
progress_log: list
todos: list
scheduler: dict | None
created_at: str
updated_at: str
class Config:
from_attributes = True
# ── 依赖注入 helper ──
async def get_task_service(
db: AsyncSession = Depends(get_db),
) -> TaskService:
bus = await get_event_bus()
return TaskService(db, bus)
# ── Endpoints ──
@router.get("")
async def list_tasks(
state: str | None = None,
assignee_org: str | None = None,
priority: str | None = None,
limit: int = Query(default=50, le=200),
offset: int = Query(default=0, ge=0),
svc: TaskService = Depends(get_task_service),
):
"""获取任务列表。"""
task_state = TaskState(state) if state else None
tasks = await svc.list_tasks(
state=task_state,
assignee_org=assignee_org,
priority=priority,
limit=limit,
offset=offset,
)
return {"tasks": [t.to_dict() for t in tasks], "count": len(tasks)}
@router.get("/live-status")
async def live_status(svc: TaskService = Depends(get_task_service)):
"""兼容旧 live_status.json 格式的全局状态。"""
return await svc.get_live_status()
@router.get("/stats")
async def task_stats(svc: TaskService = Depends(get_task_service)):
"""任务统计。"""
stats = {}
for s in TaskState:
stats[s.value] = await svc.count_tasks(s)
total = sum(stats.values())
return {"total": total, "by_state": stats}
@router.post("", status_code=201)
async def create_task(
body: TaskCreate,
svc: TaskService = Depends(get_task_service),
):
"""创建新任务。"""
task = await svc.create_task(
title=body.title,
description=body.description,
priority=body.priority,
assignee_org=body.assignee_org,
creator=body.creator,
tags=body.tags,
meta=body.meta,
)
return {"task_id": str(task.task_id), "trace_id": str(task.trace_id), "state": task.state.value}
@router.get("/{task_id}")
async def get_task(
task_id: uuid.UUID,
svc: TaskService = Depends(get_task_service),
):
"""获取任务详情。"""
try:
task = await svc.get_task(task_id)
return task.to_dict()
except ValueError:
raise HTTPException(status_code=404, detail="Task not found")
@router.post("/{task_id}/transition")
async def transition_task(
task_id: uuid.UUID,
body: TaskTransition,
svc: TaskService = Depends(get_task_service),
):
"""执行状态流转。"""
try:
new_state = TaskState(body.new_state)
except ValueError:
raise HTTPException(status_code=400, detail=f"Invalid state: {body.new_state}")
try:
task = await svc.transition_state(
task_id=task_id,
new_state=new_state,
agent=body.agent,
reason=body.reason,
)
return {"task_id": str(task.task_id), "state": task.state.value, "message": "ok"}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/{task_id}/dispatch")
async def dispatch_task(
task_id: uuid.UUID,
agent: str = Query(description="目标 agent"),
message: str = Query(default="", description="派发消息"),
svc: TaskService = Depends(get_task_service),
):
"""手动派发任务给指定 agent。"""
try:
await svc.request_dispatch(task_id, agent, message)
return {"message": "dispatch requested", "agent": agent}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.post("/{task_id}/progress")
async def add_progress(
task_id: uuid.UUID,
body: TaskProgress,
svc: TaskService = Depends(get_task_service),
):
"""添加进度记录。"""
try:
await svc.add_progress(task_id, body.agent, body.content)
return {"message": "ok"}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.put("/{task_id}/todos")
async def update_todos(
task_id: uuid.UUID,
body: TaskTodoUpdate,
svc: TaskService = Depends(get_task_service),
):
"""更新任务 TODO 清单。"""
try:
await svc.update_todos(task_id, body.todos)
return {"message": "ok"}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
@router.put("/{task_id}/scheduler")
async def update_scheduler(
task_id: uuid.UUID,
body: TaskSchedulerUpdate,
svc: TaskService = Depends(get_task_service),
):
"""更新任务排期信息。"""
try:
await svc.update_scheduler(task_id, body.scheduler)
return {"message": "ok"}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))