From ce612ffcfcb8cee9e8d2e84d1acb751583a583f6 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Mon, 8 Jun 2026 14:46:52 +0200 Subject: [PATCH] import referencing fixes --- app.py | 2 +- modules/aicore/aicoreModelRegistry.py | 25 ++++++++---- modules/datamodels/datamodelPortTypes.py | 21 ++++++++++ modules/demoConfigs/pwgDemo2026.py | 2 +- .../features/redmine/workflows/__init__.py | 3 ++ .../workflows}/methodRedmine/__init__.py | 0 .../methodRedmine/actions/__init__.py | 0 .../methodRedmine/actions/_shared.py | 0 .../methodRedmine/actions/createTicket.py | 0 .../methodRedmine/actions/getStats.py | 0 .../methodRedmine/actions/listRelations.py | 0 .../methodRedmine/actions/listTickets.py | 0 .../methodRedmine/actions/readTicket.py | 0 .../methodRedmine/actions/runSync.py | 0 .../methodRedmine/actions/updateTicket.py | 0 .../workflows}/methodRedmine/methodRedmine.py | 0 .../workspace/routeFeatureWorkspace.py | 2 +- .../interfaces/interfaceWorkflowAutomation.py | 6 +-- modules/nodeCatalog/__init__.py | 8 ++++ .../_workflowFileSchema.py | 0 .../editor => nodeCatalog}/nodeAdapter.py | 0 .../nodeDefinitions/__init__.py | 0 .../nodeDefinitions/ai.py | 4 +- .../nodeDefinitions/clickup.py | 2 +- .../nodeDefinitions/context.py | 4 +- .../nodeDefinitions/contextPickerHelp.py | 8 ++-- .../nodeDefinitions/data.py | 2 +- .../nodeDefinitions/email.py | 4 +- .../nodeDefinitions/file.py | 6 +-- .../nodeDefinitions/flow.py | 10 ++--- .../nodeDefinitions/input.py | 2 +- .../nodeDefinitions/redmine.py | 2 +- .../nodeDefinitions/sharepoint.py | 2 +- .../nodeDefinitions/triggers.py | 2 +- .../nodeDefinitions/trustee.py | 2 +- .../editor => nodeCatalog}/portTypes.py | 2 +- modules/routes/routeWorkflowAutomation.py | 20 +++++++--- .../core/serviceStreaming/__init__.py | 3 +- .../core/serviceStreaming/eventManager.py | 8 ---- .../serviceStreaming/mainServiceStreaming.py | 2 +- .../serviceAgent/externalToolRegistry.py | 39 +++++++++++++++++++ .../services/serviceAgent/mainServiceAgent.py | 27 ++++++------- modules/serviceHub/__init__.py | 7 ---- modules/system/i18nBootSync.py | 4 +- .../agentTools.py} | 6 +-- .../editor/adapterValidator.py | 2 +- .../editor/conditionOperators.py | 2 +- .../workflowAutomation/editor/nodeRegistry.py | 8 ++-- .../workflowAutomation/editor/switchOutput.py | 2 +- .../editor/upstreamPathsService.py | 4 +- .../engine/executionEngine.py | 4 +- .../engine/executors/actionNodeExecutor.py | 11 +++--- .../engine/executors/dataExecutor.py | 2 +- .../engine/executors/flowExecutor.py | 2 +- .../workflowAutomation/engine/graphUtils.py | 12 +++--- .../engine/pickNotPushMigration.py | 4 +- .../helpers.py} | 5 ++- .../mainWorkflowAutomation.py | 15 +++++++ modules/workflows/automation2/__init__.py | 13 ------- .../automation2/executors/__init__.py | 23 ----------- .../methods/_actionSignatureValidator.py | 4 +- modules/workflows/methods/methodBase.py | 2 +- .../methodContext/actions/setContext.py | 19 ++++----- .../processing/core/actionExecutor.py | 2 +- .../processing/core/messageCreator.py | 2 +- .../workflows/processing/core/taskPlanner.py | 2 +- .../processing/modes/modeAutomation.py | 2 +- .../workflows/processing/modes/modeDynamic.py | 2 +- .../processing/shared/parameterValidation.py | 2 +- .../workflows/processing/shared/stateTools.py | 10 ----- .../workflows/processing/workflowProcessor.py | 2 +- modules/workflows/scheduler/__init__.py | 11 ------ modules/workflows/workflowManager.py | 2 +- .../script_migrate_feature_instance_refs.py | 2 +- tests/eval/runTrusteeBenchmark.py | 2 +- tests/functional/test01_ai_model_selection.py | 2 +- tests/functional/test02_ai_models.py | 2 +- tests/functional/test03_ai_operations.py | 2 +- tests/functional/test04_ai_behavior.py | 2 +- .../graphicalEditor/test_adapter_validator.py | 6 +-- ...est_featureInstanceRef_node_definitions.py | 4 +- .../unit/graphicalEditor/test_node_adapter.py | 2 +- .../graphicalEditor/test_portTypes_catalog.py | 2 +- .../test_port_schema_recursive.py | 2 +- .../test_upstream_paths_and_graph_schema.py | 2 +- .../test_action_signature_validator.py | 2 +- .../test_trustee_schema_compliance.py | 4 +- .../unit/nodeDefinitions/test_usesai_flag.py | 2 +- .../serviceAgent/test_workflow_tools_crud.py | 2 +- tests/unit/workflow/test_node_combinations.py | 4 +- .../unit/workflow/test_phase3_context_node.py | 4 +- .../workflow/test_phase4_workflow_nodes.py | 2 +- tests/unit/workflow/test_phase5_highvol.py | 2 +- .../workflow/test_switch_filtered_output.py | 2 +- .../unit/workflow/test_workflowFileSchema.py | 2 +- .../workflows/test_automation2_graphUtils.py | 2 +- 96 files changed, 249 insertions(+), 217 deletions(-) create mode 100644 modules/features/redmine/workflows/__init__.py rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/__init__.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/__init__.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/_shared.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/createTicket.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/getStats.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/listRelations.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/listTickets.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/readTicket.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/runSync.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/actions/updateTicket.py (100%) rename modules/{workflows/methods => features/redmine/workflows}/methodRedmine/methodRedmine.py (100%) create mode 100644 modules/nodeCatalog/__init__.py rename modules/{workflowAutomation/editor => nodeCatalog}/_workflowFileSchema.py (100%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeAdapter.py (100%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/__init__.py (100%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/ai.py (99%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/clickup.py (99%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/context.py (98%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/contextPickerHelp.py (78%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/data.py (97%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/email.py (96%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/file.py (90%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/flow.py (97%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/input.py (98%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/redmine.py (98%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/sharepoint.py (99%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/triggers.py (96%) rename modules/{workflowAutomation/editor => nodeCatalog}/nodeDefinitions/trustee.py (98%) rename modules/{workflowAutomation/editor => nodeCatalog}/portTypes.py (99%) delete mode 100644 modules/serviceCenter/core/serviceStreaming/eventManager.py create mode 100644 modules/serviceCenter/services/serviceAgent/externalToolRegistry.py delete mode 100644 modules/serviceHub/__init__.py rename modules/{serviceCenter/services/serviceAgent/workflowTools.py => workflowAutomation/agentTools.py} (99%) rename modules/{shared/workflowAutomationHelpers.py => workflowAutomation/helpers.py} (99%) delete mode 100644 modules/workflows/automation2/__init__.py delete mode 100644 modules/workflows/automation2/executors/__init__.py delete mode 100644 modules/workflows/processing/shared/stateTools.py delete mode 100644 modules/workflows/scheduler/__init__.py diff --git a/app.py b/app.py index 2ecf3ad5..185ea95d 100644 --- a/app.py +++ b/app.py @@ -552,7 +552,7 @@ async def lifespan(app: FastAPI): # to finish (up to 120 s keepalive timeout) before the rest of # the shutdown can proceed. try: - from modules.serviceCenter.core.serviceStreaming.eventManager import get_event_manager as _getStreamingEM + from modules.shared.eventManager import get_event_manager as _getStreamingEM _getStreamingEM().shutdown() except Exception as e: logger.warning(f"Streaming EventManager shutdown failed: {e}") diff --git a/modules/aicore/aicoreModelRegistry.py b/modules/aicore/aicoreModelRegistry.py index 813b3ac4..164f71f9 100644 --- a/modules/aicore/aicoreModelRegistry.py +++ b/modules/aicore/aicoreModelRegistry.py @@ -10,14 +10,16 @@ import importlib import os import time import threading -from typing import Dict, List, Optional, Any, Tuple +from typing import Dict, List, Optional, Any, Tuple, TYPE_CHECKING from modules.datamodels.datamodelAi import AiModel +from modules.datamodels.datamodelRbac import AccessRuleContext from .aicoreBase import BaseConnectorAi from modules.datamodels.datamodelUam import User -from modules.security.rbacHelpers import checkResourceAccess -from modules.security.rbac import RbacClass from modules.connectors.connectorDbPostgre import DatabaseConnector +if TYPE_CHECKING: + from modules.security.rbac import RbacClass + logger = logging.getLogger(__name__) # TODO TESTING: Override maxTokens for all models during testing @@ -186,7 +188,7 @@ class ModelRegistry: def getAvailableModels( self, currentUser: Optional[User] = None, - rbacInstance: Optional[RbacClass] = None, + rbacInstance: Optional["RbacClass"] = None, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None ) -> List[AiModel]: @@ -237,7 +239,7 @@ class ModelRegistry: self, models: List[AiModel], currentUser: User, - rbacInstance: RbacClass, + rbacInstance: "RbacClass", mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None ) -> List[AiModel]: @@ -262,7 +264,7 @@ class ModelRegistry: logger.debug(f"User {currentUser.username} does not have access to model {model.displayName} (connector: {model.connectorType})") return filteredModels - def getModel(self, displayName: str, currentUser: Optional[User] = None, rbacInstance: Optional[RbacClass] = None) -> Optional[AiModel]: + def getModel(self, displayName: str, currentUser: Optional[User] = None, rbacInstance: Optional["RbacClass"] = None) -> Optional[AiModel]: """Get a specific model by displayName, optionally checking RBAC permissions. Args: @@ -284,8 +286,15 @@ class ModelRegistry: connectorResourcePath = f"ai.model.{model.connectorType}" modelResourcePath = f"ai.model.{model.connectorType}.{model.displayName}" - hasConnectorAccess = checkResourceAccess(rbacInstance, currentUser, connectorResourcePath) - hasModelAccess = checkResourceAccess(rbacInstance, currentUser, modelResourcePath) + try: + connPerms = rbacInstance.getUserPermissions(currentUser, AccessRuleContext.RESOURCE, connectorResourcePath) + modelPerms = rbacInstance.getUserPermissions(currentUser, AccessRuleContext.RESOURCE, modelResourcePath) + hasConnectorAccess = connPerms.view if connPerms else False + hasModelAccess = modelPerms.view if modelPerms else False + except Exception as e: + logger.error(f"Error checking resource access for {modelResourcePath}: {e}") + hasConnectorAccess = False + hasModelAccess = False if not (hasConnectorAccess or hasModelAccess): logger.warning(f"User {currentUser.username} does not have access to model {displayName}") diff --git a/modules/datamodels/datamodelPortTypes.py b/modules/datamodels/datamodelPortTypes.py index 6d87c25e..1357af4f 100644 --- a/modules/datamodels/datamodelPortTypes.py +++ b/modules/datamodels/datamodelPortTypes.py @@ -549,3 +549,24 @@ PORT_TYPE_CATALOG: Dict[str, PortSchema] = { PRIMITIVE_TYPES: frozenset = frozenset({ "str", "int", "bool", "float", "Any", "Dict", "List", }) + + +def stripContainer(typeStr: str) -> List[str]: + """ + Extract referenced type names from a PortField.type string. + + Examples: + "str" -> ["str"] + "List[Document]" -> ["Document"] + "Dict[str,Any]" -> ["str", "Any"] + "ConnectionRef" -> ["ConnectionRef"] + "List[ProcessError]" -> ["ProcessError"] + """ + s = (typeStr or "").strip() + if not s: + return [] + if "[" in s and s.endswith("]"): + inner = s[s.index("[") + 1 : -1] + parts = [p.strip() for p in inner.split(",") if p.strip()] + return parts or [s] + return [s] diff --git a/modules/demoConfigs/pwgDemo2026.py b/modules/demoConfigs/pwgDemo2026.py index c2c196af..efcd8c8a 100644 --- a/modules/demoConfigs/pwgDemo2026.py +++ b/modules/demoConfigs/pwgDemo2026.py @@ -561,7 +561,7 @@ class PwgDemo2026(BaseDemoConfig): summary["errors"].append(f"WorkflowAutomation DB connection failed: {exc}") return - from modules.workflowAutomation.editor._workflowFileSchema import ( + from modules.nodeCatalog._workflowFileSchema import ( envelopeToWorkflowData, validateFileEnvelope, ) diff --git a/modules/features/redmine/workflows/__init__.py b/modules/features/redmine/workflows/__init__.py new file mode 100644 index 00000000..8c4ceb1a --- /dev/null +++ b/modules/features/redmine/workflows/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2025 Patrick Motsch +# All rights reserved. +"""Feature-owned workflow methods for Redmine.""" diff --git a/modules/workflows/methods/methodRedmine/__init__.py b/modules/features/redmine/workflows/methodRedmine/__init__.py similarity index 100% rename from modules/workflows/methods/methodRedmine/__init__.py rename to modules/features/redmine/workflows/methodRedmine/__init__.py diff --git a/modules/workflows/methods/methodRedmine/actions/__init__.py b/modules/features/redmine/workflows/methodRedmine/actions/__init__.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/__init__.py rename to modules/features/redmine/workflows/methodRedmine/actions/__init__.py diff --git a/modules/workflows/methods/methodRedmine/actions/_shared.py b/modules/features/redmine/workflows/methodRedmine/actions/_shared.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/_shared.py rename to modules/features/redmine/workflows/methodRedmine/actions/_shared.py diff --git a/modules/workflows/methods/methodRedmine/actions/createTicket.py b/modules/features/redmine/workflows/methodRedmine/actions/createTicket.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/createTicket.py rename to modules/features/redmine/workflows/methodRedmine/actions/createTicket.py diff --git a/modules/workflows/methods/methodRedmine/actions/getStats.py b/modules/features/redmine/workflows/methodRedmine/actions/getStats.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/getStats.py rename to modules/features/redmine/workflows/methodRedmine/actions/getStats.py diff --git a/modules/workflows/methods/methodRedmine/actions/listRelations.py b/modules/features/redmine/workflows/methodRedmine/actions/listRelations.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/listRelations.py rename to modules/features/redmine/workflows/methodRedmine/actions/listRelations.py diff --git a/modules/workflows/methods/methodRedmine/actions/listTickets.py b/modules/features/redmine/workflows/methodRedmine/actions/listTickets.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/listTickets.py rename to modules/features/redmine/workflows/methodRedmine/actions/listTickets.py diff --git a/modules/workflows/methods/methodRedmine/actions/readTicket.py b/modules/features/redmine/workflows/methodRedmine/actions/readTicket.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/readTicket.py rename to modules/features/redmine/workflows/methodRedmine/actions/readTicket.py diff --git a/modules/workflows/methods/methodRedmine/actions/runSync.py b/modules/features/redmine/workflows/methodRedmine/actions/runSync.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/runSync.py rename to modules/features/redmine/workflows/methodRedmine/actions/runSync.py diff --git a/modules/workflows/methods/methodRedmine/actions/updateTicket.py b/modules/features/redmine/workflows/methodRedmine/actions/updateTicket.py similarity index 100% rename from modules/workflows/methods/methodRedmine/actions/updateTicket.py rename to modules/features/redmine/workflows/methodRedmine/actions/updateTicket.py diff --git a/modules/workflows/methods/methodRedmine/methodRedmine.py b/modules/features/redmine/workflows/methodRedmine/methodRedmine.py similarity index 100% rename from modules/workflows/methods/methodRedmine/methodRedmine.py rename to modules/features/redmine/workflows/methodRedmine/methodRedmine.py diff --git a/modules/features/workspace/routeFeatureWorkspace.py b/modules/features/workspace/routeFeatureWorkspace.py index d9fc3f4d..6d83e234 100644 --- a/modules/features/workspace/routeFeatureWorkspace.py +++ b/modules/features/workspace/routeFeatureWorkspace.py @@ -27,7 +27,7 @@ from modules.interfaces import interfaceDbChat, interfaceDbManagement from modules.features.workspace import interfaceFeatureWorkspace from modules.interfaces.interfaceDbKnowledge import getInterface as getKnowledgeInterface from modules.interfaces.interfaceAiObjects import AiObjects -from modules.serviceCenter.core.serviceStreaming import get_event_manager +from modules.shared.eventManager import get_event_manager from modules.serviceCenter.services.serviceAgent.datamodelAgent import AgentEventTypeEnum, PendingFileEdit from modules.shared.timeUtils import parseTimestamp from modules.shared.i18nRegistry import apiRouteContext, resolveText diff --git a/modules/interfaces/interfaceWorkflowAutomation.py b/modules/interfaces/interfaceWorkflowAutomation.py index 6d192451..ba8fe6e7 100644 --- a/modules/interfaces/interfaceWorkflowAutomation.py +++ b/modules/interfaces/interfaceWorkflowAutomation.py @@ -692,7 +692,7 @@ class WorkflowAutomationObjects: envelope) and can be JSON-serialized as-is. Returns ``None`` if the workflow does not exist for this mandate. """ - from modules.workflowAutomation.editor._workflowFileSchema import buildFileFromWorkflow + from modules.nodeCatalog._workflowFileSchema import buildFileFromWorkflow wf = self.getWorkflow(workflowId) if not wf: @@ -711,11 +711,11 @@ class WorkflowAutomationObjects: ``existingWorkflowId`` is given. Imports are always saved with ``active=False`` so operators can review before scheduling. """ - from modules.workflowAutomation.editor._workflowFileSchema import ( + from modules.nodeCatalog._workflowFileSchema import ( envelopeToWorkflowData, validateFileEnvelope, ) - from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES + from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES knownTypes = [n.get("id") for n in STATIC_NODE_TYPES if isinstance(n, dict) and n.get("id")] normalizedEnvelope, warnings = validateFileEnvelope(envelope, knownNodeTypes=knownTypes) diff --git a/modules/nodeCatalog/__init__.py b/modules/nodeCatalog/__init__.py new file mode 100644 index 00000000..cbe6a49e --- /dev/null +++ b/modules/nodeCatalog/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Patrick Motsch +""" +nodeCatalog (L2) — neutraler Node-Definitions-Container. + +Statische Node-Schemas, Port-Typen, Node-Adapter und das Workflow-File-Schema. +Haengt NUR von shared (L0) + datamodels (L1) ab. Wird von workflowAutomation, +serviceCenter, interfaces, system, routes und demoConfigs importiert. +""" diff --git a/modules/workflowAutomation/editor/_workflowFileSchema.py b/modules/nodeCatalog/_workflowFileSchema.py similarity index 100% rename from modules/workflowAutomation/editor/_workflowFileSchema.py rename to modules/nodeCatalog/_workflowFileSchema.py diff --git a/modules/workflowAutomation/editor/nodeAdapter.py b/modules/nodeCatalog/nodeAdapter.py similarity index 100% rename from modules/workflowAutomation/editor/nodeAdapter.py rename to modules/nodeCatalog/nodeAdapter.py diff --git a/modules/workflowAutomation/editor/nodeDefinitions/__init__.py b/modules/nodeCatalog/nodeDefinitions/__init__.py similarity index 100% rename from modules/workflowAutomation/editor/nodeDefinitions/__init__.py rename to modules/nodeCatalog/nodeDefinitions/__init__.py diff --git a/modules/workflowAutomation/editor/nodeDefinitions/ai.py b/modules/nodeCatalog/nodeDefinitions/ai.py similarity index 99% rename from modules/workflowAutomation/editor/nodeDefinitions/ai.py rename to modules/nodeCatalog/nodeDefinitions/ai.py index 37cf691f..8e0f081e 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/ai.py +++ b/modules/nodeCatalog/nodeDefinitions/ai.py @@ -3,10 +3,10 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.contextPickerHelp import ( +from modules.nodeCatalog.nodeDefinitions.contextPickerHelp import ( CONTEXT_BUILDER_PARAM_DESCRIPTION, ) -from modules.workflowAutomation.editor.nodeDefinitions.flow import ( +from modules.nodeCatalog.nodeDefinitions.flow import ( CONTEXT_ENVELOPE_DATA_PICK_OPTIONS, ) diff --git a/modules/workflowAutomation/editor/nodeDefinitions/clickup.py b/modules/nodeCatalog/nodeDefinitions/clickup.py similarity index 99% rename from modules/workflowAutomation/editor/nodeDefinitions/clickup.py rename to modules/nodeCatalog/nodeDefinitions/clickup.py index 60c60bd5..1e330d29 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/clickup.py +++ b/modules/nodeCatalog/nodeDefinitions/clickup.py @@ -4,7 +4,7 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS +from modules.nodeCatalog.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS TASK_LIST_DATA_PICK_OPTIONS = [ { diff --git a/modules/workflowAutomation/editor/nodeDefinitions/context.py b/modules/nodeCatalog/nodeDefinitions/context.py similarity index 98% rename from modules/workflowAutomation/editor/nodeDefinitions/context.py rename to modules/nodeCatalog/nodeDefinitions/context.py index 839417e9..dc05fa40 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/context.py +++ b/modules/nodeCatalog/nodeDefinitions/context.py @@ -4,7 +4,7 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.flow import ( +from modules.nodeCatalog.nodeDefinitions.flow import ( CONTEXT_ENVELOPE_DATA_PICK_OPTIONS, CONTEXT_MERGE_ACTION_RESULT_DATA_PICK_OPTIONS, ) @@ -245,7 +245,7 @@ CONTEXT_NODES = [ "description": t( "Filtert fuer die Presentation-Schicht nach typeGroup/MIME " "(gilt fuer alle Dokumenttypen analog, nicht nur PDF). " - "Passt zum Inhaltsfilter „Alles"; „Text & Tabellen" blendet Bild-Parts in der Presentation aus." + "Passt zum Inhaltsfilter „Alles“; „Text & Tabellen“ blendet Bild-Parts in der Presentation aus." ), }, { diff --git a/modules/workflowAutomation/editor/nodeDefinitions/contextPickerHelp.py b/modules/nodeCatalog/nodeDefinitions/contextPickerHelp.py similarity index 78% rename from modules/workflowAutomation/editor/nodeDefinitions/contextPickerHelp.py rename to modules/nodeCatalog/nodeDefinitions/contextPickerHelp.py index 55529951..116164c1 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/contextPickerHelp.py +++ b/modules/nodeCatalog/nodeDefinitions/contextPickerHelp.py @@ -4,14 +4,14 @@ from modules.shared.i18nRegistry import t CONTEXT_BUILDER_PARAM_DESCRIPTION = t( - "Inhalt aus vorherigen Schritten wählen (DataRef / Daten-Picker): z. B. „response" für Klartext, " + "Inhalt aus vorherigen Schritten wählen (DataRef / Daten-Picker): z. B. „response“ für Klartext, " "Handover-Pfade für strukturiertes JSON oder Medienlisten. " "Die Auflösung erfolgt vollständig serverseitig (`resolveParameterReferences`). " - "Formular-Schritte speichern Antworten unter „payload" — fehlt ein gewählter Pfad am Root, " - "wird derselbe Pfad automatisch unter „payload" nachgeschlagen (Kompatibilität mit älteren " + "Formular-Schritte speichern Antworten unter „payload“ — fehlt ein gewählter Pfad am Root, " + "wird derselbe Pfad automatisch unter „payload“ nachgeschlagen (Kompatibilität mit älteren " "und neuen Picker-Pfaden). " "In Freitext-/Template-Feldern werden weiterhin Platzhalter `{{KnotenId.feld.b.z.}}` ersetzt " - "(gleiche Semantik inkl. optionalem Nachschlagen unter „payload")." + "(gleiche Semantik inkl. optionalem Nachschlagen unter „payload“)." ) # Kurzreferenz für Node-Beschreibungen (optional einbinden): dieselbe Auflösungslogik diff --git a/modules/workflowAutomation/editor/nodeDefinitions/data.py b/modules/nodeCatalog/nodeDefinitions/data.py similarity index 97% rename from modules/workflowAutomation/editor/nodeDefinitions/data.py rename to modules/nodeCatalog/nodeDefinitions/data.py index c8a4a3e5..a12ddeb6 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/data.py +++ b/modules/nodeCatalog/nodeDefinitions/data.py @@ -3,7 +3,7 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.ai import CONSOLIDATE_RESULT_DATA_PICK_OPTIONS +from modules.nodeCatalog.nodeDefinitions.ai import CONSOLIDATE_RESULT_DATA_PICK_OPTIONS AGGREGATE_RESULT_DATA_PICK_OPTIONS = [ { diff --git a/modules/workflowAutomation/editor/nodeDefinitions/email.py b/modules/nodeCatalog/nodeDefinitions/email.py similarity index 96% rename from modules/workflowAutomation/editor/nodeDefinitions/email.py rename to modules/nodeCatalog/nodeDefinitions/email.py index d5c7fe8c..a0503452 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/email.py +++ b/modules/nodeCatalog/nodeDefinitions/email.py @@ -3,10 +3,10 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.contextPickerHelp import ( +from modules.nodeCatalog.nodeDefinitions.contextPickerHelp import ( CONTEXT_BUILDER_PARAM_DESCRIPTION, ) -from modules.workflowAutomation.editor.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS +from modules.nodeCatalog.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS EMAIL_LIST_DATA_PICK_OPTIONS = [ { diff --git a/modules/workflowAutomation/editor/nodeDefinitions/file.py b/modules/nodeCatalog/nodeDefinitions/file.py similarity index 90% rename from modules/workflowAutomation/editor/nodeDefinitions/file.py rename to modules/nodeCatalog/nodeDefinitions/file.py index 88deb5ec..70c13a07 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/file.py +++ b/modules/nodeCatalog/nodeDefinitions/file.py @@ -3,10 +3,10 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.contextPickerHelp import ( +from modules.nodeCatalog.nodeDefinitions.contextPickerHelp import ( CONTEXT_BUILDER_PARAM_DESCRIPTION, ) -from modules.workflowAutomation.editor.nodeDefinitions.ai import DOCUMENT_LIST_DATA_PICK_OPTIONS +from modules.nodeCatalog.nodeDefinitions.ai import DOCUMENT_LIST_DATA_PICK_OPTIONS FILE_NODES = [ { @@ -14,7 +14,7 @@ FILE_NODES = [ "category": "file", "label": t("Datei erstellen"), "description": t( - "Erstellt eine Datei aus der Presentation von „Inhalt extrahieren" " + "Erstellt eine Datei aus der Presentation von „Inhalt extrahieren“ " "(``data`` oder Schleifen-``bodyResults``). Ausgabe über den Generation-Service." ), "parameters": [ diff --git a/modules/workflowAutomation/editor/nodeDefinitions/flow.py b/modules/nodeCatalog/nodeDefinitions/flow.py similarity index 97% rename from modules/workflowAutomation/editor/nodeDefinitions/flow.py rename to modules/nodeCatalog/nodeDefinitions/flow.py index fe1b1f30..94f517d9 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/flow.py +++ b/modules/nodeCatalog/nodeDefinitions/flow.py @@ -96,7 +96,7 @@ MERGE_RESULT_DATA_PICK_OPTIONS = [ { "path": ["first"], "pickerLabel": t("Erster Zweig"), - "detail": t("Daten vom ersten verbundenen Eingang (Modus „first")."), + "detail": t("Daten vom ersten verbundenen Eingang (Modus „first“)."), "recommended": False, "type": "Any", }, @@ -243,9 +243,9 @@ FLOW_NODES = [ "category": "flow", "label": t("Schleife / Für jedes"), "description": t( - "Zwei Ausgänge: „Schleife" verbindet den Rumpf (pro Element); optional führt der Rumpf " + "Zwei Ausgänge: „Schleife“ verbindet den Rumpf (pro Element); optional führt der Rumpf " "mit einem Rücklauf-Pfeil wieder zum **gleichen Eingang** wie der vorherige Schritt (wie in n8n). " - "„Fertig" führt genau einmal fort, wenn alle Iterationen beendet sind. " + "„Fertig“ führt genau einmal fort, wenn alle Iterationen beendet sind. " "Die zu durchlaufende Liste wählen Sie wie bisher; UDM-/Strukturdaten werden automatisch sinnvoll in Elemente aufgelöst." ), "parameters": [ @@ -266,7 +266,7 @@ FLOW_NODES = [ }, "description": t( "Welche Elemente die Schleife besucht: alle, nur das erste/letzte, jedes zweite/dritte " - "oder jedes n-te (Schritt dann unter „Schrittweite")." + "oder jedes n-te (Schritt dann unter „Schrittweite“)." ), "default": "all", }, @@ -276,7 +276,7 @@ FLOW_NODES = [ "required": False, "frontendType": "number", "frontendOptions": {"min": 2, "max": 100}, - "description": t("Nur bei „jedes n-te": Schrittweite (z. B. 5 = jedes 5. Element ab Index 0)."), + "description": t("Nur bei „jedes n-te“: Schrittweite (z. B. 5 = jedes 5. Element ab Index 0)."), "default": 2, }, { diff --git a/modules/workflowAutomation/editor/nodeDefinitions/input.py b/modules/nodeCatalog/nodeDefinitions/input.py similarity index 98% rename from modules/workflowAutomation/editor/nodeDefinitions/input.py rename to modules/nodeCatalog/nodeDefinitions/input.py index 5c152fdb..0f469880 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/input.py +++ b/modules/nodeCatalog/nodeDefinitions/input.py @@ -3,7 +3,7 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.ai import DOCUMENT_LIST_DATA_PICK_OPTIONS +from modules.nodeCatalog.nodeDefinitions.ai import DOCUMENT_LIST_DATA_PICK_OPTIONS BOOL_RESULT_DATA_PICK_OPTIONS = [ { diff --git a/modules/workflowAutomation/editor/nodeDefinitions/redmine.py b/modules/nodeCatalog/nodeDefinitions/redmine.py similarity index 98% rename from modules/workflowAutomation/editor/nodeDefinitions/redmine.py rename to modules/nodeCatalog/nodeDefinitions/redmine.py index f20f2901..bf61cd26 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/redmine.py +++ b/modules/nodeCatalog/nodeDefinitions/redmine.py @@ -4,7 +4,7 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS +from modules.nodeCatalog.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS # Typed FeatureInstance binding (replaces legacy `string, hidden`). # - type FeatureInstanceRef[redmine] is filtered by the DataPicker. diff --git a/modules/workflowAutomation/editor/nodeDefinitions/sharepoint.py b/modules/nodeCatalog/nodeDefinitions/sharepoint.py similarity index 99% rename from modules/workflowAutomation/editor/nodeDefinitions/sharepoint.py rename to modules/nodeCatalog/nodeDefinitions/sharepoint.py index db48d8db..ae56f9a6 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/sharepoint.py +++ b/modules/nodeCatalog/nodeDefinitions/sharepoint.py @@ -3,7 +3,7 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.ai import ( +from modules.nodeCatalog.nodeDefinitions.ai import ( ACTION_RESULT_DATA_PICK_OPTIONS, DOCUMENT_LIST_DATA_PICK_OPTIONS, ) diff --git a/modules/workflowAutomation/editor/nodeDefinitions/triggers.py b/modules/nodeCatalog/nodeDefinitions/triggers.py similarity index 96% rename from modules/workflowAutomation/editor/nodeDefinitions/triggers.py rename to modules/nodeCatalog/nodeDefinitions/triggers.py index 0ae34ff2..deeae7a0 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/triggers.py +++ b/modules/nodeCatalog/nodeDefinitions/triggers.py @@ -3,7 +3,7 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS +from modules.nodeCatalog.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS TRIGGER_NODES = [ { diff --git a/modules/workflowAutomation/editor/nodeDefinitions/trustee.py b/modules/nodeCatalog/nodeDefinitions/trustee.py similarity index 98% rename from modules/workflowAutomation/editor/nodeDefinitions/trustee.py rename to modules/nodeCatalog/nodeDefinitions/trustee.py index a8c390a8..b0521696 100644 --- a/modules/workflowAutomation/editor/nodeDefinitions/trustee.py +++ b/modules/nodeCatalog/nodeDefinitions/trustee.py @@ -3,7 +3,7 @@ from modules.shared.i18nRegistry import t -from modules.workflowAutomation.editor.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS +from modules.nodeCatalog.nodeDefinitions.ai import ACTION_RESULT_DATA_PICK_OPTIONS # Typed FeatureInstance binding (replaces legacy `string, hidden`). # - type uses the discriminator notation `FeatureInstanceRef[]` so the diff --git a/modules/workflowAutomation/editor/portTypes.py b/modules/nodeCatalog/portTypes.py similarity index 99% rename from modules/workflowAutomation/editor/portTypes.py rename to modules/nodeCatalog/portTypes.py index 6246896e..aa8f4385 100644 --- a/modules/workflowAutomation/editor/portTypes.py +++ b/modules/nodeCatalog/portTypes.py @@ -418,7 +418,7 @@ def deriveFormPayloadSchemaFromParam( - Group-fields: ``type == "group"`` recursed via ``fields``. - List[str]: each string is taken as a leaf path key (used for ``filterContext.keys``). """ - from modules.workflowAutomation.editor.nodeDefinitions.input import FORM_FIELD_TYPES + from modules.nodeCatalog.nodeDefinitions.input import FORM_FIELD_TYPES _FORM_TYPE_TO_PORT: Dict[str, str] = {f["id"]: f["portType"] for f in FORM_FIELD_TYPES} fields_param = (node.get("parameters") or {}).get(param_key) diff --git a/modules/routes/routeWorkflowAutomation.py b/modules/routes/routeWorkflowAutomation.py index ee5d4ac1..81c009fb 100644 --- a/modules/routes/routeWorkflowAutomation.py +++ b/modules/routes/routeWorkflowAutomation.py @@ -12,22 +12,32 @@ RBAC model: - isPlatformAdmin bypasses all checks """ +import asyncio +import json import logging +import math +import time import uuid from typing import Optional, List, Dict, Any from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query, Request +from fastapi.responses import JSONResponse, Response, StreamingResponse from slowapi import Limiter from slowapi.util import get_remote_address from modules.auth.authentication import getRequestContext, RequestContext from modules.connectors.connectorDbPostgre import DatabaseConnector +from modules.datamodels.datamodelPagination import PaginationParams, PaginationMetadata, normalize_pagination_dict from modules.datamodels.datamodelWorkflowAutomation import ( AutoWorkflow, AutoVersion, AutoRun, AutoStepLog, AutoTask, ) +from modules.dbHelpers.paginationHelpers import applyFiltersAndSort +from modules.interfaces.interfaceDbApp import getRootInterface from modules.shared.i18nRegistry import apiRouteContext, resolveText -from modules.shared.workflowAutomationHelpers import ( +from modules.workflowAutomation.helpers import ( _getWorkflowAutomationDb, + _getUserMandateIds, + _isUserMandateAdmin, _validateWorkflowAccess, _scopedWorkflowFilter, _scopedRunFilter, @@ -736,7 +746,7 @@ def _importWorkflow( if not context.user: raise HTTPException(status_code=401, detail=routeApiMsg("Authentication required")) - from modules.workflowAutomation.editor._workflowFileSchema import WorkflowFileSchemaError + from modules.nodeCatalog._workflowFileSchema import WorkflowFileSchemaError mandateId = body.get("mandateId") if isinstance(body, dict) else None userId = str(context.user.id) @@ -799,7 +809,7 @@ def _exportWorkflow( if not context.user: raise HTTPException(status_code=401, detail=routeApiMsg("Authentication required")) - from modules.workflowAutomation.editor._workflowFileSchema import buildFileName + from modules.nodeCatalog._workflowFileSchema import buildFileName db = _getWorkflowAutomationDb() try: @@ -909,7 +919,7 @@ def _getFeatureInstanceOptions( targetMandateIds = userMandateIds if not context.isPlatformAdmin else [] if context.isPlatformAdmin: try: - from modules.datamodels.datamodelMandate import Mandate + from modules.datamodels.datamodelUam import Mandate mandates = rootInterface.db.getRecordset(Mandate) or [] targetMandateIds = [str(m.get("id") if isinstance(m, dict) else getattr(m, "id", "")) for m in mandates] except Exception: @@ -1038,7 +1048,7 @@ async def _getRunStream( else: raise HTTPException(status_code=403, detail=routeApiMsg("Access denied")) - from modules.serviceCenter.core.serviceStreaming.eventManager import get_event_manager + from modules.shared.eventManager import get_event_manager sseEventManager = get_event_manager() queueId = f"run-trace-{runId}" sseEventManager.create_queue(queueId) diff --git a/modules/serviceCenter/core/serviceStreaming/__init__.py b/modules/serviceCenter/core/serviceStreaming/__init__.py index ae7f582b..18a34f4e 100644 --- a/modules/serviceCenter/core/serviceStreaming/__init__.py +++ b/modules/serviceCenter/core/serviceStreaming/__init__.py @@ -2,7 +2,6 @@ # All rights reserved. """Streaming core service for SSE event management.""" -from .eventManager import EventManager, get_event_manager from .mainServiceStreaming import StreamingService -__all__ = ["EventManager", "get_event_manager", "StreamingService"] +__all__ = ["StreamingService"] diff --git a/modules/serviceCenter/core/serviceStreaming/eventManager.py b/modules/serviceCenter/core/serviceStreaming/eventManager.py deleted file mode 100644 index 823dbda1..00000000 --- a/modules/serviceCenter/core/serviceStreaming/eventManager.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (c) 2025 Patrick Motsch -# All rights reserved. -"""Re-export shim — canonical source is modules.shared.eventManager.""" - -from modules.shared.eventManager import ( # noqa: F401 - EventManager, - get_event_manager, -) diff --git a/modules/serviceCenter/core/serviceStreaming/mainServiceStreaming.py b/modules/serviceCenter/core/serviceStreaming/mainServiceStreaming.py index ee534706..c6c7ddf7 100644 --- a/modules/serviceCenter/core/serviceStreaming/mainServiceStreaming.py +++ b/modules/serviceCenter/core/serviceStreaming/mainServiceStreaming.py @@ -8,7 +8,7 @@ Core service - not requested by features directly. import logging from typing import Any, Callable -from modules.serviceCenter.core.serviceStreaming.eventManager import EventManager, get_event_manager +from modules.shared.eventManager import EventManager, get_event_manager logger = logging.getLogger(__name__) diff --git a/modules/serviceCenter/services/serviceAgent/externalToolRegistry.py b/modules/serviceCenter/services/serviceAgent/externalToolRegistry.py new file mode 100644 index 00000000..cfa24a2b --- /dev/null +++ b/modules/serviceCenter/services/serviceAgent/externalToolRegistry.py @@ -0,0 +1,39 @@ +# Copyright (c) 2025 Patrick Motsch +# All rights reserved. +""" +External agent-tool provider registry. + +Allows higher-layer components (e.g. ``workflowAutomation``) to register their +agent tool definitions WITHOUT the agent service importing them. The agent +reads registered definitions by toolbox id during toolbox activation. + +This inverts the dependency: ``workflowAutomation -> serviceCenter`` (push at +boot), instead of ``serviceCenter -> workflowAutomation`` (pull). The agent +never imports workflowAutomation. + +Tool definition dicts are the same shape consumed by +``ToolRegistry.registerFromDefinition`` (``name``, ``description``, +``parameters``, optional ``handler`` / ``readOnly`` / ``toolSet`` ...). +""" + +import logging +from typing import Dict, List, Any + +logger = logging.getLogger(__name__) + +_externalTools: Dict[str, List[Dict[str, Any]]] = {} + + +def registerExternalTools(toolboxId: str, toolDefinitions: List[Dict[str, Any]]) -> None: + """Register agent tool definitions (with handlers) for a toolbox id.""" + _externalTools[toolboxId] = list(toolDefinitions or []) + logger.info( + "Registered %d external agent tool(s) for toolbox '%s'", + len(_externalTools[toolboxId]), + toolboxId, + ) + + +def getExternalTools(toolboxId: str) -> List[Dict[str, Any]]: + """Return registered tool definitions for a toolbox id (empty if none).""" + return _externalTools.get(toolboxId, []) diff --git a/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py b/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py index 6620f219..e0c57496 100644 --- a/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py +++ b/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py @@ -420,20 +420,21 @@ class AgentService: for tb in activeToolboxes: activeToolNames.update(tb.tools) + from modules.serviceCenter.services.serviceAgent.externalToolRegistry import getExternalTools + from modules.serviceCenter.services.serviceAgent.datamodelAgent import ToolDefinition for tb in activeToolboxes: - if tb.id == "workflow": - try: - from modules.serviceCenter.services.serviceAgent.workflowTools import getWorkflowToolDefinitions - from modules.serviceCenter.services.serviceAgent.datamodelAgent import ToolDefinition - wfDefs = getWorkflowToolDefinitions() - for rawDef in wfDefs: - handler = rawDef.get("handler") - defFields = {k: v for k, v in rawDef.items() if k != "handler"} - toolDef = ToolDefinition(**defFields) - registry.registerFromDefinition(toolDef, handler) - logger.info("Registered %d workflow tools from toolbox", len(wfDefs)) - except Exception as e: - logger.warning("Could not register workflow tools: %s", e) + extDefs = getExternalTools(tb.id) + if not extDefs: + continue + try: + for rawDef in extDefs: + handler = rawDef.get("handler") + defFields = {k: v for k, v in rawDef.items() if k != "handler"} + toolDef = ToolDefinition(**defFields) + registry.registerFromDefinition(toolDef, handler) + logger.info("Registered %d external tool(s) for toolbox '%s'", len(extDefs), tb.id) + except Exception as e: + logger.warning("Could not register external tools for toolbox '%s': %s", tb.id, e) inactiveToolNames = set() for tb in tbRegistry.getAllToolboxes(): diff --git a/modules/serviceHub/__init__.py b/modules/serviceHub/__init__.py deleted file mode 100644 index 14021394..00000000 --- a/modules/serviceHub/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Re-export shim — canonical source: modules.serviceCenter.serviceHub -from modules.serviceCenter.serviceHub import ( # noqa: F401 - PublicService, - ServiceHub, - Services, - getInterface, -) diff --git a/modules/system/i18nBootSync.py b/modules/system/i18nBootSync.py index 3a32bee2..820376b1 100644 --- a/modules/system/i18nBootSync.py +++ b/modules/system/i18nBootSync.py @@ -242,7 +242,7 @@ def _registerNodeLabels(): added += 1 try: - from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES + from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES for nd in STATIC_NODE_TYPES: _reg(_extractRegistrySourceText(nd.get("label")), "node.label") _reg(_extractRegistrySourceText(nd.get("description")), "node.desc") @@ -265,7 +265,7 @@ def _registerNodeLabels(): pass try: - from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG + from modules.nodeCatalog.portTypes import PORT_TYPE_CATALOG for schema in PORT_TYPE_CATALOG.values(): for field in getattr(schema, "fields", []) or []: desc = getattr(field, "description", None) diff --git a/modules/serviceCenter/services/serviceAgent/workflowTools.py b/modules/workflowAutomation/agentTools.py similarity index 99% rename from modules/serviceCenter/services/serviceAgent/workflowTools.py rename to modules/workflowAutomation/agentTools.py index c1d3bf1e..88ac3a05 100644 --- a/modules/serviceCenter/services/serviceAgent/workflowTools.py +++ b/modules/workflowAutomation/agentTools.py @@ -436,7 +436,7 @@ async def _listAvailableNodeTypes(params: Dict[str, Any], context: Any) -> ToolR """ name = "listAvailableNodeTypes" try: - from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES + from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES nodeTypes = [] for n in STATIC_NODE_TYPES: if not isinstance(n, dict): @@ -462,7 +462,7 @@ async def _describeNodeType(params: Dict[str, Any], context: Any) -> ToolResult: nodeType = params.get("nodeType") or params.get("id") if not nodeType: return _err(name, "nodeType required") - from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES + from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES target: Dict[str, Any] = {} for n in STATIC_NODE_TYPES: if isinstance(n, dict) and n.get("id") == nodeType: @@ -875,7 +875,7 @@ async def _exportWorkflowToFile(params: Dict[str, Any], context: Any) -> ToolRes envelope = iface.exportWorkflowToDict(workflowId) if envelope is None: return _err(name, f"Workflow {workflowId} not found") - from modules.workflowAutomation.editor._workflowFileSchema import buildFileName + from modules.nodeCatalog._workflowFileSchema import buildFileName return _ok(name, { "fileName": buildFileName(envelope.get("label", "workflow")), "envelope": envelope, diff --git a/modules/workflowAutomation/editor/adapterValidator.py b/modules/workflowAutomation/editor/adapterValidator.py index 77d16a91..6e430878 100644 --- a/modules/workflowAutomation/editor/adapterValidator.py +++ b/modules/workflowAutomation/editor/adapterValidator.py @@ -26,7 +26,7 @@ from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Dict, List, Mapping -from modules.workflowAutomation.editor.nodeAdapter import ( +from modules.nodeCatalog.nodeAdapter import ( NodeAdapter, _adapterFromLegacyNode, _isMethodBoundNode, diff --git a/modules/workflowAutomation/editor/conditionOperators.py b/modules/workflowAutomation/editor/conditionOperators.py index 3f67440f..e99defc1 100644 --- a/modules/workflowAutomation/editor/conditionOperators.py +++ b/modules/workflowAutomation/editor/conditionOperators.py @@ -8,7 +8,7 @@ import re from datetime import datetime from typing import Any, Dict, List, Optional, Tuple -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES from modules.shared.i18nRegistry import resolveText, t logger = logging.getLogger(__name__) diff --git a/modules/workflowAutomation/editor/nodeRegistry.py b/modules/workflowAutomation/editor/nodeRegistry.py index bbddd9f0..7a7ca1a9 100644 --- a/modules/workflowAutomation/editor/nodeRegistry.py +++ b/modules/workflowAutomation/editor/nodeRegistry.py @@ -9,10 +9,10 @@ import logging from typing import Dict, List, Any, Optional from modules.workflowAutomation.editor.conditionOperators import localize_operator_catalog -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES -from modules.workflowAutomation.editor.nodeDefinitions.input import FORM_FIELD_TYPES -from modules.workflowAutomation.editor.nodeAdapter import bindsActionFromLegacy -from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG, SYSTEM_VARIABLES +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.nodeDefinitions.input import FORM_FIELD_TYPES +from modules.nodeCatalog.nodeAdapter import bindsActionFromLegacy +from modules.nodeCatalog.portTypes import PORT_TYPE_CATALOG, SYSTEM_VARIABLES from modules.shared.i18nRegistry import normalizePrimaryLanguageTag, resolveText logger = logging.getLogger(__name__) diff --git a/modules/workflowAutomation/editor/switchOutput.py b/modules/workflowAutomation/editor/switchOutput.py index e7cc830b..b70c5eb1 100644 --- a/modules/workflowAutomation/editor/switchOutput.py +++ b/modules/workflowAutomation/editor/switchOutput.py @@ -7,7 +7,7 @@ import copy import re from typing import Any, Dict, List, Optional -from modules.workflowAutomation.editor.portTypes import unwrapTransit +from modules.nodeCatalog.portTypes import unwrapTransit _CONTEXT_FILTER_OPERATORS = frozenset({"contains_content"}) _BLOB_IMAGE_CHUNK_RE = re.compile(r"^\[image(?:\:([^\]]+))?\]$") diff --git a/modules/workflowAutomation/editor/upstreamPathsService.py b/modules/workflowAutomation/editor/upstreamPathsService.py index f3d2a6ab..3639b7b7 100644 --- a/modules/workflowAutomation/editor/upstreamPathsService.py +++ b/modules/workflowAutomation/editor/upstreamPathsService.py @@ -5,8 +5,8 @@ from __future__ import annotations from typing import Any, Dict, List, Set from modules.workflowAutomation.editor.conditionOperators import catalog_type_to_value_kind, resolve_value_kind -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES -from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG, PortSchema, parse_graph_defined_output_schema +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.portTypes import PORT_TYPE_CATALOG, PortSchema, parse_graph_defined_output_schema from modules.workflowAutomation.engine.graphUtils import buildConnectionMap, getLoopBodyNodeIds, getLoopDoneNodeIds _NODE_BY_TYPE = {n["id"]: n for n in STATIC_NODE_TYPES} diff --git a/modules/workflowAutomation/engine/executionEngine.py b/modules/workflowAutomation/engine/executionEngine.py index cbe572da..443de25d 100644 --- a/modules/workflowAutomation/engine/executionEngine.py +++ b/modules/workflowAutomation/engine/executionEngine.py @@ -29,8 +29,8 @@ from modules.workflowAutomation.engine.executors import ( PauseForHumanTaskError, PauseForEmailWaitError, ) -from modules.workflowAutomation.editor.portTypes import normalizeToSchema, wrapTransit, unwrapTransit -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.portTypes import normalizeToSchema, wrapTransit, unwrapTransit +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES from modules.datamodels.serviceExceptions import SubscriptionInactiveException as _SubscriptionInactiveException, BillingContextError as _BillingContextError from modules.workflowAutomation.engine.runFileLogger import ( RunFileLogger, diff --git a/modules/workflowAutomation/engine/executors/actionNodeExecutor.py b/modules/workflowAutomation/engine/executors/actionNodeExecutor.py index 41c88a5d..dc88c7ab 100644 --- a/modules/workflowAutomation/engine/executors/actionNodeExecutor.py +++ b/modules/workflowAutomation/engine/executors/actionNodeExecutor.py @@ -13,7 +13,7 @@ import re import time from typing import Any, Dict, Optional -from modules.workflowAutomation.editor.portTypes import ( +from modules.nodeCatalog.portTypes import ( _normalizeError, normalizeToSchema, ) @@ -35,6 +35,7 @@ def _attach_unified_presentation_data(out: Dict[str, Any], *, node_def: Dict[str """Ensure ``out[\"data\"]`` carries ``context.extractContent.presentation.v1`` for ``file.create``.""" if node_def.get("skipUnifiedPresentation"): return + node_type = node_def.get("type") or node_def.get("nodeType") data = out.get("data") if isinstance(data, dict) and data.get("kind") == PRESENTATION_KIND: return @@ -181,7 +182,7 @@ def _isUserConnectionId(val: Any) -> bool: def _getNodeDefinition(nodeType: str) -> Optional[Dict[str, Any]]: """Get node definition by type id.""" - from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES + from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES for node in STATIC_NODE_TYPES: if node.get("id") == nodeType: return node @@ -304,7 +305,7 @@ def _buildConnectionRefDict(connRef: str, chatService, services) -> Optional[Dic def _schemaCarriesConnectionProvenance(outputSchema: str) -> bool: """True iff the port schema declares ``carriesConnectionProvenance`` in the catalog.""" - from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG + from modules.nodeCatalog.portTypes import PORT_TYPE_CATALOG schema = PORT_TYPE_CATALOG.get(outputSchema) return bool(getattr(schema, "carriesConnectionProvenance", False)) @@ -430,7 +431,7 @@ def _resolveUpstreamPayload(nodeId: str, context: Dict[str, Any]) -> Any: the first ``connectionMap`` entry so ``injectUpstreamPayload`` (e.g. ``context.mergeContext`` after ``flow.loop``) still receives data. """ - from modules.workflowAutomation.editor.switchOutput import unwrap_transit_for_port + from modules.workflowAutomation.editor.switchOutput import unwrap_transit_for_port nodeOutputs = context.get("nodeOutputs") or {} connectionMap = context.get("connectionMap") or {} @@ -456,7 +457,7 @@ def _resolveUpstreamPayload(nodeId: str, context: Dict[str, Any]) -> Any: return unwrap_transit_for_port(upstream, src_out) - def _resolveBranchInputs(nodeId: str, context: Dict[str, Any]) -> Dict[int, Any]: +def _resolveBranchInputs(nodeId: str, context: Dict[str, Any]) -> Dict[int, Any]: """Return ``Dict[port_index → unwrapped upstream output]`` for every wired input port.""" from modules.workflowAutomation.editor.switchOutput import unwrap_transit_for_port src_map = (context.get("inputSources") or {}).get(nodeId) or {} diff --git a/modules/workflowAutomation/engine/executors/dataExecutor.py b/modules/workflowAutomation/engine/executors/dataExecutor.py index 3429e650..e22eda6f 100644 --- a/modules/workflowAutomation/engine/executors/dataExecutor.py +++ b/modules/workflowAutomation/engine/executors/dataExecutor.py @@ -4,7 +4,7 @@ import logging from typing import Any, Dict -from modules.workflowAutomation.editor.portTypes import unwrapTransit, wrapTransit +from modules.nodeCatalog.portTypes import unwrapTransit, wrapTransit logger = logging.getLogger(__name__) diff --git a/modules/workflowAutomation/engine/executors/flowExecutor.py b/modules/workflowAutomation/engine/executors/flowExecutor.py index f107a580..7a296204 100644 --- a/modules/workflowAutomation/engine/executors/flowExecutor.py +++ b/modules/workflowAutomation/engine/executors/flowExecutor.py @@ -6,7 +6,7 @@ from datetime import datetime from typing import Any, Dict, List, Optional from modules.workflowAutomation.editor.conditionOperators import apply_condition_operator, resolve_value_kind -from modules.workflowAutomation.editor.portTypes import wrapTransit, unwrapTransit +from modules.nodeCatalog.portTypes import wrapTransit, unwrapTransit logger = logging.getLogger(__name__) diff --git a/modules/workflowAutomation/engine/graphUtils.py b/modules/workflowAutomation/engine/graphUtils.py index 946faafa..c6a4b5cd 100644 --- a/modules/workflowAutomation/engine/graphUtils.py +++ b/modules/workflowAutomation/engine/graphUtils.py @@ -91,7 +91,7 @@ def getLoopPrimaryInputSource( ) -> Optional[Tuple[str, int]]: """Pick the inbound edge for ``flow.loop`` when several wires hit the same input (0). - The Schleifen-Rücklauf vom Rumpf und der „normale" Vorgänger enden auf demselben Port; + The Schleifen-Rücklauf vom Rumpf und der „normale“ Vorgänger enden auf demselben Port; für die Datenzusammenführung (Fertig-Ausgang, Logs) zählt der Vorgänger **außerhalb** des Rumpfes. """ incoming = connectionMap.get(loop_node_id, []) @@ -209,7 +209,7 @@ def parse_graph_defined_schema(node: Dict[str, Any], parameter_key: str) -> Opti Build a JSON-serializable port schema dict from graph parameters (e.g. form ``fields``). Used by tooling and future API surfaces; mirrors ``parse_graph_defined_output_schema`` logic. """ - from modules.workflowAutomation.editor.portTypes import deriveFormPayloadSchemaFromParam + from modules.nodeCatalog.portTypes import deriveFormPayloadSchemaFromParam sch = deriveFormPayloadSchemaFromParam(node, parameter_key) if sch is None: @@ -227,8 +227,8 @@ def _checkPortCompatibility( """ Hard typed-port check: incompatible connections become validation errors. """ - from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES - from modules.workflowAutomation.editor.portTypes import resolve_output_schema_name + from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES + from modules.nodeCatalog.portTypes import resolve_output_schema_name nodeDefMap = {n["id"]: n for n in STATIC_NODE_TYPES} nodeById = {n["id"]: n for n in nodes if n.get("id")} @@ -481,7 +481,7 @@ def resolveParameterReferences( ) if value.get("type") == "system": variable = value.get("variable", "") - from modules.workflowAutomation.editor.portTypes import resolveSystemVariable + from modules.nodeCatalog.portTypes import resolveSystemVariable return resolveSystemVariable(variable, nodeOutputs.get("_context", {})) return { k: resolveParameterReferences( @@ -576,7 +576,7 @@ def extract_wired_document_list(inp: Any) -> Optional[Dict[str, Any]]: """ if inp is None: return None - from modules.workflowAutomation.editor.portTypes import ( + from modules.nodeCatalog.portTypes import ( unwrapTransit, _coerce_document_list_upload_fields, _file_record_to_document, diff --git a/modules/workflowAutomation/engine/pickNotPushMigration.py b/modules/workflowAutomation/engine/pickNotPushMigration.py index 1b3d9249..78bd63c4 100644 --- a/modules/workflowAutomation/engine/pickNotPushMigration.py +++ b/modules/workflowAutomation/engine/pickNotPushMigration.py @@ -16,8 +16,8 @@ import copy import logging from typing import Any, Dict, List, Optional -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES -from modules.workflowAutomation.editor.portTypes import ( +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.portTypes import ( PRIMARY_TEXT_HANDOVER_REF_PATH, resolve_output_schema_name, ) diff --git a/modules/shared/workflowAutomationHelpers.py b/modules/workflowAutomation/helpers.py similarity index 99% rename from modules/shared/workflowAutomationHelpers.py rename to modules/workflowAutomation/helpers.py index 4813c087..21471e6e 100644 --- a/modules/shared/workflowAutomationHelpers.py +++ b/modules/workflowAutomation/helpers.py @@ -202,8 +202,9 @@ def _validateWorkflowAccess( if action == "execute": targetInstanceId = workflow.get("targetFeatureInstanceId") if targetInstanceId: - from modules.interfaces.interfaceFeatureAccess import _hasFeatureAccess - if _hasFeatureAccess(userId, targetInstanceId): + from modules.interfaces.interfaceDbApp import getRootInterface + access = getRootInterface().getFeatureAccess(userId, targetInstanceId) + if access and access.get("enabled"): return adminMandateIds = _getAdminMandateIds(userId, [wfMandateId]) diff --git a/modules/workflowAutomation/mainWorkflowAutomation.py b/modules/workflowAutomation/mainWorkflowAutomation.py index 754d77b5..20c1d4fb 100644 --- a/modules/workflowAutomation/mainWorkflowAutomation.py +++ b/modules/workflowAutomation/mainWorkflowAutomation.py @@ -226,9 +226,24 @@ def _migrateRbacNamespace() -> None: logger.warning(f"RBAC namespace migration failed (non-critical): {e}") +def _registerAgentTools() -> None: + """Push workflow agent tools into the agent's external tool registry. + + Inverts the dependency: workflowAutomation -> serviceCenter (push at boot), + so the agent service never imports workflowAutomation to obtain its tools. + """ + try: + from modules.serviceCenter.services.serviceAgent.externalToolRegistry import registerExternalTools + from modules.workflowAutomation.agentTools import getWorkflowToolDefinitions, TOOLBOX_ID + registerExternalTools(TOOLBOX_ID, getWorkflowToolDefinitions()) + except Exception as e: + logger.warning(f"Could not register workflow agent tools (non-critical): {e}") + + def onBootstrap() -> None: """Seed system workflow templates and sync feature template workflows on boot.""" _migrateRbacNamespace() + _registerAgentTools() from modules.datamodels.datamodelWorkflowAutomation import GRAPHICAL_EDITOR_DATABASE, AutoWorkflow from modules.connectors.connectorDbPostgre import DatabaseConnector diff --git a/modules/workflows/automation2/__init__.py b/modules/workflows/automation2/__init__.py deleted file mode 100644 index 28ce2eea..00000000 --- a/modules/workflows/automation2/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2025 Patrick Motsch -# Re-export shim: modules moved to modules.workflowAutomation.engine -# This file preserves backwards compatibility for existing imports. - -from modules.workflowAutomation.engine.executionEngine import * # noqa: F401,F403 -from modules.workflowAutomation.engine.graphUtils import * # noqa: F401,F403 -from modules.workflowAutomation.engine.runEnvelope import * # noqa: F401,F403 -from modules.workflowAutomation.engine.scheduleCron import * # noqa: F401,F403 -from modules.workflowAutomation.engine.runFileLogger import * # noqa: F401,F403 -from modules.workflowAutomation.engine.pickNotPushMigration import * # noqa: F401,F403 -from modules.workflowAutomation.engine.featureInstanceRefMigration import * # noqa: F401,F403 -from modules.workflowAutomation.engine.workflowArtifactVisibility import * # noqa: F401,F403 -from modules.workflowAutomation.engine.clickupTaskUpdateMerge import * # noqa: F401,F403 diff --git a/modules/workflows/automation2/executors/__init__.py b/modules/workflows/automation2/executors/__init__.py deleted file mode 100644 index 1c2b18d4..00000000 --- a/modules/workflows/automation2/executors/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2025 Patrick Motsch -# Re-export shim: executors moved to modules.workflowAutomation.engine.executors -# This file preserves backwards compatibility for existing imports. - -from modules.workflowAutomation.engine.executors import ( # noqa: F401 - TriggerExecutor, - FlowExecutor, - ActionNodeExecutor, - InputExecutor, - DataExecutor, - PauseForHumanTaskError, - PauseForEmailWaitError, -) - -__all__ = [ - "TriggerExecutor", - "FlowExecutor", - "ActionNodeExecutor", - "InputExecutor", - "DataExecutor", - "PauseForHumanTaskError", - "PauseForEmailWaitError", -] diff --git a/modules/workflows/methods/_actionSignatureValidator.py b/modules/workflows/methods/_actionSignatureValidator.py index aeeb49c1..ce43ee7b 100644 --- a/modules/workflows/methods/_actionSignatureValidator.py +++ b/modules/workflows/methods/_actionSignatureValidator.py @@ -25,10 +25,10 @@ from modules.datamodels.datamodelWorkflowActions import ( WorkflowActionDefinition, WorkflowActionParameter, ) -from modules.workflowAutomation.editor.portTypes import ( +from modules.datamodels.datamodelPortTypes import ( PORT_TYPE_CATALOG, PRIMITIVE_TYPES, - _stripContainer, + stripContainer as _stripContainer, ) logger = logging.getLogger(__name__) diff --git a/modules/workflows/methods/methodBase.py b/modules/workflows/methods/methodBase.py index 5ab10077..abc7b9c0 100644 --- a/modules/workflows/methods/methodBase.py +++ b/modules/workflows/methods/methodBase.py @@ -240,7 +240,7 @@ class MethodBase: runtime structural validation is handled by the workflow engine / port-schema layer, not at the action-call boundary. """ - from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG + from modules.datamodels.datamodelPortTypes import PORT_TYPE_CATALOG if expectedType in PORT_TYPE_CATALOG: return value diff --git a/modules/workflows/methods/methodContext/actions/setContext.py b/modules/workflows/methods/methodContext/actions/setContext.py index 62435e38..58925f9e 100644 --- a/modules/workflows/methods/methodContext/actions/setContext.py +++ b/modules/workflows/methods/methodContext/actions/setContext.py @@ -320,18 +320,15 @@ def _pause_for_human_tasks( ) task_id = str((task or {}).get("id") or "") ordered_ids = [n.get("id") for n in (run_context.get("_orderedNodes") or []) if n.get("id")] - from modules.workflowAutomation.engine.runFileLogger import merge_persisted_run_context - _pause_ctx = merge_persisted_run_context( - iface, - run_id, - { - "connectionMap": run_context.get("connectionMap"), - "inputSources": run_context.get("inputSources"), - "orderedNodeIds": ordered_ids, - "pauseReason": "contextAssignment", - }, - ) + prev_ctx = dict((iface.getRun(run_id) or {}).get("context") or {}) + _pause_ctx = { + **prev_ctx, + "connectionMap": run_context.get("connectionMap"), + "inputSources": run_context.get("inputSources"), + "orderedNodeIds": ordered_ids, + "pauseReason": "contextAssignment", + } iface.updateRun( run_id, status="paused", diff --git a/modules/workflows/processing/core/actionExecutor.py b/modules/workflows/processing/core/actionExecutor.py index 1a162922..3156aa4b 100644 --- a/modules/workflows/processing/core/actionExecutor.py +++ b/modules/workflows/processing/core/actionExecutor.py @@ -9,7 +9,7 @@ from typing import Dict, Any, List from modules.datamodels.datamodelChat import ActionResult, ActionItem, TaskStep from modules.datamodels.datamodelChat import ChatWorkflow from modules.workflows.processing.shared.methodDiscovery import methods -from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.workflowState import checkWorkflowStopped from modules.workflows.processing.shared.parameterValidation import ( InvalidActionParameterError, validateAndCoerceParameters, ) diff --git a/modules/workflows/processing/core/messageCreator.py b/modules/workflows/processing/core/messageCreator.py index cb8e344f..00aebc20 100644 --- a/modules/workflows/processing/core/messageCreator.py +++ b/modules/workflows/processing/core/messageCreator.py @@ -8,7 +8,7 @@ import re from typing import Dict, Any, Optional, List from modules.datamodels.datamodelChat import TaskPlan, TaskStep, ActionResult, ReviewResult from modules.datamodels.datamodelChat import ChatWorkflow -from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.workflowState import checkWorkflowStopped logger = logging.getLogger(__name__) diff --git a/modules/workflows/processing/core/taskPlanner.py b/modules/workflows/processing/core/taskPlanner.py index 233488fe..8401c2a3 100644 --- a/modules/workflows/processing/core/taskPlanner.py +++ b/modules/workflows/processing/core/taskPlanner.py @@ -10,7 +10,7 @@ from modules.datamodels.datamodelChat import TaskStep, TaskContext, TaskPlan, Wo from modules.workflows.processing.shared.promptGenerationTaskplan import ( generateTaskPlanningPrompt ) -from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.workflowState import checkWorkflowStopped logger = logging.getLogger(__name__) diff --git a/modules/workflows/processing/modes/modeAutomation.py b/modules/workflows/processing/modes/modeAutomation.py index f48d509e..229bed5b 100644 --- a/modules/workflows/processing/modes/modeAutomation.py +++ b/modules/workflows/processing/modes/modeAutomation.py @@ -13,7 +13,7 @@ from modules.datamodels.datamodelChat import ( ) from modules.datamodels.datamodelChat import ChatWorkflow from modules.workflows.processing.modes.modeBase import BaseMode -from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.workflowState import checkWorkflowStopped from modules.shared.timeUtils import parseTimestamp logger = logging.getLogger(__name__) diff --git a/modules/workflows/processing/modes/modeDynamic.py b/modules/workflows/processing/modes/modeDynamic.py index b31568a2..045835fa 100644 --- a/modules/workflows/processing/modes/modeDynamic.py +++ b/modules/workflows/processing/modes/modeDynamic.py @@ -15,7 +15,7 @@ from modules.datamodels.datamodelChat import ( ) from modules.datamodels.datamodelChat import ChatWorkflow from modules.workflows.processing.modes.modeBase import BaseMode -from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.workflowState import checkWorkflowStopped from modules.shared.timeUtils import parseTimestamp from modules.workflows.processing.shared.executionState import TaskExecutionState, shouldContinue from modules.workflows.processing.shared.promptGenerationActionsDynamic import ( diff --git a/modules/workflows/processing/shared/parameterValidation.py b/modules/workflows/processing/shared/parameterValidation.py index ea182212..f8045b28 100644 --- a/modules/workflows/processing/shared/parameterValidation.py +++ b/modules/workflows/processing/shared/parameterValidation.py @@ -64,7 +64,7 @@ def _isRefSchema(typeStr: str) -> bool: """ if not typeStr or not typeStr.endswith("Ref"): return False - from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG + from modules.datamodels.datamodelPortTypes import PORT_TYPE_CATALOG schema = PORT_TYPE_CATALOG.get(typeStr) if schema is None: return False diff --git a/modules/workflows/processing/shared/stateTools.py b/modules/workflows/processing/shared/stateTools.py deleted file mode 100644 index c1614b69..00000000 --- a/modules/workflows/processing/shared/stateTools.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2025 Patrick Motsch -# All rights reserved. -""" -State Tools -Re-exports from modules.shared.workflowState for backward compatibility. -""" - -from modules.shared.workflowState import checkWorkflowStopped, WorkflowStoppedException - -__all__ = ["checkWorkflowStopped", "WorkflowStoppedException"] diff --git a/modules/workflows/processing/workflowProcessor.py b/modules/workflows/processing/workflowProcessor.py index 7f63fc62..d6fa00f0 100644 --- a/modules/workflows/processing/workflowProcessor.py +++ b/modules/workflows/processing/workflowProcessor.py @@ -14,7 +14,7 @@ from modules.datamodels.datamodelChat import ChatWorkflow, WorkflowModeEnum from modules.workflows.processing.modes.modeBase import BaseMode from modules.workflows.processing.modes.modeDynamic import DynamicMode from modules.workflows.processing.modes.modeAutomation import AutomationMode -from modules.workflows.processing.shared.stateTools import checkWorkflowStopped +from modules.shared.workflowState import checkWorkflowStopped from modules.datamodels.datamodelAi import OperationTypeEnum, PriorityEnum, ProcessingModeEnum, AiCallOptions, AiCallRequest from modules.shared.jsonUtils import extractJsonString, repairBrokenJson, parseJsonWithModel from modules.datamodels.datamodelWorkflow import UnderstandingResult diff --git a/modules/workflows/scheduler/__init__.py b/modules/workflows/scheduler/__init__.py deleted file mode 100644 index 4e814ab5..00000000 --- a/modules/workflows/scheduler/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2025 Patrick Motsch -# Re-export shim — real implementation moved to modules.workflowAutomation.scheduler -from modules.workflowAutomation.scheduler.mainScheduler import ( - WorkflowScheduler, - start, - stop, - syncNow, - setMainLoop, - notifyRunFailed, - setOnRunFailedCallback, -) diff --git a/modules/workflows/workflowManager.py b/modules/workflows/workflowManager.py index 379283b8..e983a139 100644 --- a/modules/workflows/workflowManager.py +++ b/modules/workflows/workflowManager.py @@ -14,7 +14,7 @@ from modules.datamodels.datamodelChat import ( ) from modules.datamodels.datamodelChat import TaskContext from modules.workflows.processing.workflowProcessor import WorkflowProcessor -from modules.workflows.processing.shared.stateTools import WorkflowStoppedException, checkWorkflowStopped +from modules.shared.workflowState import WorkflowStoppedException, checkWorkflowStopped logger = logging.getLogger(__name__) diff --git a/scripts/script_migrate_feature_instance_refs.py b/scripts/script_migrate_feature_instance_refs.py index 40f723c1..8af55a6c 100644 --- a/scripts/script_migrate_feature_instance_refs.py +++ b/scripts/script_migrate_feature_instance_refs.py @@ -56,7 +56,7 @@ import psycopg2 # noqa: E402 from psycopg2.extras import Json, RealDictCursor # noqa: E402 from modules.shared.configuration import APP_CONFIG # noqa: E402 -from modules.workflows.automation2.featureInstanceRefMigration import ( # noqa: E402 +from modules.workflowAutomation.engine.featureInstanceRefMigration import ( # noqa: E402 materializeFeatureInstanceRefs, ) diff --git a/tests/eval/runTrusteeBenchmark.py b/tests/eval/runTrusteeBenchmark.py index 3f298173..749bf996 100644 --- a/tests/eval/runTrusteeBenchmark.py +++ b/tests/eval/runTrusteeBenchmark.py @@ -415,7 +415,7 @@ def _bootstrapServices() -> Tuple[Any, str, str]: """ from modules.interfaces.interfaceDbApp import getRootInterface from modules.datamodels.datamodelUam import Mandate - from modules.serviceHub import getInterface as getServices + from modules.serviceCenter.serviceHub import getInterface as getServices rootInterface = getRootInterface() user = rootInterface.currentUser diff --git a/tests/functional/test01_ai_model_selection.py b/tests/functional/test01_ai_model_selection.py index c6a588c9..7c69b927 100644 --- a/tests/functional/test01_ai_model_selection.py +++ b/tests/functional/test01_ai_model_selection.py @@ -19,7 +19,7 @@ _gateway_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ". if _gateway_path not in sys.path: sys.path.insert(0, _gateway_path) -from modules.serviceHub import getInterface as getServices +from modules.serviceCenter.serviceHub import getInterface as getServices from modules.datamodels.datamodelAi import ( AiCallOptions, AiCallRequest, diff --git a/tests/functional/test02_ai_models.py b/tests/functional/test02_ai_models.py index 0a0948cf..32aeed80 100644 --- a/tests/functional/test02_ai_models.py +++ b/tests/functional/test02_ai_models.py @@ -32,7 +32,7 @@ if _gateway_path not in sys.path: sys.path.insert(0, _gateway_path) # Import the service initialization -from modules.serviceHub import getInterface as getServices +from modules.serviceCenter.serviceHub import getInterface as getServices from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum from modules.datamodels.datamodelUam import User diff --git a/tests/functional/test03_ai_operations.py b/tests/functional/test03_ai_operations.py index 4df53c5d..835078f0 100644 --- a/tests/functional/test03_ai_operations.py +++ b/tests/functional/test03_ai_operations.py @@ -101,7 +101,7 @@ class MethodAiOperationsTester: interfaceDbChat = interfaceDbChat.getInterface(self.testUser) # Import and initialize services - from modules.serviceHub import getInterface as getServices + from modules.serviceCenter.serviceHub import getInterface as getServices # Get services first self.services = getServices(self.testUser, None) diff --git a/tests/functional/test04_ai_behavior.py b/tests/functional/test04_ai_behavior.py index 953b52a3..7845733a 100644 --- a/tests/functional/test04_ai_behavior.py +++ b/tests/functional/test04_ai_behavior.py @@ -17,7 +17,7 @@ if _gateway_path not in sys.path: sys.path.insert(0, _gateway_path) # Import the service initialization -from modules.serviceHub import getInterface as getServices +from modules.serviceCenter.serviceHub import getInterface as getServices from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum from modules.datamodels.datamodelUam import User from modules.datamodels.datamodelWorkflow import AiResponse diff --git a/tests/unit/graphicalEditor/test_adapter_validator.py b/tests/unit/graphicalEditor/test_adapter_validator.py index 605251c6..ad507daa 100644 --- a/tests/unit/graphicalEditor/test_adapter_validator.py +++ b/tests/unit/graphicalEditor/test_adapter_validator.py @@ -34,7 +34,7 @@ from modules.workflowAutomation.editor.adapterValidator import ( _validateAdapterAgainstAction, _validateAllAdapters, ) -from modules.workflowAutomation.editor.nodeAdapter import ( +from modules.nodeCatalog.nodeAdapter import ( NodeAdapter, UserParamMapping, ) @@ -254,7 +254,7 @@ def _ensureOptionalDeps(): _LIVE_METHODS = [ ("modules.features.trustee.workflows.methodTrustee.methodTrustee", "MethodTrustee", "trustee"), - ("modules.workflows.methods.methodRedmine.methodRedmine", "MethodRedmine", "redmine"), + ("modules.features.redmine.workflows.methodRedmine.methodRedmine", "MethodRedmine", "redmine"), ("modules.workflows.methods.methodSharepoint.methodSharepoint", "MethodSharepoint", "sharepoint"), ("modules.workflows.methods.methodOutlook.methodOutlook", "MethodOutlook", "outlook"), ("modules.workflows.methods.methodAi.methodAi", "MethodAi", "ai"), @@ -334,7 +334,7 @@ def test_staticNodesHaveNoDriftAgainstLiveMethods(): History: wiki/c-work/4-done/2026-04-adapter-drift-cleanup.md """ - from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES + from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES instances = _instantiateLiveMethods() if not instances: diff --git a/tests/unit/graphicalEditor/test_featureInstanceRef_node_definitions.py b/tests/unit/graphicalEditor/test_featureInstanceRef_node_definitions.py index 525faa4a..e81a0a4f 100644 --- a/tests/unit/graphicalEditor/test_featureInstanceRef_node_definitions.py +++ b/tests/unit/graphicalEditor/test_featureInstanceRef_node_definitions.py @@ -24,8 +24,8 @@ from __future__ import annotations import pytest -from modules.workflowAutomation.editor.nodeDefinitions.redmine import REDMINE_NODES -from modules.workflowAutomation.editor.nodeDefinitions.trustee import TRUSTEE_NODES +from modules.nodeCatalog.nodeDefinitions.redmine import REDMINE_NODES +from modules.nodeCatalog.nodeDefinitions.trustee import TRUSTEE_NODES def _featureInstanceParam(node: dict) -> dict | None: diff --git a/tests/unit/graphicalEditor/test_node_adapter.py b/tests/unit/graphicalEditor/test_node_adapter.py index 3c18f438..634f76d2 100644 --- a/tests/unit/graphicalEditor/test_node_adapter.py +++ b/tests/unit/graphicalEditor/test_node_adapter.py @@ -17,7 +17,7 @@ from __future__ import annotations import pytest -from modules.workflowAutomation.editor.nodeAdapter import ( +from modules.nodeCatalog.nodeAdapter import ( NodeAdapter, UserParamMapping, _adapterFromLegacyNode, diff --git a/tests/unit/graphicalEditor/test_portTypes_catalog.py b/tests/unit/graphicalEditor/test_portTypes_catalog.py index 0506be27..9e97d475 100644 --- a/tests/unit/graphicalEditor/test_portTypes_catalog.py +++ b/tests/unit/graphicalEditor/test_portTypes_catalog.py @@ -6,7 +6,7 @@ Catalog integrity + new Phase-1 schemas import pytest -from modules.workflowAutomation.editor.portTypes import ( +from modules.nodeCatalog.portTypes import ( PORT_TYPE_CATALOG, PRIMITIVE_TYPES, PortField, diff --git a/tests/unit/graphicalEditor/test_port_schema_recursive.py b/tests/unit/graphicalEditor/test_port_schema_recursive.py index 7884109e..cd32e461 100644 --- a/tests/unit/graphicalEditor/test_port_schema_recursive.py +++ b/tests/unit/graphicalEditor/test_port_schema_recursive.py @@ -1,7 +1,7 @@ # Copyright (c) 2025 Patrick Motsch """Port type catalog: nested provenance schemas (Typed Generic Handover).""" -from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG, _defaultForType +from modules.nodeCatalog.portTypes import PORT_TYPE_CATALOG, _defaultForType def test_connection_ref_in_catalog(): diff --git a/tests/unit/graphicalEditor/test_upstream_paths_and_graph_schema.py b/tests/unit/graphicalEditor/test_upstream_paths_and_graph_schema.py index 8e64367e..6c6ff2cc 100644 --- a/tests/unit/graphicalEditor/test_upstream_paths_and_graph_schema.py +++ b/tests/unit/graphicalEditor/test_upstream_paths_and_graph_schema.py @@ -1,7 +1,7 @@ # Copyright (c) 2025 Patrick Motsch from modules.workflowAutomation.editor.upstreamPathsService import compute_upstream_paths from modules.workflowAutomation.engine.graphUtils import parse_graph_defined_schema, validateGraph -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES def test_compute_upstream_paths_includes_form_dynamic_fields(): diff --git a/tests/unit/methods/test_action_signature_validator.py b/tests/unit/methods/test_action_signature_validator.py index fa4aa71f..a959989e 100644 --- a/tests/unit/methods/test_action_signature_validator.py +++ b/tests/unit/methods/test_action_signature_validator.py @@ -256,7 +256,7 @@ def _instantiateMethod(methodCls): @pytest.mark.parametrize("modulePath,className", [ ("modules.features.trustee.workflows.methodTrustee.methodTrustee", "MethodTrustee"), - ("modules.workflows.methods.methodRedmine.methodRedmine", "MethodRedmine"), + ("modules.features.redmine.workflows.methodRedmine.methodRedmine", "MethodRedmine"), ("modules.workflows.methods.methodSharepoint.methodSharepoint", "MethodSharepoint"), ("modules.workflows.methods.methodOutlook.methodOutlook", "MethodOutlook"), ("modules.workflows.methods.methodAi.methodAi", "MethodAi"), diff --git a/tests/unit/nodeDefinitions/test_trustee_schema_compliance.py b/tests/unit/nodeDefinitions/test_trustee_schema_compliance.py index 36038ee1..060d04a6 100644 --- a/tests/unit/nodeDefinitions/test_trustee_schema_compliance.py +++ b/tests/unit/nodeDefinitions/test_trustee_schema_compliance.py @@ -20,8 +20,8 @@ Verifies that: import inspect -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES -from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.portTypes import PORT_TYPE_CATALOG from modules.workflowAutomation.engine.executors import actionNodeExecutor as _actionExec from modules.workflowAutomation.engine.graphUtils import validateGraph diff --git a/tests/unit/nodeDefinitions/test_usesai_flag.py b/tests/unit/nodeDefinitions/test_usesai_flag.py index bf578fd0..caf07960 100644 --- a/tests/unit/nodeDefinitions/test_usesai_flag.py +++ b/tests/unit/nodeDefinitions/test_usesai_flag.py @@ -2,7 +2,7 @@ import pytest -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES def test_all_nodes_have_usesAi(): diff --git a/tests/unit/serviceAgent/test_workflow_tools_crud.py b/tests/unit/serviceAgent/test_workflow_tools_crud.py index b578b1de..41e56ab6 100644 --- a/tests/unit/serviceAgent/test_workflow_tools_crud.py +++ b/tests/unit/serviceAgent/test_workflow_tools_crud.py @@ -22,7 +22,7 @@ from typing import Any, Dict, Optional import pytest -from modules.serviceCenter.services.serviceAgent import workflowTools +from modules.workflowAutomation import agentTools as workflowTools from modules.serviceCenter.services.serviceAgent.datamodelAgent import ToolResult diff --git a/tests/unit/workflow/test_node_combinations.py b/tests/unit/workflow/test_node_combinations.py index 15159048..b4857a14 100644 --- a/tests/unit/workflow/test_node_combinations.py +++ b/tests/unit/workflow/test_node_combinations.py @@ -14,8 +14,8 @@ import json import pytest -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES -from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.portTypes import PORT_TYPE_CATALOG from modules.workflows.methods.methodContext.actions.extractContent import ( PRESENTATION_KIND, build_presentation_envelope_from_plain_text, diff --git a/tests/unit/workflow/test_phase3_context_node.py b/tests/unit/workflow/test_phase3_context_node.py index 49500bc2..5f113d5e 100644 --- a/tests/unit/workflow/test_phase3_context_node.py +++ b/tests/unit/workflow/test_phase3_context_node.py @@ -1,8 +1,8 @@ # Tests for Phase 3: context.extractContent node, port types, executor dispatch. import pytest -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES -from modules.workflowAutomation.editor.portTypes import PORT_TYPE_CATALOG +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.portTypes import PORT_TYPE_CATALOG from modules.workflowAutomation.engine.udmUpstreamShapes import ( _coerceConsolidateResultInput, _coerceUdmDocumentInput, diff --git a/tests/unit/workflow/test_phase4_workflow_nodes.py b/tests/unit/workflow/test_phase4_workflow_nodes.py index 24a29d1f..3ca0792d 100644 --- a/tests/unit/workflow/test_phase4_workflow_nodes.py +++ b/tests/unit/workflow/test_phase4_workflow_nodes.py @@ -1,7 +1,7 @@ # Tests for Phase 4: data.consolidate, ai.consolidate, flow.loop level/concurrency, flow.merge dynamic. import pytest -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES class TestNodeDefinitions: diff --git a/tests/unit/workflow/test_phase5_highvol.py b/tests/unit/workflow/test_phase5_highvol.py index 45079fb4..44c51d76 100644 --- a/tests/unit/workflow/test_phase5_highvol.py +++ b/tests/unit/workflow/test_phase5_highvol.py @@ -1,7 +1,7 @@ # Tests for Phase 5: Loop concurrency, StepLog batching, streaming aggregate. import pytest -from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES +from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES def test_loop_concurrency_param_default_1(): diff --git a/tests/unit/workflow/test_switch_filtered_output.py b/tests/unit/workflow/test_switch_filtered_output.py index 334a8e81..ee9271d9 100644 --- a/tests/unit/workflow/test_switch_filtered_output.py +++ b/tests/unit/workflow/test_switch_filtered_output.py @@ -3,7 +3,7 @@ import pytest -from modules.workflowAutomation.editor.portTypes import unwrapTransit, wrapTransit +from modules.nodeCatalog.portTypes import unwrapTransit, wrapTransit from modules.workflowAutomation.editor.switchOutput import ( build_switch_branch_payload, build_switch_combined_output, diff --git a/tests/unit/workflow/test_workflowFileSchema.py b/tests/unit/workflow/test_workflowFileSchema.py index e7109cbc..3eb0fb2c 100644 --- a/tests/unit/workflow/test_workflowFileSchema.py +++ b/tests/unit/workflow/test_workflowFileSchema.py @@ -4,7 +4,7 @@ import pytest -from modules.workflowAutomation.editor._workflowFileSchema import ( +from modules.nodeCatalog._workflowFileSchema import ( WORKFLOW_FILE_KIND, WORKFLOW_FILE_SCHEMA_VERSION, WorkflowFileSchemaError, diff --git a/tests/unit/workflows/test_automation2_graphUtils.py b/tests/unit/workflows/test_automation2_graphUtils.py index 0ee29412..179857c1 100644 --- a/tests/unit/workflows/test_automation2_graphUtils.py +++ b/tests/unit/workflows/test_automation2_graphUtils.py @@ -38,7 +38,7 @@ class TestValidateGraphStartNode: def test_switch_second_output_to_ai_prompt_ok(self): - from modules.workflowAutomation.editor.nodeDefinitions import STATIC_NODE_TYPES + from modules.nodeCatalog.nodeDefinitions import STATIC_NODE_TYPES node_type_ids = {n["id"] for n in STATIC_NODE_TYPES} graph = {