Merge branch 'feat/service-center' into int

This commit is contained in:
Ida Dittrich 2026-03-06 15:32:59 +01:00
commit f4fb4637ea
5 changed files with 126 additions and 20 deletions

View file

@ -6,7 +6,7 @@ Handles feature initialization and RBAC catalog registration.
"""
import logging
from typing import Dict, List, Any
from typing import Dict, List, Any, Optional
logger = logging.getLogger(__name__)
@ -121,6 +121,106 @@ TEMPLATE_ROLES = [
},
]
# Service requirements - services this feature needs from the service center
REQUIRED_SERVICES = [
{"serviceKey": "chat", "meta": {"usage": "Workflow CRUD, messages, logs"}},
{"serviceKey": "ai", "meta": {"usage": "AI planning for workflow execution"}},
{"serviceKey": "utils", "meta": {"usage": "Timestamps, utilities"}},
{"serviceKey": "billing", "meta": {"usage": "AI call billing"}},
{"serviceKey": "extraction", "meta": {"usage": "Workflow method actions"}},
{"serviceKey": "sharepoint", "meta": {"usage": "SharePoint actions (listDocuments, uploadDocument, etc.)"}},
]
def getRequiredServiceKeys() -> List[str]:
"""Return list of service keys this feature requires."""
return [s["serviceKey"] for s in REQUIRED_SERVICES]
def getAutomationServices(
user,
mandateId: Optional[str] = None,
featureInstanceId: Optional[str] = None,
workflow=None,
) -> "_AutomationServiceHub":
"""
Get a service hub for the automation feature using the service center.
Resolves only the services declared in REQUIRED_SERVICES.
No legacy fallback - service center only.
Returns a hub-like object with: chat, ai, utils, billing, extraction,
sharepoint, rbac, interfaceDbApp, interfaceDbComponent, interfaceDbChat,
interfaceDbAutomation.
"""
from modules.serviceCenter import getService
from modules.serviceCenter.context import ServiceCenterContext
from modules.features.automation.interfaceFeatureAutomation import getInterface as getAutomationInterface
_workflow = workflow
if _workflow is None:
_workflow = type("_Placeholder", (), {"featureCode": FEATURE_CODE})()
ctx = ServiceCenterContext(
user=user,
mandate_id=mandateId,
feature_instance_id=featureInstanceId,
workflow=_workflow,
)
hub = _AutomationServiceHub()
hub.user = user
hub.mandateId = mandateId
hub.featureInstanceId = featureInstanceId
hub.workflow = workflow
hub.featureCode = FEATURE_CODE
hub.allowedProviders = None
for spec in REQUIRED_SERVICES:
key = spec["serviceKey"]
try:
svc = getService(key, ctx, legacy_hub=None)
setattr(hub, key, svc)
except Exception as e:
logger.warning(f"Could not resolve service '{key}' for automation: {e}")
setattr(hub, key, None)
# Copy interfaces from chat service for WorkflowManager compatibility
if hub.chat:
hub.interfaceDbApp = getattr(hub.chat, "interfaceDbApp", None)
hub.interfaceDbComponent = getattr(hub.chat, "interfaceDbComponent", None)
hub.interfaceDbChat = getattr(hub.chat, "interfaceDbChat", None)
# RBAC for MethodBase action permission checks (workflow methods)
hub.rbac = getattr(hub.interfaceDbApp, "rbac", None) if hub.interfaceDbApp else None
# Set interfaceDbAutomation from feature interface
hub.interfaceDbAutomation = getAutomationInterface(
user, mandateId=mandateId, featureInstanceId=featureInstanceId
)
return hub
class _AutomationServiceHub:
"""Lightweight hub exposing only services required by the automation feature."""
user = None
mandateId = None
featureInstanceId = None
workflow = None
featureCode = "automation"
allowedProviders = None
interfaceDbApp = None
interfaceDbComponent = None
interfaceDbChat = None
interfaceDbAutomation = None
rbac = None
chat = None
ai = None
utils = None
billing = None
extraction = None
sharepoint = None
def getFeatureDefinition() -> Dict[str, Any]:
"""Return the feature definition for registration."""

View file

@ -14,6 +14,7 @@ import json
# Import interfaces and models
from modules.features.automation.interfaceFeatureAutomation import getInterface as getAutomationInterface
from modules.features.automation.mainAutomation import getAutomationServices
from modules.auth import limiter, getRequestContext, RequestContext
from modules.features.automation.datamodelFeatureAutomation import AutomationDefinition, AutomationTemplate
from modules.datamodels.datamodelChat import ChatWorkflow
@ -147,12 +148,14 @@ def get_available_actions(
"""
try:
from modules.workflows.processing.shared.methodDiscovery import methods, discoverMethods
from modules.services import getInterface as getServices
# Ensure methods are discovered (need a service center for discovery)
# Ensure methods are discovered (need a service hub for discovery)
if not methods:
# Create a lightweight service center for method discovery
services = getServices(context.user, mandateId=context.mandateId)
services = getAutomationServices(
context.user,
mandateId=context.mandateId,
featureInstanceId=context.featureInstanceId,
)
discoverMethods(services)
actionsList = []
@ -365,8 +368,11 @@ async def execute_automation_route(
) -> ChatWorkflow:
"""Execute an automation immediately (test mode)"""
try:
from modules.services import getInterface as getServices
services = getServices(context.user, mandateId=context.mandateId, featureInstanceId=context.featureInstanceId)
services = getAutomationServices(
context.user,
mandateId=context.mandateId,
featureInstanceId=context.featureInstanceId,
)
# Load automation with current user's context (user has RBAC permissions via UI)
automation = services.interfaceDbAutomation.getAutomationDefinition(automationId, includeSystemFields=True)

