Skip to content

Commit 886d44c

Browse files
committed
Fix copilot trigger unsave
1 parent 997c463 commit 886d44c

6 files changed

Lines changed: 96 additions & 17 deletions

File tree

apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
1010
import { validateWorkflowState } from '@/lib/workflows/validation'
1111
import { getAllBlocks } from '@/blocks/registry'
1212
import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils'
13+
import { TRIGGER_RUNTIME_SUBBLOCK_IDS } from '@/triggers/consts'
1314

1415
interface EditWorkflowOperation {
1516
operation_type: 'add' | 'edit' | 'delete' | 'insert_into_subflow' | 'extract_from_subflow'
@@ -307,6 +308,38 @@ function addConnectionsAsEdges(
307308
})
308309
}
309310

311+
function applyTriggerConfigToBlockSubblocks(block: any, triggerConfig: Record<string, any>) {
312+
if (!block?.subBlocks || !triggerConfig || typeof triggerConfig !== 'object') {
313+
return
314+
}
315+
316+
Object.entries(triggerConfig).forEach(([configKey, configValue]) => {
317+
const existingSubblock = block.subBlocks[configKey]
318+
if (existingSubblock) {
319+
const existingValue = existingSubblock.value
320+
const valuesEqual =
321+
typeof existingValue === 'object' || typeof configValue === 'object'
322+
? JSON.stringify(existingValue) === JSON.stringify(configValue)
323+
: existingValue === configValue
324+
325+
if (valuesEqual) {
326+
return
327+
}
328+
329+
block.subBlocks[configKey] = {
330+
...existingSubblock,
331+
value: configValue,
332+
}
333+
} else {
334+
block.subBlocks[configKey] = {
335+
id: configKey,
336+
type: 'short-input',
337+
value: configValue,
338+
}
339+
}
340+
})
341+
}
342+
310343
/**
311344
* Apply operations directly to the workflow JSON state
312345
*/
@@ -405,6 +438,9 @@ function applyOperationsToWorkflowState(
405438
if (params?.inputs) {
406439
if (!block.subBlocks) block.subBlocks = {}
407440
Object.entries(params.inputs).forEach(([key, value]) => {
441+
if (TRIGGER_RUNTIME_SUBBLOCK_IDS.includes(key)) {
442+
return
443+
}
408444
let sanitizedValue = value
409445

410446
// Special handling for inputFormat - ensure it's an array
@@ -432,10 +468,26 @@ function applyOperationsToWorkflowState(
432468
value: sanitizedValue,
433469
}
434470
} else {
435-
block.subBlocks[key].value = sanitizedValue
471+
const existingValue = block.subBlocks[key].value
472+
const valuesEqual =
473+
typeof existingValue === 'object' || typeof sanitizedValue === 'object'
474+
? JSON.stringify(existingValue) === JSON.stringify(sanitizedValue)
475+
: existingValue === sanitizedValue
476+
477+
if (!valuesEqual) {
478+
block.subBlocks[key].value = sanitizedValue
479+
}
436480
}
437481
})
438482

483+
if (
484+
Object.prototype.hasOwnProperty.call(params.inputs, 'triggerConfig') &&
485+
block.subBlocks.triggerConfig &&
486+
typeof block.subBlocks.triggerConfig.value === 'object'
487+
) {
488+
applyTriggerConfigToBlockSubblocks(block, block.subBlocks.triggerConfig.value)
489+
}
490+
439491
// Update loop/parallel configuration in block.data
440492
if (block.type === 'loop') {
441493
block.data = block.data || {}

apps/sim/lib/workflows/json-sanitizer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Edge } from 'reactflow'
22
import { sanitizeWorkflowForSharing } from '@/lib/workflows/credential-extractor'
33
import type { BlockState, Loop, Parallel, WorkflowState } from '@/stores/workflows/workflow/types'
4+
import { TRIGGER_PERSISTED_SUBBLOCK_IDS } from '@/triggers/consts'
45

56
/**
67
* Sanitized workflow state for copilot (removes all UI-specific data)
@@ -68,6 +69,10 @@ export interface ExportWorkflowState {
6869
* Check if a subblock contains sensitive/secret data
6970
*/
7071
function isSensitiveSubBlock(key: string, subBlock: BlockState['subBlocks'][string]): boolean {
72+
if (TRIGGER_PERSISTED_SUBBLOCK_IDS.includes(key)) {
73+
return false
74+
}
75+
7176
// Check if it's an OAuth input type
7277
if (subBlock.type === 'oauth-input') {
7378
return true

apps/sim/lib/workflows/training/compute-edit-sequence.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { CopilotWorkflowState } from '@/lib/workflows/json-sanitizer'
2+
import { TRIGGER_RUNTIME_SUBBLOCK_IDS } from '@/triggers/consts'
23

34
export interface EditOperation {
45
operation_type: 'add' | 'edit' | 'delete' | 'insert_into_subflow' | 'extract_from_subflow'
@@ -502,6 +503,9 @@ function computeInputDelta(
502503
const delta: Record<string, any> = {}
503504

504505
for (const key in endInputs) {
506+
if (TRIGGER_RUNTIME_SUBBLOCK_IDS.includes(key)) {
507+
continue
508+
}
505509
if (
506510
!(key in startInputs) ||
507511
JSON.stringify(startInputs[key]) !== JSON.stringify(endInputs[key])

apps/sim/stores/workflows/server-utils.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
import type { BlockState, SubBlockState } from '@/stores/workflows/workflow/types'
1212

13-
const WEBHOOK_SUBBLOCK_FIELDS = ['webhookId', 'triggerPath']
14-
1513
/**
1614
* Server-safe version of mergeSubblockState for API routes
1715
*
@@ -44,11 +42,10 @@ export function mergeSubblockState(
4442
const blockValues = subBlockValues[id] || {}
4543

4644
// Create a deep copy of the block's subBlocks to maintain structure
47-
// Exclude webhook-specific fields that should not be persisted
4845
const mergedSubBlocks = Object.entries(blockSubBlocks).reduce(
4946
(subAcc, [subBlockId, subBlock]) => {
50-
// Skip if subBlock is undefined or is a webhook-specific field
51-
if (!subBlock || WEBHOOK_SUBBLOCK_FIELDS.includes(subBlockId)) {
47+
// Skip if subBlock is undefined
48+
if (!subBlock) {
5249
return subAcc
5350
}
5451

@@ -78,8 +75,7 @@ export function mergeSubblockState(
7875
if (
7976
!mergedSubBlocks[subBlockId] &&
8077
value !== null &&
81-
value !== undefined &&
82-
!WEBHOOK_SUBBLOCK_FIELDS.includes(subBlockId)
78+
value !== undefined
8379
) {
8480
// Create a minimal subblock structure
8581
mergedSubBlocks[subBlockId] = {

apps/sim/stores/workflows/utils.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
22
import type { BlockState, SubBlockState } from '@/stores/workflows/workflow/types'
33

4-
const WEBHOOK_SUBBLOCK_FIELDS = ['webhookId', 'triggerPath']
5-
64
/**
75
* Normalizes a block name for comparison by converting to lowercase and removing spaces
86
* @param name - The block name to normalize
@@ -77,11 +75,10 @@ export function mergeSubblockState(
7775
const blockValues = workflowSubblockValues[id] || {}
7876

7977
// Create a deep copy of the block's subBlocks to maintain structure
80-
// Exclude webhook-specific fields that should not be persisted
8178
const mergedSubBlocks = Object.entries(blockSubBlocks).reduce(
8279
(subAcc, [subBlockId, subBlock]) => {
83-
// Skip if subBlock is undefined or is a webhook-specific field
84-
if (!subBlock || WEBHOOK_SUBBLOCK_FIELDS.includes(subBlockId)) {
80+
// Skip if subBlock is undefined
81+
if (!subBlock) {
8582
return subAcc
8683
}
8784

@@ -122,8 +119,7 @@ export function mergeSubblockState(
122119
if (
123120
!mergedSubBlocks[subBlockId] &&
124121
value !== null &&
125-
value !== undefined &&
126-
!WEBHOOK_SUBBLOCK_FIELDS.includes(subBlockId)
122+
value !== undefined
127123
) {
128124
// Create a minimal subblock structure
129125
mergedSubBlocks[subBlockId] = {
@@ -174,8 +170,8 @@ export async function mergeSubblockStateAsync(
174170
// Process all subblocks in parallel
175171
const subBlockEntries = await Promise.all(
176172
Object.entries(block.subBlocks).map(async ([subBlockId, subBlock]) => {
177-
// Skip if subBlock is undefined or webhook-specific
178-
if (!subBlock || WEBHOOK_SUBBLOCK_FIELDS.includes(subBlockId)) {
173+
// Skip if subBlock is undefined
174+
if (!subBlock) {
179175
return null
180176
}
181177

apps/sim/triggers/consts.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,29 @@ export const SYSTEM_SUBBLOCK_IDS: string[] = [
1414
'triggerId', // Stored trigger ID
1515
'selectedTriggerId', // Selected trigger from dropdown (multi-trigger blocks)
1616
]
17+
18+
/**
19+
* Trigger-related subblock IDs whose values should be persisted and
20+
* propagated when workflows are edited programmatically.
21+
*/
22+
export const TRIGGER_PERSISTED_SUBBLOCK_IDS: string[] = [
23+
'triggerConfig',
24+
'triggerCredentials',
25+
'triggerId',
26+
'selectedTriggerId',
27+
'webhookId',
28+
'triggerPath',
29+
'testUrl',
30+
'testUrlExpiresAt',
31+
]
32+
33+
/**
34+
* Trigger-related subblock IDs that represent runtime metadata. They should remain
35+
* in the workflow state but must not be modified or cleared by diff operations.
36+
*/
37+
export const TRIGGER_RUNTIME_SUBBLOCK_IDS: string[] = [
38+
'webhookId',
39+
'triggerPath',
40+
'testUrl',
41+
'testUrlExpiresAt',
42+
]

0 commit comments

Comments
 (0)