feat(agents): Add task assignment and comments functionality

This commit is contained in:
Abhimanyu Saharan
2026-02-04 18:13:17 +05:30
parent 8078580996
commit 7892dfad7c
9 changed files with 1882 additions and 6 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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