From 864824d8cd88f2a7fba170cac5b127af928400bf Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Thu, 20 Aug 2026 21:09:41 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20F-PREBUILT=20+=20F-COMM=20+=20F-WORK=20?= =?UTF-8?q?+=20G-WORK=20=E2=80=94=20prebuilt=20agents=20registered,=20agen?= =?UTF-8?q?t=20results=20posted=20to=20communication,=20workflow=20results?= =?UTF-8?q?=20posted=20to=20communication?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builtins/automation/agent_runner.py | 65 +++++++++++++++++++ app/plugins/builtins/automation/plugin.py | 42 ++++++++++++ app/workflows/engine.py | 57 ++++++++++++++++ 3 files changed, 164 insertions(+) diff --git a/app/plugins/builtins/automation/agent_runner.py b/app/plugins/builtins/automation/agent_runner.py index ed3070f..cde4c4a 100644 --- a/app/plugins/builtins/automation/agent_runner.py +++ b/app/plugins/builtins/automation/agent_runner.py @@ -369,6 +369,71 @@ async def run_agent( status=result_data.get("status"), result=result_data, ) + + # ── Post agent result to Communication (F-COMM) ── + try: + from app.plugins.builtins.contracts import get_contract_registry + komm = get_contract_registry().get("kommunikation") + if komm: + async with factory() as db: + # Find or create agent conversation room + from app.plugins.builtins.kommunikation.models import CommConversation + from sqlalchemy import select as sa_select + room_title = f"Agent: {agent.name}" + existing = await db.execute( + sa_select(CommConversation).where( + CommConversation.tenant_id == agent.tenant_id, + CommConversation.title == room_title, + CommConversation.is_locked.is_(True), + CommConversation.locked_by == "automation", + CommConversation.deleted_at.is_(None), + ) + ) + conv = existing.scalar_one_or_none() + if not conv: + room = await komm.create_plugin_room( + db=db, + tenant_id=agent.tenant_id, + user_id=agent.created_by, + plugin_name="automation", + title=room_title, + participant_type="agent", + ) + conv_id = uuid.UUID(room["conversation_id"]) + else: + conv_id = conv.id + + # Post result as message with action_card block + status = result_data.get("status", "unknown") + result_text = result_data.get("llm_response", result_data.get("error", "No result")) + await komm.send_message( + db=db, + tenant_id=agent.tenant_id, + conversation_id=conv_id, + sender_id=agent.id, + sender_type="agent", + content=f"Agent '{agent.name}' completed with status: {status}", + content_format="text", + blocks=[ + { + "block_type": "action_card", + "block_data": { + "title": f"Agent Result: {agent.name}", + "description": result_text[:500] if result_text else "No result", + "actions": [ + {"label": "View Details", "action": "view_agent_run", "data": {"run_id": str(run_id)}}, + ], + }, + "sort_order": 0, + } + ], + metadata={"agent_id": str(agent.id), "run_id": str(run_id), "status": status}, + ) + await db.commit() + logger.info("Posted agent result to conversation %s", conv_id) + except Exception as e: + logger.warning("Failed to post agent result to communication: %s", e) + async with factory() as db: await enqueue_outbox_event( db, diff --git a/app/plugins/builtins/automation/plugin.py b/app/plugins/builtins/automation/plugin.py index e390423..911fff4 100644 --- a/app/plugins/builtins/automation/plugin.py +++ b/app/plugins/builtins/automation/plugin.py @@ -232,6 +232,48 @@ class AutomationPlugin(BasePlugin): logger.info("Registered own cron jobs from manifest") except Exception: logger.exception("Failed to register own cron jobs") + # Register pre-built agents in DB (if not already present) + try: + from app.plugins.builtins.automation.models import AgentDefinition + from app.plugins.builtins.automation.prebuilt.email_triage_agent import create_email_triage_agent + from app.plugins.builtins.automation.prebuilt.contact_enrichment_agent import create_contact_enrichment_agent + from app.plugins.builtins.automation.prebuilt.follow_up_agent import create_follow_up_agent + from app.plugins.builtins.automation.prebuilt.report_agent import create_report_agent + from sqlalchemy import select as sa_select + + # Get first tenant + admin user for seeding + from app.models.user import User, Tenant + tenant_result = await db.execute(sa_select(Tenant).limit(1)) + tenant = tenant_result.scalar_one_or_none() + if tenant: + user_result = await db.execute( + sa_select(User).where(User.tenant_id == tenant.id).limit(1) + ) + user = user_result.scalar_one_or_none() + if user: + prebuilt_factories = [ + ("E-Mail-Triage-Agent", create_email_triage_agent), + ("Kontakt-Anreicherungs-Agent", create_contact_enrichment_agent), + ("Follow-Up-Agent", create_follow_up_agent), + ("Berichts-Agent", create_report_agent), + ] + for agent_name, factory in prebuilt_factories: + # Check if agent already exists + existing = await db.execute( + sa_select(AgentDefinition).where( + AgentDefinition.tenant_id == tenant.id, + AgentDefinition.name == agent_name, + ) + ) + if not existing.scalar_one_or_none(): + agent = factory(tenant_id=tenant.id, user_id=user.id) + db.add(agent) + logger.info("Registered pre-built agent '%s'", agent_name) + await db.commit() + logger.info("Pre-built agents registration complete") + except Exception: + logger.exception("Failed to register pre-built agents") + logger.info("Automation plugin activated") async def on_deactivate(self, db, service_container, event_bus) -> None: diff --git a/app/workflows/engine.py b/app/workflows/engine.py index 128778e..29b5175 100644 --- a/app/workflows/engine.py +++ b/app/workflows/engine.py @@ -89,6 +89,63 @@ class WorkflowEngine: 'initiated_by': str(instance.initiated_by) if instance.initiated_by else None, }) + # Post workflow completion to Communication (G-WORK) + try: + from app.plugins.builtins.contracts import get_contract_registry + from app.plugins.builtins.kommunikation.models import CommConversation + from sqlalchemy import select as sa_select + komm = get_contract_registry().get("kommunikation") + if komm and instance.initiated_by: + room_title = f"Workflow: {workflow.name if hasattr(workflow, 'name') else str(instance.workflow_id)}" + existing = await self.db.execute( + sa_select(CommConversation).where( + CommConversation.tenant_id == self.tenant_id, + CommConversation.title == room_title, + CommConversation.is_locked.is_(True), + CommConversation.locked_by == "workflow", + CommConversation.deleted_at.is_(None), + ) + ) + conv = existing.scalar_one_or_none() + if not conv: + room = await komm.create_plugin_room( + db=self.db, + tenant_id=self.tenant_id, + user_id=instance.initiated_by, + plugin_name="workflow", + title=room_title, + participant_type="workflow", + ) + conv_id = uuid.UUID(room["conversation_id"]) + else: + conv_id = conv.id + await komm.send_message( + db=self.db, + tenant_id=self.tenant_id, + conversation_id=conv_id, + sender_id=instance.workflow_id, + sender_type="system", + content=f"Workflow completed: {instance.status}", + content_format="text", + blocks=[ + { + "block_type": "action_card", + "block_data": { + "title": f"Workflow Result: {instance.status}", + "description": f"Workflow instance {str(instance.id)[:8]} completed successfully", + "actions": [ + {"label": "View Details", "action": "view_workflow_instance", "data": {"instance_id": str(instance.id)}}, + ], + }, + "sort_order": 0, + } + ], + metadata={"workflow_id": str(instance.workflow_id), "instance_id": str(instance.id), "status": instance.status}, + ) + await self.db.flush() + except Exception: + logger.warning("Failed to post workflow result to communication", exc_info=True) + return _instance_to_dict(instance) step = steps[instance.current_step_index]