From ce612ffcfcb8cee9e8d2e84d1acb751583a583f6 Mon Sep 17 00:00:00 2001 From: ValueOn AG
]` 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 = {