diff --git a/app/plugins/builtins/automation/plugin.py b/app/plugins/builtins/automation/plugin.py index ee7a56f..10114b8 100644 --- a/app/plugins/builtins/automation/plugin.py +++ b/app/plugins/builtins/automation/plugin.py @@ -210,12 +210,11 @@ class AutomationPlugin(BasePlugin): register_agent_coordinator_tools() except Exception: logger.exception("Failed to register agent coordinator tools") - # Register integration tools (I-AW: Agent→Workflow, I-AK: Agent→Knowledge) + # Register workflow agent tools (I-AW: Agent→Workflow) try: - from app.ai.integration_tools import register_integration_tools - register_integration_tools() + self._register_workflow_agent_tools() except Exception: - logger.exception("Failed to register integration tools") + logger.exception("Failed to register workflow agent tools") # Register MiniApps from manifest try: from app.plugins.builtins.kommunikation.contracts import get_miniapp_registry @@ -286,6 +285,94 @@ class AutomationPlugin(BasePlugin): logger.info("Automation plugin activated") + def _register_workflow_agent_tools(self) -> None: + """Register I-AW agent tools for starting and inspecting workflows.""" + import uuid + from typing import Any + + from app.ai.tool_registry import get_tool_registry + registry = get_tool_registry() + + async def _start_workflow_handler(arguments: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: + """Start a workflow by ID.""" + from app.services.workflow_service import create_instance + from app.core.db import get_worker_session_factory + workflow_id = arguments.get("workflow_id", "") + tenant_id = context.get("tenant_id") + user_id = context.get("user_id") + if not workflow_id or not tenant_id: + return {"error": "workflow_id and tenant_id required"} + factory = get_worker_session_factory() + async with factory() as db: + instance = await create_instance( + db=db, + tenant_id=uuid.UUID(str(tenant_id)), + workflow_id=uuid.UUID(workflow_id), + initiated_by=uuid.UUID(str(user_id)) if user_id else None, + ) + await db.commit() + return {"instance_id": str(instance.get("id", "")), "status": instance.get("status", "created")} + + registry.register( + name="start_workflow", + description="Start a workflow by its ID. Returns the instance ID and status.", + parameters={ + "type": "object", + "properties": { + "workflow_id": {"type": "string", "description": "UUID of the workflow to start"}, + }, + "required": ["workflow_id"], + }, + handler=_start_workflow_handler, + plugin_name=self.manifest.name, + required_permission="workflows:read", + category="workflow", + ) + + async def _check_workflow_status_handler(arguments: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: + """Check the status of a workflow instance.""" + from sqlalchemy import select + from app.models.workflow import WorkflowInstance + from app.core.db import get_worker_session_factory + instance_id = arguments.get("instance_id", "") + tenant_id = context.get("tenant_id") + if not instance_id or not tenant_id: + return {"error": "instance_id and tenant_id required"} + factory = get_worker_session_factory() + async with factory() as db: + result = await db.execute( + select(WorkflowInstance).where( + WorkflowInstance.id == uuid.UUID(instance_id), + WorkflowInstance.tenant_id == uuid.UUID(str(tenant_id)), + ) + ) + inst = result.scalar_one_or_none() + if not inst: + return {"error": "Instance not found"} + return { + "instance_id": str(inst.id), + "status": inst.status, + "current_step": inst.current_step_index, + "completed_at": inst.completed_at.isoformat() if inst.completed_at else None, + } + + registry.register( + name="check_workflow_status", + description="Check the status of a workflow instance by its ID.", + parameters={ + "type": "object", + "properties": { + "instance_id": {"type": "string", "description": "UUID of the workflow instance"}, + }, + "required": ["instance_id"], + }, + handler=_check_workflow_status_handler, + plugin_name=self.manifest.name, + required_permission="workflows:read", + category="workflow", + ) + logger.info("Registered workflow agent tools: start_workflow, check_workflow_status") + async def on_deactivate(self, db, service_container, event_bus) -> None: """Clean up on deactivation.""" # Contract abmelden @@ -307,6 +394,13 @@ class AutomationPlugin(BasePlugin): unregister_agent_coordinator_tools() except Exception: logger.exception("Failed to unregister agent coordinator tools") + # Unregister workflow agent tools from the core AI tool registry + try: + from app.ai.tool_registry import get_tool_registry + get_tool_registry().unregister_plugin(self.manifest.name) + logger.info("Unregistered AI agent tools for plugin '%s'", self.manifest.name) + except Exception: + logger.exception("Failed to unregister AI agent tools") # Unregister MiniApps try: from app.plugins.builtins.kommunikation.contracts import get_miniapp_registry diff --git a/app/plugins/builtins/knowledge/plugin.py b/app/plugins/builtins/knowledge/plugin.py index 9d02001..a5acd07 100644 --- a/app/plugins/builtins/knowledge/plugin.py +++ b/app/plugins/builtins/knowledge/plugin.py @@ -1,6 +1,8 @@ """Knowledge plugin — LLM-based entity/relationship extraction, ask-knowledge, review queue.""" from __future__ import annotations import logging +import uuid +from typing import Any from app.plugins.base import BasePlugin from app.plugins.manifest import PluginManifest, PluginRouteDef @@ -59,9 +61,97 @@ class KnowledgePlugin(BasePlugin): logger.info("Registered knowledge extraction hooks") except Exception: logger.exception("Failed to register knowledge hooks") + # Register knowledge agent tools (I-AK: Agent→Knowledge) + try: + self._register_knowledge_agent_tools() + except Exception: + logger.exception("Failed to register knowledge agent tools") + + def _register_knowledge_agent_tools(self) -> None: + """Register I-AK agent tools for asking and searching knowledge.""" + from app.ai.tool_registry import get_tool_registry + registry = get_tool_registry() + + async def _ask_knowledge_handler(arguments: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: + """Ask a knowledge question.""" + from app.plugins.builtins.knowledge.services import ask_knowledge + from app.core.db import get_worker_session_factory + question = arguments.get("question", "") + tenant_id = context.get("tenant_id") + if not question or not tenant_id: + return {"error": "question and tenant_id required"} + factory = get_worker_session_factory() + async with factory() as db: + result = await ask_knowledge(db=db, tenant_id=uuid.UUID(str(tenant_id)), question=question) + return {"answer": result.get("answer", ""), "evidence_count": len(result.get("evidence", []))} + + registry.register( + name="ask_knowledge", + description="Ask a knowledge question. Searches wiki articles and graph relationships for context.", + parameters={ + "type": "object", + "properties": { + "question": {"type": "string", "description": "The question to ask"}, + }, + "required": ["question"], + }, + handler=_ask_knowledge_handler, + plugin_name=self.manifest.name, + required_permission="wiki:read", + category="knowledge", + ) + + async def _search_knowledge_handler(arguments: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: + """Search wiki articles via unified search.""" + from app.plugins.builtins.unified_search.provider_registry import get_search_registry + from app.core.db import get_worker_session_factory + query = arguments.get("query", "") + tenant_id = context.get("tenant_id") + if not query or not tenant_id: + return {"error": "query and tenant_id required"} + factory = get_worker_session_factory() + async with factory() as db: + search_registry = get_search_registry() + wiki_provider = search_registry.get("wiki_article") + if not wiki_provider: + return {"error": "Wiki search provider not available"} + results = await wiki_provider._search_fts_filtered( + db=db, tsquery=query, tenant_id=uuid.UUID(str(tenant_id)), limit=5, visible_ids=None + ) + return { + "results": [ + {"title": r.get("title", ""), "summary": (r.get("summary") or r.get("content", "")[:200] or "")} + for r in results + ], + "count": len(results), + } + + registry.register( + name="search_knowledge", + description="Search wiki articles by keyword. Returns matching articles with title and summary.", + parameters={ + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query"}, + }, + "required": ["query"], + }, + handler=_search_knowledge_handler, + plugin_name=self.manifest.name, + required_permission="wiki:read", + category="knowledge", + ) + logger.info("Registered knowledge agent tools: ask_knowledge, search_knowledge") async def on_deactivate(self, db, service_container, event_bus) -> None: """Clean up on deactivation.""" from app.core.hooks import unregister_actions_by_owner unregister_actions_by_owner("knowledge") + # Unregister knowledge agent tools from the core AI tool registry + try: + from app.ai.tool_registry import get_tool_registry + get_tool_registry().unregister_plugin(self.manifest.name) + logger.info("Unregistered AI agent tools for plugin '%s'", self.manifest.name) + except Exception: + logger.exception("Failed to unregister AI agent tools") await super().on_deactivate(db, service_container, event_bus)