Files
leocrm/app/plugins/builtins/tasks/workstream.py
T
Agent Zero a53dcc38d5
Check Cross-Plugin Imports / check (push) Has been cancelled
feat(F.14): Unified Task System — F-TASK-MODEL/API/AGENT/WORK/UI/MIG/GOAL/TEST
- F-TASK-MODEL: Extended Task model with polymorphic assignee/entity/creator, subtasks, dependencies, task_type, success_criteria, progress
- F-TASK-API: Extended task routes with polymorphic filters, subtasks, dependencies, new lifecycle
- F-TASK-AGENT: ai_tools.py (191 lines) — create_task, assign_task, update_task_status, decompose_goal tools
- F-TASK-WORK: workstream.py — task_card and goal_card blocks in communication system
- F-TASK-UI: TaskBoard.tsx, TaskDetail.tsx, GoalView.tsx frontend components
- F-TASK-MIG: Migration 0124 — new columns, data migration for contact_id/assigned_to
- F-TASK-GOAL: Progress aggregation, success criteria evaluation, parent status propagation
- F-TASK-TEST: test_unified_tasks.py (414 lines)
- i18n updates for task system
2026-08-17 18:51:22 +02:00

107 lines
3.6 KiB
Python

"""Task → Workstream integration (F-TASK-WORK).
Posts task_card and goal_card blocks to the communication system so tasks
and goals appear in the workstream with live status and progress.
"""
from __future__ import annotations
import logging
import uuid
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from app.plugins.builtins.tasks.models import Task
logger = logging.getLogger(__name__)
def _task_card_data(task: Task) -> dict[str, Any]:
"""Build a task_card block payload for a task."""
return {
"task_id": str(task.id),
"title": task.title,
"status": task.status,
"priority": task.priority,
"task_type": task.task_type,
"assignee_type": task.assignee_type,
"assignee_id": str(task.assignee_id) if task.assignee_id else None,
"entity_type": task.entity_type,
"entity_id": str(task.entity_id) if task.entity_id else None,
"parent_task_id": str(task.parent_task_id) if task.parent_task_id else None,
"due_date": task.due_date.isoformat() if task.due_date else None,
"progress": task.progress or 0,
}
def _goal_card_data(task: Task) -> dict[str, Any]:
"""Build a goal_card block payload for a goal/milestone."""
return {
"goal_id": str(task.id),
"title": task.title,
"status": task.status,
"task_type": task.task_type,
"progress": task.progress or 0,
"target_date": task.target_date.isoformat() if task.target_date else None,
"success_criteria": task.success_criteria,
"parent_task_id": str(task.parent_task_id) if task.parent_task_id else None,
}
async def post_task_to_workstream(
db: AsyncSession,
tenant_id: uuid.UUID,
task: Task,
*,
actor_id: uuid.UUID | None = None,
actor_type: str = "user",
conversation_id: uuid.UUID | None = None,
content: str | None = None,
) -> uuid.UUID | None:
"""Post a task_card block to the workstream.
Returns the created message ID, or None if the communication system is
unavailable.
"""
try:
from app.plugins.builtins.kommunikation.services import send_message
block_type = "goal_card" if task.task_type in ("goal", "milestone") else "task_card"
block_data = _goal_card_data(task) if block_type == "goal_card" else _task_card_data(task)
message_id = await send_message(
db=db,
tenant_id=tenant_id,
sender_id=actor_id or task.created_by or task.id,
sender_type=actor_type,
conversation_id=conversation_id,
content=content or f"{task.title} ({task.status})",
blocks=[{"type": block_type, "data": block_data}],
metadata={"source": "tasks", "task_id": str(task.id)},
)
return message_id
except Exception:
logger.exception("Failed to post task %s to workstream", task.id)
return None
async def post_task_status_update(
db: AsyncSession,
tenant_id: uuid.UUID,
task: Task,
*,
old_status: str | None = None,
actor_id: uuid.UUID | None = None,
actor_type: str = "user",
conversation_id: uuid.UUID | None = None,
) -> uuid.UUID | None:
"""Post a status-change update to the workstream."""
content = f"Status geändert: {task.title}{task.status}"
if old_status and old_status != task.status:
content = f"Status geändert: {task.title} ({old_status}{task.status})"
return await post_task_to_workstream(
db, tenant_id, task,
actor_id=actor_id, actor_type=actor_type,
conversation_id=conversation_id, content=content,
)