feat(agents): Add task assignment and comments functionality
This commit is contained in:
@@ -3,7 +3,8 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlmodel import Session, select
|
||||
from sqlalchemy import asc
|
||||
from sqlmodel import Session, col, select
|
||||
|
||||
from app.api.deps import (
|
||||
ActorContext,
|
||||
@@ -14,9 +15,17 @@ from app.api.deps import (
|
||||
)
|
||||
from app.core.auth import AuthContext
|
||||
from app.db.session import get_session
|
||||
from app.models.agents import Agent
|
||||
from app.models.activity_events import ActivityEvent
|
||||
from app.models.boards import Board
|
||||
from app.models.tasks import Task
|
||||
from app.schemas.tasks import TaskCreate, TaskRead, TaskUpdate
|
||||
from app.schemas.tasks import (
|
||||
TaskCommentCreate,
|
||||
TaskCommentRead,
|
||||
TaskCreate,
|
||||
TaskRead,
|
||||
TaskUpdate,
|
||||
)
|
||||
from app.services.activity_log import record_activity
|
||||
|
||||
router = APIRouter(prefix="/boards/{board_id}/tasks", tags=["tasks"])
|
||||
@@ -66,9 +75,25 @@ def update_task(
|
||||
previous_status = task.status
|
||||
updates = payload.model_dump(exclude_unset=True)
|
||||
if actor.actor_type == "agent":
|
||||
if actor.agent and actor.agent.board_id and task.board_id:
|
||||
if actor.agent.board_id != task.board_id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
allowed_fields = {"status"}
|
||||
if not set(updates).issubset(allowed_fields):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
if "status" in updates:
|
||||
if updates["status"] == "inbox":
|
||||
task.assigned_agent_id = None
|
||||
else:
|
||||
task.assigned_agent_id = actor.agent.id if actor.agent else None
|
||||
elif "status" in updates and updates["status"] == "inbox":
|
||||
task.assigned_agent_id = None
|
||||
if "assigned_agent_id" in updates and updates["assigned_agent_id"]:
|
||||
agent = session.get(Agent, updates["assigned_agent_id"])
|
||||
if agent is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
if agent.board_id and task.board_id and agent.board_id != task.board_id:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
for key, value in updates.items():
|
||||
setattr(task, key, value)
|
||||
task.updated_at = datetime.utcnow()
|
||||
@@ -103,3 +128,45 @@ def delete_task(
|
||||
session.delete(task)
|
||||
session.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/{task_id}/comments", response_model=list[TaskCommentRead])
|
||||
def list_task_comments(
|
||||
task: Task = Depends(get_task_or_404),
|
||||
session: Session = Depends(get_session),
|
||||
actor: ActorContext = Depends(require_admin_or_agent),
|
||||
) -> list[ActivityEvent]:
|
||||
if actor.actor_type == "agent" and actor.agent:
|
||||
if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
statement = (
|
||||
select(ActivityEvent)
|
||||
.where(col(ActivityEvent.task_id) == task.id)
|
||||
.where(col(ActivityEvent.event_type) == "task.comment")
|
||||
.order_by(asc(col(ActivityEvent.created_at)))
|
||||
)
|
||||
return list(session.exec(statement))
|
||||
|
||||
|
||||
@router.post("/{task_id}/comments", response_model=TaskCommentRead)
|
||||
def create_task_comment(
|
||||
payload: TaskCommentCreate,
|
||||
task: Task = Depends(get_task_or_404),
|
||||
session: Session = Depends(get_session),
|
||||
actor: ActorContext = Depends(require_admin_or_agent),
|
||||
) -> ActivityEvent:
|
||||
if actor.actor_type == "agent" and actor.agent:
|
||||
if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
if not payload.message.strip():
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
event = ActivityEvent(
|
||||
event_type="task.comment",
|
||||
message=payload.message.strip(),
|
||||
task_id=task.id,
|
||||
agent_id=actor.agent.id if actor.actor_type == "agent" and actor.agent else None,
|
||||
)
|
||||
session.add(event)
|
||||
session.commit()
|
||||
session.refresh(event)
|
||||
return event
|
||||
|
||||
@@ -21,6 +21,7 @@ class Task(TenantScoped, table=True):
|
||||
due_at: datetime | None = None
|
||||
|
||||
created_by_user_id: UUID | None = Field(default=None, foreign_key="users.id", index=True)
|
||||
assigned_agent_id: UUID | None = Field(default=None, foreign_key="agents.id", index=True)
|
||||
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
@@ -12,6 +12,7 @@ class TaskBase(SQLModel):
|
||||
status: str = "inbox"
|
||||
priority: str = "medium"
|
||||
due_at: datetime | None = None
|
||||
assigned_agent_id: UUID | None = None
|
||||
|
||||
|
||||
class TaskCreate(TaskBase):
|
||||
@@ -24,6 +25,7 @@ class TaskUpdate(SQLModel):
|
||||
status: str | None = None
|
||||
priority: str | None = None
|
||||
due_at: datetime | None = None
|
||||
assigned_agent_id: UUID | None = None
|
||||
|
||||
|
||||
class TaskRead(TaskBase):
|
||||
@@ -32,3 +34,15 @@ class TaskRead(TaskBase):
|
||||
created_by_user_id: UUID | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class TaskCommentCreate(SQLModel):
|
||||
message: str
|
||||
|
||||
|
||||
class TaskCommentRead(SQLModel):
|
||||
id: UUID
|
||||
message: str | None
|
||||
agent_id: UUID | None
|
||||
task_id: UUID | None
|
||||
created_at: datetime
|
||||
|
||||
Reference in New Issue
Block a user