View file

@ -45,8 +45,8 @@ def get_all_automation_events(
try:
from modules.shared.eventManagement import eventManager
from modules.interfaces.interfaceDbApp import getRootInterface
from modules.services import getInterface as getServices
from modules.features.automation.mainAutomation import getAutomationServices
if not eventManager.scheduler:
return []
@ -74,7 +74,7 @@ def get_all_automation_events(
rootInterface = getRootInterface()
eventUser = rootInterface.getUserByUsername("event")
if eventUser:
services = getServices(currentUser, None)
services = getAutomationServices(currentUser, mandateId=None, featureInstanceId=None)
allAutomations = services.interfaceDbAutomation.getAllAutomationDefinitionsWithRBAC(eventUser)
# Build lookup by automation ID
@ -171,8 +171,8 @@ async def sync_all_automation_events(
detail="Event user not available"
)
from modules.services import getInterface as getServices
services = getServices(currentUser, None)
from modules.features.automation.mainAutomation import getAutomationServices
services = getAutomationServices(currentUser, mandateId=None, featureInstanceId=None)
result = syncAutomationEvents(services, eventUser)
return {
"success": True,

View file

@ -17,7 +17,7 @@ from modules.features.automation.datamodelFeatureAutomation import AutomationDef
from modules.datamodels.datamodelUam import User
from modules.shared.timeUtils import getUtcTimestamp
from modules.shared.eventManagement import eventManager
from modules.services import getInterface as getServices
from modules.features.automation.mainAutomation import getAutomationServices
from modules.workflows.workflowManager import WorkflowManager
from .subAutomationUtils import parseScheduleToCron, planToPrompt, replacePlaceholders
@ -38,7 +38,7 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
featureCode: Feature code (e.g., 'chatplayground', 'automation')
"""
try:
services = getServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
# Store allowedProviders in services context for model selection
if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders:
@ -59,7 +59,7 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
async def chatStop(currentUser: User, workflowId: str, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None) -> ChatWorkflow:
"""Stops a running chat."""
try:
services = getServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
if featureCode:
services.featureCode = featureCode
workflowManager = WorkflowManager(services)
@ -163,7 +163,7 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
# Set workflow name with "automated" prefix — use creatorUser's Services
# (services parameter is eventServices with eventUser context, must use creatorUser context)
creatorServices = getServices(creatorUser, mandateId=automationMandateId, featureInstanceId=automationFeatureInstanceId)
creatorServices = getAutomationServices(creatorUser, mandateId=automationMandateId, featureInstanceId=automationFeatureInstanceId)
automationLabel = automation.label or "Unknown Automation"
workflowName = f"automated: {automationLabel}"
creatorServices.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName})
@ -292,7 +292,7 @@ def createAutomationEventHandler(automationId: str, eventUser):
return
# Load automation using SysAdmin eventUser (has unrestricted access)
eventServices = getServices(eventUser, None)
eventServices = getAutomationServices(eventUser, mandateId=None, featureInstanceId=None)
automation = eventServices.interfaceDbAutomation.getAutomationDefinition(automationId, includeSystemFields=True)
if not automation or not getattr(automation, "active", False):
logger.warning(f"Automation {automationId} not found or not active, skipping execution")

View file

@ -9,7 +9,7 @@ the automation scheduler (loading/syncing scheduled automation events).
"""
import logging
from modules.services import getInterface as getServices
from modules.features.automation.mainAutomation import getAutomationServices
logger = logging.getLogger(__name__)
@ -31,12 +31,12 @@ def start(eventUser) -> bool:
from modules.shared.callbackRegistry import callbackRegistry
# Get services for event user (provides access to interfaces)
services = getServices(eventUser, None)
services = getAutomationServices(eventUser, mandateId=None, featureInstanceId=None)
# Register callback for automation changes
def onAutomationChanged(chatInterface):
"""Callback triggered when automations are created/updated/deleted."""
eventServices = getServices(eventUser, None)
eventServices = getAutomationServices(eventUser, mandateId=None, featureInstanceId=None)
syncAutomationEvents(eventServices, eventUser)
callbackRegistry.register('automation.changed', onAutomationChanged)