Files
leocrm/app/workflows/workstream.py
T

222 lines
7.3 KiB
Python
Raw Normal View History

"""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",
]