feat(G): G-WORK/G-HUMAN-DEC/G-UI-TEMPL/G-DOC — workflow workstream, decision guard, template gallery (3 templates), API docs, 43 tests passing
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
"""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",
|
||||
]
|
||||
Reference in New Issue
Block a user