diff --git a/packages/@n8n/api-types/src/frontend-settings.ts b/packages/@n8n/api-types/src/frontend-settings.ts index 2e372fdff0a..a84def1f432 100644 --- a/packages/@n8n/api-types/src/frontend-settings.ts +++ b/packages/@n8n/api-types/src/frontend-settings.ts @@ -100,6 +100,7 @@ export interface FrontendSettings { executionTimeout: number; maxExecutionTimeout: number; workflowCallerPolicyDefaultOption: WorkflowSettings.CallerPolicy; + excludeNodes: string[]; oauthCallbackUrls: { oauth1: string; oauth2: string; diff --git a/packages/cli/src/services/__tests__/frontend.service.test.ts b/packages/cli/src/services/__tests__/frontend.service.test.ts index 6c8961c95fb..501f2b4b23c 100644 --- a/packages/cli/src/services/__tests__/frontend.service.test.ts +++ b/packages/cli/src/services/__tests__/frontend.service.test.ts @@ -276,6 +276,15 @@ describe('FrontendService', () => { ); }); + it('should expose excluded node types from NODES_EXCLUDE', async () => { + globalConfig.nodes.exclude = ['n8n-nodes-base.executeWorkflow']; + const { service } = createMockService(); + + const settings = await service.getSettings(); + + expect(settings.excludeNodes).toEqual(['n8n-nodes-base.executeWorkflow']); + }); + it('should enable the AI Gateway when configured and licensed', async () => { globalConfig.aiAssistant.baseUrl = 'https://ai-assistant.n8n.io'; globalConfig.aiGateway.enabled = true; diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index 2362a339c6d..abfdef913ae 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -11,6 +11,10 @@ import { BinaryDataConfig, InstanceSettings } from 'n8n-core'; import type { ICredentialType, INodeTypeBaseDescription, INodeTypeDescription } from 'n8n-workflow'; import path from 'path'; +import { AiUsageService } from './ai-usage.service'; +import { UrlService } from './url.service'; +import { WorkflowReviewPolicyService } from './workflow-review-policy.service'; + import config from '@/config'; import { inE2ETests, N8N_VERSION } from '@/constants'; import { isWorkflowReviewsFeatureAvailable } from '@/constants/workflow-reviews'; @@ -33,10 +37,6 @@ import { getWorkflowHistoryPruneTime, } from '@/workflows/workflow-history/workflow-history-helper'; -import { AiUsageService } from './ai-usage.service'; -import { UrlService } from './url.service'; -import { WorkflowReviewPolicyService } from './workflow-review-policy.service'; - const DYNAMIC_BANNER_FILTERS_CACHE_TTL = 30 * Time.seconds.toMilliseconds; /** @@ -220,6 +220,7 @@ export class FrontendService { executionTimeout: this.globalConfig.executions.timeout, maxExecutionTimeout: this.globalConfig.executions.maxTimeout, workflowCallerPolicyDefaultOption: this.globalConfig.workflows.callerPolicyDefaultOption, + excludeNodes: this.globalConfig.nodes.exclude, timezone: this.globalConfig.generic.timezone, urlBaseWebhook: this.urlService.getWebhookBaseUrl(), urlBaseEditor: instanceBaseUrl, diff --git a/packages/frontend/@n8n/stores/src/settings.store.test.ts b/packages/frontend/@n8n/stores/src/settings.store.test.ts index 770becffadc..d9a77e045db 100644 --- a/packages/frontend/@n8n/stores/src/settings.store.test.ts +++ b/packages/frontend/@n8n/stores/src/settings.store.test.ts @@ -146,6 +146,74 @@ describe('settings.store', () => { }); }); + describe('isExecuteWorkflowNodeExcluded', () => { + it('should return true when executeWorkflow is in excludeNodes', async () => { + getSettings.mockResolvedValueOnce({ + ...mockSettings, + excludeNodes: ['n8n-nodes-base.executeWorkflow'], + }); + + const settingsStore = useSettingsStore(); + await settingsStore.getSettings(); + + expect(settingsStore.isExecuteWorkflowNodeExcluded).toBe(true); + }); + + it('should return false when executeWorkflow is not excluded', async () => { + getSettings.mockResolvedValueOnce({ + ...mockSettings, + excludeNodes: ['n8n-nodes-base.executeCommand'], + }); + + const settingsStore = useSettingsStore(); + await settingsStore.getSettings(); + + expect(settingsStore.isExecuteWorkflowNodeExcluded).toBe(false); + }); + + it('should return false when only executeWorkflowTrigger is excluded', async () => { + getSettings.mockResolvedValueOnce({ + ...mockSettings, + excludeNodes: ['n8n-nodes-base.executeWorkflowTrigger'], + }); + + const settingsStore = useSettingsStore(); + await settingsStore.getSettings(); + + expect(settingsStore.isExecuteWorkflowNodeExcluded).toBe(false); + }); + }); + + describe('isSubworkflowConversionDisabled', () => { + it.each([ + [['n8n-nodes-base.executeWorkflow']], + [['n8n-nodes-base.executeWorkflowTrigger']], + [['n8n-nodes-base.executeWorkflow', 'n8n-nodes-base.executeWorkflowTrigger']], + ])('should return true when %j is excluded', async (excludeNodes) => { + getSettings.mockResolvedValueOnce({ + ...mockSettings, + excludeNodes, + }); + + const settingsStore = useSettingsStore(); + await settingsStore.getSettings(); + + expect(settingsStore.isSubworkflowConversionDisabled).toBe(true); + }); + + it('should return false when both sub-workflow nodes are available', async () => { + getSettings.mockResolvedValueOnce({ + ...mockSettings, + excludeNodes: ['n8n-nodes-base.executeCommand'], + }); + + const settingsStore = useSettingsStore(); + await settingsStore.getSettings(); + + expect(settingsStore.isSubworkflowConversionDisabled).toBe(false); + }); + }); + describe('isCrdtCollaborationEnabled', () => { it('should return true when collaboration.crdt is local', async () => { getSettings.mockResolvedValueOnce({ diff --git a/packages/frontend/@n8n/stores/src/settings.store.ts b/packages/frontend/@n8n/stores/src/settings.store.ts index 42b8b23cf1d..e5521bfea24 100644 --- a/packages/frontend/@n8n/stores/src/settings.store.ts +++ b/packages/frontend/@n8n/stores/src/settings.store.ts @@ -13,7 +13,12 @@ import * as moduleSettingsApi from '@n8n/rest-api-client/api/module-settings'; import * as settingsApi from '@n8n/rest-api-client/api/settings'; import { testHealthEndpoint } from '@n8n/rest-api-client/api/templates'; import Bowser from 'bowser'; -import type { IDataObject, WorkflowSettings } from 'n8n-workflow'; +import { + EXECUTE_WORKFLOW_NODE_TYPE, + EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, + type IDataObject, + type WorkflowSettings, +} from 'n8n-workflow'; import { defineStore } from 'pinia'; import { computed, ref } from 'vue'; @@ -258,6 +263,20 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => { () => settings.value.workflowCallerPolicyDefaultOption, ); + const isNodeTypeExcluded = (nodeType: string) => { + const excludeNodes = settings.value.excludeNodes; + return Array.isArray(excludeNodes) && excludeNodes.includes(nodeType); + }; + + const isExecuteWorkflowNodeExcluded = computed(() => + isNodeTypeExcluded(EXECUTE_WORKFLOW_NODE_TYPE), + ); + + const isSubworkflowConversionDisabled = computed( + () => + isExecuteWorkflowNodeExcluded.value || isNodeTypeExcluded(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE), + ); + const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []); const isCommunityPlan = computed(() => planName.value.toLowerCase() === 'community'); @@ -490,6 +509,8 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => { isMultiMain, isWorkerViewAvailable, workflowCallerPolicyDefaultOption, + isExecuteWorkflowNodeExcluded, + isSubworkflowConversionDisabled, permanentlyDismissedBanners, saveDataErrorExecution, saveDataSuccessExecution, diff --git a/packages/frontend/editor-ui/src/__tests__/defaults.ts b/packages/frontend/editor-ui/src/__tests__/defaults.ts index 03a129f0c14..951fbb3328f 100644 --- a/packages/frontend/editor-ui/src/__tests__/defaults.ts +++ b/packages/frontend/editor-ui/src/__tests__/defaults.ts @@ -134,6 +134,7 @@ export const defaultSettings: FrontendSettings = { maxSize: 0, }, workflowCallerPolicyDefaultOption: 'any', + excludeNodes: [], workflowTagsDisabled: false, workflowsAutosaveDisabled: false, variables: { diff --git a/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.test.ts b/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.test.ts index f29f5fe7caf..6f0c8bc68cd 100644 --- a/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.test.ts +++ b/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.test.ts @@ -126,6 +126,7 @@ vi.mock('vue-router', () => ({ import { useWorkflowExtraction } from '@/app/composables/useWorkflowExtraction'; import { RemoveNodeGroupCommand, UpdateNodeGroupCommand } from '@/app/models/history'; +import { useSettingsStore } from '@n8n/stores/settings.store'; function makeNode(name: string, position: [number, number] = [0, 0]): INodeUi { return { @@ -203,6 +204,17 @@ describe('useWorkflowExtraction', () => { expect(mockTelemetry.track).not.toHaveBeenCalled(); }); + it('does not start extraction when executeWorkflow is excluded', () => { + const settingsStore = useSettingsStore(); + vi.spyOn(settingsStore, 'isSubworkflowConversionDisabled', 'get').mockReturnValue(true); + + const { extractWorkflow } = useWorkflowExtraction(); + extractWorkflow(['id-A']); + + expect(mockTelemetry.track).not.toHaveBeenCalled(); + expect(mockUIStore.openModalWithData).not.toHaveBeenCalled(); + }); + it('includes attached sub-nodes when starting extraction', () => { const nodeA = makeNode('A', [0, 0]); const agentB = makeNode('Agent B', [200, 0]); diff --git a/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.ts b/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.ts index 82094772ede..a5b4f0acf51 100644 --- a/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.ts +++ b/packages/frontend/editor-ui/src/app/composables/useWorkflowExtraction.ts @@ -27,6 +27,7 @@ import { useSelectionValidation } from './useSelectionValidation'; import type { AddedNode, INodeUi, IWorkflowDb } from '@/Interface'; import type { WorkflowDataCreate } from '@n8n/rest-api-client/api/workflows'; import { useI18n } from '@n8n/i18n'; +import { useSettingsStore } from '@n8n/stores/settings.store'; import { PUSH_NODES_OFFSET } from '@/app/utils/nodeViewUtils'; import { useUIStore } from '@/app/stores/ui.store'; import { useNodeTypesStore } from '@/app/stores/nodeTypes.store'; @@ -47,6 +48,7 @@ export function useWorkflowExtraction() { const workflowsStore = useWorkflowsStore(); const workflowDocumentStore = injectWorkflowDocumentStore(); const nodeTypesStore = useNodeTypesStore(); + const settingsStore = useSettingsStore(); const toast = useToast(); const router = useRouter(); const historyStore = useHistoryStore(); @@ -689,7 +691,7 @@ export function useWorkflowExtraction() { * @param nodeIds the ids to be extracted from the current workflow into a sub-workflow */ function extractWorkflow(nodeIds: string[]) { - if (nodeIds.length === 0) return; + if (nodeIds.length === 0 || settingsStore.isSubworkflowConversionDisabled) return; const success = tryExtractNodesIntoSubworkflow(nodeIds); trackStartExtractWorkflow(nodeIds.length, success); diff --git a/packages/frontend/editor-ui/src/features/shared/contextMenu/composables/useContextMenu.test.ts b/packages/frontend/editor-ui/src/features/shared/contextMenu/composables/useContextMenu.test.ts index 2c34b19d37b..b7dd0a5cbf4 100644 --- a/packages/frontend/editor-ui/src/features/shared/contextMenu/composables/useContextMenu.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/contextMenu/composables/useContextMenu.test.ts @@ -23,6 +23,7 @@ import { useSourceControlStore } from '@/features/integrations/sourceControl.ee/ import { useUIStore } from '@/app/stores/ui.store'; import { useWorkflowsStore } from '@/app/stores/workflows.store'; import { useFocusedNodesStore } from '@/features/ai/assistant/focusedNodes.store'; +import { useSettingsStore } from '@n8n/stores/settings.store'; import { useWorkflowDocumentStore, createWorkflowDocumentId, @@ -184,6 +185,29 @@ describe('useContextMenu', () => { }); }); + describe('extract_sub_workflow gating', () => { + it('hides convert to sub-workflow when executeWorkflow is excluded', () => { + const settingsStore = useSettingsStore(); + vi.spyOn(settingsStore, 'isSubworkflowConversionDisabled', 'get').mockReturnValue(true); + + const { open, actions } = useContextMenu(); + open(mockEvent, { source: 'canvas', nodeIds: selectedNodes.map((n) => n.id) }); + + expect(actions.value.some((action) => action.id === 'extract_sub_workflow')).toBe(false); + }); + + it('hides convert to sub-workflow on a group target when executeWorkflow is excluded', () => { + const settingsStore = useSettingsStore(); + vi.spyOn(settingsStore, 'isSubworkflowConversionDisabled', 'get').mockReturnValue(true); + const group = workflowDocumentStore.createGroup([nodes[0].id, nodes[1].id], 'My group'); + + const { open, actions } = useContextMenu(); + open(mockEvent, { source: 'group', groupId: group.id, nodeIds: group.nodeIds }); + + expect(actions.value.some((action) => action.id === 'extract_sub_workflow')).toBe(false); + }); + }); + describe('group_nodes gating', () => { beforeEach(() => { // Connect the first two nodes so they form a groupable subgraph diff --git a/packages/frontend/editor-ui/src/features/shared/contextMenu/composables/useContextMenuItems.ts b/packages/frontend/editor-ui/src/features/shared/contextMenu/composables/useContextMenuItems.ts index 570bc3e034a..72f41bf148c 100644 --- a/packages/frontend/editor-ui/src/features/shared/contextMenu/composables/useContextMenuItems.ts +++ b/packages/frontend/editor-ui/src/features/shared/contextMenu/composables/useContextMenuItems.ts @@ -11,6 +11,7 @@ import { useCollaborationStore } from '@/features/collaboration/collaboration/co import { useFocusedNodesStore } from '@/features/ai/assistant/focusedNodes.store'; import { useI18n } from '@n8n/i18n'; import { getResourcePermissions } from '@n8n/permissions'; +import { useSettingsStore } from '@n8n/stores/settings.store'; import type { INode, INodeTypeDescription } from 'n8n-workflow'; import { NodeHelpers, WEBHOOK_NODE_TYPE } from 'n8n-workflow'; import { computed, type ComputedRef } from 'vue'; @@ -81,6 +82,7 @@ export function useContextMenuItems( ): ComputedRef { const uiStore = useUIStore(); const nodeTypesStore = useNodeTypesStore(); + const settingsStore = useSettingsStore(); const workflowDocumentStore = injectWorkflowDocumentStore(); const sourceControlStore = useSourceControlStore(); const collaborationStore = useCollaborationStore(); @@ -262,7 +264,10 @@ export function useContextMenuItems( } const onlyStickies = nodes.every((node) => node.type === STICKY_NODE_TYPE); - const canExtract = nodes.some(isExecutable) && !nodes.every(isAiSubNode); + const canExtract = + !settingsStore.isSubworkflowConversionDisabled && + nodes.some(isExecutable) && + !nodes.every(isAiSubNode); const i18nOptions = isGroupTarget ? { diff --git a/packages/frontend/editor-ui/src/features/workflows/canvas/components/Canvas.test.ts b/packages/frontend/editor-ui/src/features/workflows/canvas/components/Canvas.test.ts index af422476cd1..8316e3935b0 100644 --- a/packages/frontend/editor-ui/src/features/workflows/canvas/components/Canvas.test.ts +++ b/packages/frontend/editor-ui/src/features/workflows/canvas/components/Canvas.test.ts @@ -805,6 +805,14 @@ describe('Canvas', () => { expect(rendered.queryByTestId('canvas-node-group-extract')).toBeNull(); }); + + it('hides the convert button when executeWorkflow is excluded', async () => { + vi.spyOn(useSettingsStore(), 'isSubworkflowConversionDisabled', 'get').mockReturnValue(true); + + const rendered = await setupExpandedGroupWithLooseNodes(); + + expect(rendered.queryByTestId('canvas-node-group-extract')).toBeNull(); + }); }); describe('expanded group selection', () => { diff --git a/packages/frontend/editor-ui/src/features/workflows/canvas/components/Canvas.vue b/packages/frontend/editor-ui/src/features/workflows/canvas/components/Canvas.vue index be64afcd327..58c15c37a11 100644 --- a/packages/frontend/editor-ui/src/features/workflows/canvas/components/Canvas.vue +++ b/packages/frontend/editor-ui/src/features/workflows/canvas/components/Canvas.vue @@ -440,6 +440,7 @@ const { isSelectionExtractable } = useSelectionValidation(); // Groups that can be extracted to sub-workflows const extractableGroupIds = computed(() => { const ids = new Set(); + if (settingsStore.isSubworkflowConversionDisabled) return ids; for (const group of workflowDocumentStore.value.allGroups) { if (isSelectionExtractable(group.nodeIds).valid) { ids.add(group.id); @@ -547,7 +548,10 @@ const keyMap = computed(() => { run: () => emit('save:workflow'), }, shift_alt_t: async () => await onTidyUp({ source: 'keyboard-shortcut' }), - alt_x: emitWithSelectedNodes((ids) => emit('extract-workflow', ids)), + alt_x: { + disabled: () => settingsStore.isSubworkflowConversionDisabled, + run: emitWithSelectedNodes((ids) => emit('extract-workflow', ids)), + }, c: () => emit('start-chat'), r: emitWithLastSelectedNode((id) => emit('replace:node', id)), shift_alt_u: emitWithLastSelectedNode((id) => emit('copy:test:url', id)), diff --git a/packages/frontend/editor-ui/src/features/workflows/canvas/components/elements/selection/CanvasSelectionToolbar.test.ts b/packages/frontend/editor-ui/src/features/workflows/canvas/components/elements/selection/CanvasSelectionToolbar.test.ts index 892db3f6ce4..09f8f091fdd 100644 --- a/packages/frontend/editor-ui/src/features/workflows/canvas/components/elements/selection/CanvasSelectionToolbar.test.ts +++ b/packages/frontend/editor-ui/src/features/workflows/canvas/components/elements/selection/CanvasSelectionToolbar.test.ts @@ -10,6 +10,7 @@ import { useWorkflowDocumentStore, createWorkflowDocumentId, } from '@/app/stores/workflowDocument.store'; +import { useSettingsStore } from '@n8n/stores/settings.store'; const isSelectionGroupableMock = vi.fn(); const isSelectionExtractableMock = vi.fn(); @@ -187,6 +188,15 @@ describe('CanvasSelectionToolbar', () => { expect(wrapper.queryByTestId('canvas-selection-toolbar')).toBeNull(); }); + it('hides Extract when executeWorkflow is excluded', () => { + vi.spyOn(useSettingsStore(), 'isSubworkflowConversionDisabled', 'get').mockReturnValue(true); + + const wrapper = render({ selectedNodes: [makeNode('a'), makeNode('b')] }); + + expect(wrapper.getByTestId('canvas-selection-toolbar-group')).toBeTruthy(); + expect(wrapper.queryByTestId('canvas-selection-toolbar-extract')).toBeNull(); + }); + it('creates a group when Group is clicked', async () => { const store = workflowDocumentStore; const wrapper = render({ selectedNodes: [makeNode('a'), makeNode('b')] }); diff --git a/packages/frontend/editor-ui/src/features/workflows/canvas/components/elements/selection/CanvasSelectionToolbar.vue b/packages/frontend/editor-ui/src/features/workflows/canvas/components/elements/selection/CanvasSelectionToolbar.vue index 20a45f56b9b..201a8bb9e0b 100644 --- a/packages/frontend/editor-ui/src/features/workflows/canvas/components/elements/selection/CanvasSelectionToolbar.vue +++ b/packages/frontend/editor-ui/src/features/workflows/canvas/components/elements/selection/CanvasSelectionToolbar.vue @@ -9,6 +9,7 @@ import type { GraphNode } from '@vue-flow/core'; import { useVueFlowTransformPaneTeleport } from '../../../composables/useVueFlowTransformPaneTeleport'; import { useCanvasNodeGroupActions } from '../../../composables/useCanvasNodeGroupActions'; import { useSelectionValidation } from '@/app/composables/useSelectionValidation'; +import { useSettingsStore } from '@n8n/stores/settings.store'; import type { BoundingBox } from '../../../canvas.types'; const TOOLBAR_OFFSET_PX = 12; @@ -33,6 +34,7 @@ const props = withDefaults( ); const i18n = useI18n(); +const settingsStore = useSettingsStore(); const { teleportTarget } = useVueFlowTransformPaneTeleport(); const { isSelectionExtractable } = useSelectionValidation(); const { canGroup, groupSelection } = useCanvasNodeGroupActions(() => props.selectedNodes, { @@ -47,7 +49,10 @@ const emit = defineEmits<{ const selectedNodeIds = computed(() => props.selectedNodes.map((node) => node.id)); const canExtractWorkflow = computed( - () => !props.readOnly && isSelectionExtractable(selectedNodeIds.value).valid, + () => + !props.readOnly && + !settingsStore.isSubworkflowConversionDisabled && + isSelectionExtractable(selectedNodeIds.value).valid, ); const isToolbarVisible = computed( diff --git a/packages/frontend/editor-ui/src/features/workflows/components/WorkflowSettings/WorkflowSettings.test.ts b/packages/frontend/editor-ui/src/features/workflows/components/WorkflowSettings/WorkflowSettings.test.ts index cb154507d5a..74768c2d119 100644 --- a/packages/frontend/editor-ui/src/features/workflows/components/WorkflowSettings/WorkflowSettings.test.ts +++ b/packages/frontend/editor-ui/src/features/workflows/components/WorkflowSettings/WorkflowSettings.test.ts @@ -236,6 +236,24 @@ describe('WorkflowSettingsVue', () => { expect(getByTestId('workflow-caller-policy')).toBeVisible(); }); + it('should lock caller policy to none when executeWorkflow is excluded', async () => { + settingsStore.settings.enterprise[EnterpriseEditionFeature.Sharing] = true; + vi.spyOn(settingsStore, 'isExecuteWorkflowNodeExcluded', 'get').mockReturnValue(true); + workflowDocumentStore.setSettings({ + callerPolicy: 'workflowsFromAList', + callerIds: 'abc', + executionOrder: 'v1', + }); + + const { getByTestId, queryByTestId } = createComponent({ pinia }); + await flushPromises(); + + expect( + within(getByTestId('workflow-caller-policy-select')).getByRole('combobox'), + ).toBeDisabled(); + expect(queryByTestId('workflow-caller-policy-workflow-ids')).not.toBeInTheDocument(); + }); + describe('Custom span attributes', () => { beforeEach(() => { settingsStore.settings.activeModules = ['dynamic-credentials', 'otel']; diff --git a/packages/frontend/editor-ui/src/features/workflows/components/WorkflowSettings/WorkflowSettings.vue b/packages/frontend/editor-ui/src/features/workflows/components/WorkflowSettings/WorkflowSettings.vue index 881e6675cd1..11ca1042d1d 100644 --- a/packages/frontend/editor-ui/src/features/workflows/components/WorkflowSettings/WorkflowSettings.vue +++ b/packages/frontend/editor-ui/src/features/workflows/components/WorkflowSettings/WorkflowSettings.vue @@ -913,6 +913,9 @@ onMounted(async () => { workflowSettingsData.callerPolicy = defaultValues.value .workflowCallerPolicy as WorkflowSettings.CallerPolicy; } + if (settingsStore.isExecuteWorkflowNodeExcluded) { + workflowSettingsData.callerPolicy = 'none'; + } if (workflowSettingsData.executionTimeout === undefined) { workflowSettingsData.executionTimeout = rootStore.executionTimeout; } @@ -1156,10 +1159,15 @@ onBeforeUnmount(() => {