import referencing fixes
All checks were successful
Deploy Plattform-Core (Int) / test (push) Successful in 1m0s
Deploy Plattform-Core (Int) / deploy (push) Successful in 9s

This commit is contained in:
ValueOn AG 2026-06-08 14:46:52 +02:00
parent 9be2d8aab5
commit ce612ffcfc
96 changed files with 249 additions and 217 deletions

2
app.py
View file

@ -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}")

View file

@ -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}")

View file

@ -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]

View file

@ -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,
)

View file

@ -0,0 +1,3 @@
# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""Feature-owned workflow methods for Redmine."""

View file

@ -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

View file

@ -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)

View file

@ -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.
"""

View file

@ -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,
)

View file

@ -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 = [
{

View file

@ -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."
),
},
{

View file

@ -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

View file

@ -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 = [
{

View file

@ -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 = [
{

View file

@ -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": [

View file

@ -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,
},
{

View file

@ -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 = [
{

View file

@ -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.

View file

@ -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,
)

View file

@ -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 = [
{

View file

@ -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[<code>]` so the

View file

@ -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)

View file

@ -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)

View file

@ -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"]

View file

@ -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,
)

View file

@ -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__)

View file

@ -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, [])

View file

@ -420,20 +420,21 @@ class AgentService:
for tb in activeToolboxes:
activeToolNames.update(tb.tools)
for tb in activeToolboxes:
if tb.id == "workflow":
try:
from modules.serviceCenter.services.serviceAgent.workflowTools import getWorkflowToolDefinitions
from modules.serviceCenter.services.serviceAgent.externalToolRegistry import getExternalTools
from modules.serviceCenter.services.serviceAgent.datamodelAgent import ToolDefinition
wfDefs = getWorkflowToolDefinitions()
for rawDef in wfDefs:
for tb in activeToolboxes:
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 workflow tools from toolbox", len(wfDefs))
logger.info("Registered %d external tool(s) for toolbox '%s'", len(extDefs), tb.id)
except Exception as e:
logger.warning("Could not register workflow tools: %s", e)
logger.warning("Could not register external tools for toolbox '%s': %s", tb.id, e)
inactiveToolNames = set()
for tb in tbRegistry.getAllToolboxes():

View file

@ -1,7 +0,0 @@
# Re-export shim — canonical source: modules.serviceCenter.serviceHub
from modules.serviceCenter.serviceHub import ( # noqa: F401
PublicService,
ServiceHub,
Services,
getInterface,
)

View file

@ -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)

View file

@ -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,

View file

@ -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,

View file

@ -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__)

View file

@ -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__)

View file

@ -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(?:\:([^\]]+))?\]$")

View file

@ -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}

View file

@ -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,

View file

@ -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))

View file

@ -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__)

View file

@ -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__)

View file

@ -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,

View file

@ -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,
)

View file

@ -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])

View file

@ -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

View file

@ -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

View file

@ -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",
]

View file

@ -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__)

View file

@ -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

View file

@ -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,
{
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",

View file

@ -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,
)

View file

@ -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__)

View file

@ -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__)

View file

@ -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__)

View file

@ -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 (

View file

@ -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

View file

@ -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"]

View file

@ -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

View file

@ -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,
)

View file

@ -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__)

View file

@ -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,
)

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -17,7 +17,7 @@ from __future__ import annotations
import pytest
from modules.workflowAutomation.editor.nodeAdapter import (
from modules.nodeCatalog.nodeAdapter import (
NodeAdapter,
UserParamMapping,
_adapterFromLegacyNode,

View file

@ -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,

View file

@ -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():

View file

@ -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():

View file

@ -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"),

View file

@ -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

View file

@ -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():

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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:

View file

@ -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():

View file

@ -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,

View file

@ -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,

View file

@ -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 = {