diff --git a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow/builders.ts b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow/builders.ts index 529a0bc780..9d87c14b26 100644 --- a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow/builders.ts +++ b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow/builders.ts @@ -2,7 +2,11 @@ import crypto from 'crypto' import { createLogger } from '@sim/logger' import type { PermissionGroupConfig } from '@/lib/permission-groups/types' import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs' -import { buildCanonicalIndex, isCanonicalPair } from '@/lib/workflows/subblocks/visibility' +import { + buildCanonicalIndex, + buildDefaultCanonicalModes, + isCanonicalPair, +} from '@/lib/workflows/subblocks/visibility' import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' @@ -130,6 +134,12 @@ export function createBlockFromParams( } }) + const defaultModes = buildDefaultCanonicalModes(blockConfig.subBlocks) + if (Object.keys(defaultModes).length > 0) { + if (!blockState.data) blockState.data = {} + blockState.data.canonicalModes = defaultModes + } + if (validatedInputs) { updateCanonicalModesForInputs(blockState, Object.keys(validatedInputs), blockConfig) } diff --git a/apps/sim/lib/workflows/subblocks/visibility.ts b/apps/sim/lib/workflows/subblocks/visibility.ts index 1ce0076b44..aab03ca5db 100644 --- a/apps/sim/lib/workflows/subblocks/visibility.ts +++ b/apps/sim/lib/workflows/subblocks/visibility.ts @@ -75,6 +75,23 @@ export function isCanonicalPair(group?: CanonicalGroup): boolean { return Boolean(group?.basicId && group?.advancedIds?.length) } +/** + * Builds default canonical mode overrides for a block's subblocks. + * All canonical pairs default to `'basic'`. + */ +export function buildDefaultCanonicalModes( + subBlocks: SubBlockConfig[] +): Record { + const index = buildCanonicalIndex(subBlocks) + const modes: Record = {} + for (const group of Object.values(index.groupsById)) { + if (isCanonicalPair(group)) { + modes[group.canonicalId] = 'basic' + } + } + return modes +} + /** * Determine the active mode for a canonical group. */ diff --git a/apps/sim/serializer/index.test.ts b/apps/sim/serializer/index.test.ts index 8b0539144e..dfdf3a7be3 100644 --- a/apps/sim/serializer/index.test.ts +++ b/apps/sim/serializer/index.test.ts @@ -683,34 +683,37 @@ describe('Serializer', () => { expect(slackBlock?.config.params.username).toBe('bot') }) - it.concurrent('should fall back to legacy advancedMode when canonicalModes not set', () => { - const serializer = new Serializer() + it.concurrent( + 'should fall back to legacy advancedMode for non-credential canonical groups when canonicalModes not set', + () => { + const serializer = new Serializer() - const block: any = { - id: 'slack-1', - type: 'slack', - name: 'Test Slack Block', - position: { x: 0, y: 0 }, - advancedMode: true, - subBlocks: { - operation: { value: 'send' }, - destinationType: { value: 'channel' }, - channel: { value: 'general' }, - manualChannel: { value: 'C1234567890' }, - text: { value: 'Hello world' }, - username: { value: 'bot' }, - }, - outputs: {}, - enabled: true, - } + const block: any = { + id: 'slack-1', + type: 'slack', + name: 'Test Slack Block', + position: { x: 0, y: 0 }, + advancedMode: true, + subBlocks: { + operation: { value: 'send' }, + destinationType: { value: 'channel' }, + channel: { value: 'general' }, + manualChannel: { value: 'C1234567890' }, + text: { value: 'Hello world' }, + username: { value: 'bot' }, + }, + outputs: {}, + enabled: true, + } - const serialized = serializer.serializeWorkflow({ 'slack-1': block }, [], {}) - const slackBlock = serialized.blocks.find((b) => b.id === 'slack-1') + const serialized = serializer.serializeWorkflow({ 'slack-1': block }, [], {}) + const slackBlock = serialized.blocks.find((b) => b.id === 'slack-1') - expect(slackBlock).toBeDefined() - expect(slackBlock?.config.params.channel).toBe('C1234567890') - expect(slackBlock?.config.params.manualChannel).toBeUndefined() - }) + expect(slackBlock).toBeDefined() + expect(slackBlock?.config.params.channel).toBe('C1234567890') + expect(slackBlock?.config.params.manualChannel).toBeUndefined() + } + ) it.concurrent('should use basic value by default when no mode specified', () => { const serializer = new Serializer() diff --git a/apps/sim/serializer/index.ts b/apps/sim/serializer/index.ts index 6626468dd0..671535ef68 100644 --- a/apps/sim/serializer/index.ts +++ b/apps/sim/serializer/index.ts @@ -61,8 +61,9 @@ function shouldSerializeSubBlock( const group = canonicalId ? canonicalIndex.groupsById[canonicalId] : undefined if (group && isCanonicalPair(group)) { const mode = - canonicalModeOverrides?.[group.canonicalId] ?? - (displayAdvancedOptions ? 'advanced' : resolveCanonicalMode(group, values)) + canonicalModeOverrides?.[group.canonicalId] != null || !displayAdvancedOptions + ? resolveCanonicalMode(group, values, canonicalModeOverrides) + : 'advanced' const matchesMode = mode === 'advanced' ? group.advancedIds.includes(subBlockConfig.id) @@ -374,8 +375,11 @@ export class Serializer { Object.values(canonicalIndex.groupsById).forEach((group) => { const { basicValue, advancedValue } = getCanonicalValues(group, params) + const hasExplicitOverride = canonicalModeOverrides?.[group.canonicalId] != null const pairMode = - canonicalModeOverrides?.[group.canonicalId] ?? (legacyAdvancedMode ? 'advanced' : 'basic') + hasExplicitOverride || !legacyAdvancedMode + ? resolveCanonicalMode(group, allValues, canonicalModeOverrides) + : 'advanced' const chosen = pairMode === 'advanced' ? advancedValue : basicValue const sourceIds = [group.basicId, ...group.advancedIds].filter(Boolean) as string[] diff --git a/apps/sim/stores/workflows/utils.ts b/apps/sim/stores/workflows/utils.ts index 89b6c9eea3..1352db5d10 100644 --- a/apps/sim/stores/workflows/utils.ts +++ b/apps/sim/stores/workflows/utils.ts @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid' import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants' import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs' import { mergeSubblockStateWithValues } from '@/lib/workflows/subblocks' +import { buildDefaultCanonicalModes } from '@/lib/workflows/subblocks/visibility' import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils' import { TriggerUtils } from '@/lib/workflows/triggers/triggers' import { getBlock } from '@/blocks' @@ -196,6 +197,13 @@ export function prepareBlockState(options: PrepareBlockStateOptions): BlockState preferToolOutputs: !effectiveTriggerMode, }) + if (blockConfig.subBlocks) { + const canonicalModes = buildDefaultCanonicalModes(blockConfig.subBlocks) + if (Object.keys(canonicalModes).length > 0) { + blockData.canonicalModes = canonicalModes + } + } + return { id, type,