"""Workflow workstream integration — posts workflow events to the central Communication system (G-WORK). Replaces the old notification-based workflow messages with typed CommMessage blocks: status, handoff, approval, action, and error. Used by the WorkflowEngine to post step transitions, approvals, errors, and completions to the workstream. """ from __future__ import annotations import logging import uuid from typing import Any from sqlalchemy.ext.asyncio import AsyncSession logger = logging.getLogger(__name__) async def post_workflow_status( db: AsyncSession, tenant_id: uuid.UUID, instance_id: uuid.UUID, workflow_name: str, status: str, step_index: int | None = None, step_name: str | None = None, user_id: uuid.UUID | None = None, ) -> dict[str, Any] | None: """Post a workflow status update to the workstream. Creates a CommMessage with a typed ``workflow_status`` block. """ try: from app.plugins.builtins.kommunikation.contracts import KommunikationContract contract = KommunikationContract post_fn = contract.get_function("post_message") if post_fn is None: # Fallback to system notification from app.core.notifications import post_system_message if user_id: await post_system_message( db, tenant_id, user_id, "workflow_status", f"Workflow: {workflow_name}", f"Status: {status}" + (f" (Step: {step_name})" if step_name else ""), ) return None block = { "type": "workflow_status", "workflow_name": workflow_name, "instance_id": str(instance_id), "status": status, "step_index": step_index, "step_name": step_name, } return await post_fn( db=db, tenant_id=tenant_id, sender_id=None, # System sender sender_type="system", message_type="workflow_status", content=f"Workflow '{workflow_name}' → {status}", blocks=[block], ) except Exception as e: logger.warning("Failed to post workflow status to workstream: %s", e) return None async def post_workflow_handoff( db: AsyncSession, tenant_id: uuid.UUID, instance_id: uuid.UUID, workflow_name: str, handoff_type: str, # review_needed, action_required, waiting_for_user assignee_id: uuid.UUID | None = None, assignee_type: str = "user", description: str = "", user_id: uuid.UUID | None = None, ) -> dict[str, Any] | None: """Post a workflow handoff to the workstream. Creates a CommMessage with a typed ``workflow_handoff`` block. The handoff indicates that the workflow is waiting for human input. """ try: from app.plugins.builtins.kommunikation.contracts import KommunikationContract contract = KommunikationContract post_fn = contract.get_function("post_message") if post_fn is None: from app.core.notifications import post_system_message if assignee_id: await post_system_message( db, tenant_id, assignee_id, "workflow_handoff", f"Workflow Handoff: {workflow_name}", f"{handoff_type}: {description}", ) return None block = { "type": "workflow_handoff", "workflow_name": workflow_name, "instance_id": str(instance_id), "handoff_type": handoff_type, "assignee_id": str(assignee_id) if assignee_id else None, "assignee_type": assignee_type, "description": description, } return await post_fn( db=db, tenant_id=tenant_id, sender_id=None, sender_type="system", message_type="workflow_handoff", content=f"Workflow '{workflow_name}' → {handoff_type}", blocks=[block], ) except Exception as e: logger.warning("Failed to post workflow handoff to workstream: %s", e) return None async def post_workflow_error( db: AsyncSession, tenant_id: uuid.UUID, instance_id: uuid.UUID, workflow_name: str, error: str, step_index: int | None = None, step_name: str | None = None, user_id: uuid.UUID | None = None, ) -> dict[str, Any] | None: """Post a workflow error to the workstream.""" try: from app.plugins.builtins.kommunikation.contracts import KommunikationContract contract = KommunikationContract post_fn = contract.get_function("post_message") if post_fn is None: from app.core.notifications import post_system_message if user_id: await post_system_message( db, tenant_id, user_id, "workflow_error", f"Workflow Error: {workflow_name}", f"Error at step {step_name or step_index}: {error}", ) return None block = { "type": "workflow_error", "workflow_name": workflow_name, "instance_id": str(instance_id), "error": error, "step_index": step_index, "step_name": step_name, } return await post_fn( db=db, tenant_id=tenant_id, sender_id=None, sender_type="system", message_type="workflow_error", content=f"Workflow '{workflow_name}' → Error: {error}", blocks=[block], ) except Exception as e: logger.warning("Failed to post workflow error to workstream: %s", e) return None async def post_workflow_completed( db: AsyncSession, tenant_id: uuid.UUID, instance_id: uuid.UUID, workflow_name: str, result: dict[str, Any] | None = None, user_id: uuid.UUID | None = None, ) -> dict[str, Any] | None: """Post a workflow completion to the workstream.""" try: from app.plugins.builtins.kommunikation.contracts import KommunikationContract contract = KommunikationContract post_fn = contract.get_function("post_message") if post_fn is None: from app.core.notifications import post_system_message if user_id: await post_system_message( db, tenant_id, user_id, "workflow_completed", f"Workflow Completed: {workflow_name}", f"Workflow '{workflow_name}' has been completed successfully.", ) return None block = { "type": "workflow_completed", "workflow_name": workflow_name, "instance_id": str(instance_id), "result": result or {}, } return await post_fn( db=db, tenant_id=tenant_id, sender_id=None, sender_type="system", message_type="workflow_completed", content=f"Workflow '{workflow_name}' → Completed", blocks=[block], ) except Exception as e: logger.warning("Failed to post workflow completion to workstream: %s", e) return None __all__ = [ "post_workflow_status", "post_workflow_handoff", "post_workflow_error", "post_workflow_completed", ]