From 89fe02b3ddd6c2d635d57bd2a66e4eaf59de5762 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 12:22:22 -0800 Subject: [PATCH 1/9] fix(env-var-resolution): new executor env var resolution changes --- .../app/api/workflows/[id]/execute/route.ts | 6 +- .../utils/workflow-execution-utils.ts | 2 +- .../components/environment/environment.tsx | 920 +++++++----------- apps/sim/background/schedule-execution.ts | 6 +- apps/sim/background/webhook-execution.ts | 2 + apps/sim/background/workflow-execution.ts | 1 + apps/sim/components/icons.tsx | 4 +- .../executor/execution/snapshot-serializer.ts | 28 +- apps/sim/executor/execution/snapshot.ts | 1 + .../lib/workflows/executor/execution-core.ts | 4 +- .../executor/human-in-the-loop-manager.ts | 5 +- 11 files changed, 387 insertions(+), 592 deletions(-) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 1b9fa2f7ff3..11c0e4d4783 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -34,7 +34,7 @@ const ExecuteWorkflowSchema = z.object({ stream: z.boolean().optional(), useDraftState: z.boolean().optional(), input: z.any().optional(), - // Optional workflow state override (for executing diff workflows) + isClientSession: z.boolean().optional(), workflowStateOverride: z .object({ blocks: z.record(z.any()), @@ -95,6 +95,7 @@ export async function executeWorkflow( triggerType, useDraftState: false, startTime: new Date().toISOString(), + isClientSession: false, } const snapshot = new ExecutionSnapshot( @@ -329,6 +330,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: stream: streamParam, useDraftState, input: validatedInput, + isClientSession = false, workflowStateOverride, } = validation.data @@ -506,6 +508,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: triggerType, useDraftState: shouldUseDraftState, startTime: new Date().toISOString(), + isClientSession, workflowStateOverride: effectiveWorkflowStateOverride, } @@ -772,6 +775,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: triggerType, useDraftState: shouldUseDraftState, startTime: new Date().toISOString(), + isClientSession, workflowStateOverride: effectiveWorkflowStateOverride, } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts index b1dbf7e9dd3..1b089579e04 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-execution-utils.ts @@ -27,12 +27,12 @@ export async function executeWorkflowWithFullLogging( const executionId = options.executionId || uuidv4() const { addConsole } = useTerminalConsoleStore.getState() - // Build request payload const payload: any = { input: options.workflowInput, stream: true, triggerType: options.overrideTriggerType || 'manual', useDraftState: true, + isClientSession: true, } const response = await fetch(`/api/workflows/${activeWorkflowId}/execute`, { diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/environment/environment.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/environment/environment.tsx index f1229c7395c..5b77087690a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/environment/environment.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/environment/environment.tsx @@ -1,7 +1,7 @@ 'use client' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Plus, Search, Share2, Undo2 } from 'lucide-react' +import { Info, Plus, Search, Share2 } from 'lucide-react' import { useParams } from 'next/navigation' import { Button, Input as EmcnInput, Tooltip } from '@/components/emcn' import { @@ -30,9 +30,6 @@ const ENV_VAR_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/ const PRIMARY_BUTTON_STYLES = '!bg-[var(--brand-tertiary-2)] !text-[var(--text-inverse)] hover:!bg-[var(--brand-tertiary-2)]/90' -/** - * Generates a unique row identifier by combining timestamp with an incrementing counter - */ const generateRowId = (() => { let counter = 0 return () => { @@ -41,119 +38,126 @@ const generateRowId = (() => { } })() -/** - * Creates a new empty environment variable with a unique ID - */ -const createEmptyEnvVar = (): UIEnvironmentVariable => ({ - key: '', - value: '', - id: generateRowId(), -}) - -/** - * Represents an environment variable in the UI with optional ID for tracking - */ interface UIEnvironmentVariable { key: string value: string id?: number } -/** - * Props for the EnvironmentVariables component - */ interface EnvironmentVariablesProps { - /** Callback to register a handler that intercepts navigation away from this section */ registerBeforeLeaveHandler?: (handler: (onProceed: () => void) => void) => void } -/** - * Props for the WorkspaceVariableRow component - */ -interface WorkspaceVariableRowProps { +interface VariableRowProps { envKey: string value: string - renamingKey: string | null - pendingKeyValue: string - isNewlyPromoted: boolean - onRenameStart: (key: string) => void - onPendingKeyChange: (value: string) => void - onRenameEnd: (key: string, value: string) => void - onDelete: (key: string) => void - onDemote: (key: string, value: string) => void + isNew: boolean + focusedValueIndex: number | null + rowIndex: number + onKeyChange: (value: string) => void + onValueChange: (value: string) => void + onValueFocus: (index: number, e: React.FocusEvent) => void + onValueBlur: () => void + onDelete: () => void + onPromote?: () => void + canPromote?: boolean + isConflict?: boolean } -/** - * Renders a single workspace environment variable row with edit and delete capabilities - */ -function WorkspaceVariableRow({ +function VariableRow({ envKey, value, - renamingKey, - pendingKeyValue, - isNewlyPromoted, - onRenameStart, - onPendingKeyChange, - onRenameEnd, + isNew, + focusedValueIndex, + rowIndex, + onKeyChange, + onValueChange, + onValueFocus, + onValueBlur, onDelete, - onDemote, -}: WorkspaceVariableRowProps) { + onPromote, + canPromote, + isConflict, +}: VariableRowProps) { + const conflictClassName = 'border-[var(--text-error)] bg-[#F6D2D2] dark:bg-[#442929]' + const maskedValueStyle = + focusedValueIndex !== rowIndex && !isConflict + ? ({ WebkitTextSecurity: 'disc' } as React.CSSProperties) + : undefined + return ( -
- { - if (renamingKey !== envKey) onRenameStart(envKey) - onPendingKeyChange(e.target.value) - }} - onBlur={() => onRenameEnd(envKey, value)} - name={`workspace_env_key_${envKey}_${Math.random()}`} - autoComplete='off' - autoCapitalize='off' - spellCheck='false' - readOnly - onFocus={(e) => e.target.removeAttribute('readOnly')} - className='h-9' - /> -
- -
- {isNewlyPromoted && ( + <> +
+ isNew && onKeyChange(e.target.value)} + placeholder='API_KEY' + name={`env_key_${rowIndex}_${Math.random()}`} + autoComplete='off' + autoCapitalize='off' + spellCheck='false' + disabled={!isNew} + readOnly={!isNew} + onFocus={(e) => isNew && e.target.removeAttribute('readOnly')} + className={`h-9 ${!isNew ? 'cursor-not-allowed' : ''} ${isConflict ? conflictClassName : ''}`} + /> +
+ isNew && onValueChange(e.target.value)} + type='text' + onFocus={(e) => { + if (isNew && !isConflict) { + e.target.removeAttribute('readOnly') + onValueFocus(rowIndex, e) + } + }} + onBlur={onValueBlur} + placeholder={isConflict ? 'Workspace override active' : 'Enter value'} + disabled={!isNew || isConflict} + name={`env_value_${rowIndex}_${Math.random()}`} + autoComplete='off' + autoCapitalize='off' + spellCheck='false' + readOnly={!isNew || isConflict} + style={isNew ? maskedValueStyle : undefined} + className={`h-9 ${!isNew || isConflict ? 'cursor-not-allowed' : ''} ${isConflict ? conflictClassName : ''}`} + /> +
+ {onPromote && ( + + + + + Promote to workspace + + )} - - Scope: workspace + Delete - )} - - - - - Delete environment variable - +
-
+ {isConflict && ( +
+ Workspace variable with the same name overrides this. Rename your personal key to use it. +
+ )} + ) } -/** - * Environment Variables management component for handling personal and workspace-scoped variables. - * Provides functionality to create, edit, delete, and share environment variables between - * personal and workspace scopes with conflict detection. - */ export function EnvironmentVariables({ registerBeforeLeaveHandler }: EnvironmentVariablesProps) { const params = useParams() const workspaceId = (params?.workspaceId as string) || '' @@ -177,81 +181,100 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment const removeWorkspaceMutation = useRemoveWorkspaceEnvironment() const isLoading = isPersonalLoading || isWorkspaceLoading - const variables = useMemo(() => personalEnvData || {}, [personalEnvData]) - const [envVars, setEnvVars] = useState([]) + const [personalVars, setPersonalVars] = useState([]) + const [workspaceVars, setWorkspaceVars] = useState([]) const [searchTerm, setSearchTerm] = useState('') const [focusedValueIndex, setFocusedValueIndex] = useState(null) const [showUnsavedChanges, setShowUnsavedChanges] = useState(false) const [shouldScrollToBottom, setShouldScrollToBottom] = useState(false) - const [workspaceVars, setWorkspaceVars] = useState>({}) - const [conflicts, setConflicts] = useState([]) - const [renamingKey, setRenamingKey] = useState(null) - const [pendingKeyValue, setPendingKeyValue] = useState('') - const [changeToken, setChangeToken] = useState(0) + const [initialPersonalVars, setInitialPersonalVars] = useState([]) + const [initialWorkspaceVars, setInitialWorkspaceVars] = useState([]) - const initialWorkspaceVarsRef = useRef>({}) const scrollContainerRef = useRef(null) const pendingProceedCallback = useRef<(() => void) | null>(null) - const initialVarsRef = useRef([]) const hasChangesRef = useRef(false) const hasSavedRef = useRef(false) - const filteredEnvVars = useMemo(() => { - const mapped = envVars.map((envVar, index) => ({ envVar, originalIndex: index })) - if (!searchTerm.trim()) return mapped + const filteredPersonalVars = useMemo(() => { + if (!searchTerm.trim()) return personalVars const term = searchTerm.toLowerCase() - return mapped.filter(({ envVar }) => envVar.key.toLowerCase().includes(term)) - }, [envVars, searchTerm]) + return personalVars.filter((v) => v.key.toLowerCase().includes(term)) + }, [personalVars, searchTerm]) - const filteredWorkspaceEntries = useMemo(() => { - const entries = Object.entries(workspaceVars) - if (!searchTerm.trim()) return entries + const filteredWorkspaceVars = useMemo(() => { + if (!searchTerm.trim()) return workspaceVars const term = searchTerm.toLowerCase() - return entries.filter(([key]) => key.toLowerCase().includes(term)) + return workspaceVars.filter((v) => v.key.toLowerCase().includes(term)) }, [workspaceVars, searchTerm]) - const hasChanges = useMemo(() => { - const initialVars = initialVarsRef.current.filter((v) => v.key || v.value) - const currentVars = envVars.filter((v) => v.key || v.value) - const initialMap = new Map(initialVars.map((v) => [v.key, v.value])) - const currentMap = new Map(currentVars.map((v) => [v.key, v.value])) - - if (initialMap.size !== currentMap.size) return true - - for (const [key, value] of currentMap) { - if (initialMap.get(key) !== value) return true - } - - for (const key of initialMap.keys()) { - if (!currentMap.has(key)) return true - } - - const before = initialWorkspaceVarsRef.current - const after = workspaceVars - const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]) - - if (Object.keys(before).length !== Object.keys(after).length) return true + const workspaceKeysSet = useMemo( + () => new Set(workspaceVars.map((v) => v.key).filter(Boolean)), + [workspaceVars] + ) - for (const key of allKeys) { - if (before[key] !== after[key]) return true + const hasChanges = useMemo(() => { + const compareVars = (current: UIEnvironmentVariable[], initial: UIEnvironmentVariable[]) => { + const currentFiltered = current.filter((v) => v.key || v.value) + const initialFiltered = initial.filter((v) => v.key || v.value) + if (currentFiltered.length !== initialFiltered.length) return true + const initialMap = new Map(initialFiltered.map((v) => [v.key, v.value])) + for (const v of currentFiltered) { + if (initialMap.get(v.key) !== v.value) return true + } + return false } + return ( + compareVars(personalVars, initialPersonalVars) || + compareVars(workspaceVars, initialWorkspaceVars) + ) + }, [personalVars, workspaceVars, initialPersonalVars, initialWorkspaceVars]) + + const hasConflicts = useMemo( + () => personalVars.some((v) => v.key && workspaceKeysSet.has(v.key)), + [personalVars, workspaceKeysSet] + ) - return false - }, [envVars, workspaceVars, changeToken]) + const hasDuplicateWorkspaceKeys = useMemo(() => { + const keys = workspaceVars.map((v) => v.key).filter(Boolean) + return keys.length !== new Set(keys).size + }, [workspaceVars]) - const hasConflicts = useMemo(() => { - return envVars.some((envVar) => !!envVar.key && Object.hasOwn(workspaceVars, envVar.key)) - }, [envVars, workspaceVars]) + const hasDuplicatePersonalKeys = useMemo(() => { + const keys = personalVars.map((v) => v.key).filter(Boolean) + return keys.length !== new Set(keys).size + }, [personalVars]) useEffect(() => { hasChangesRef.current = hasChanges }, [hasChanges]) - /** - * Handles navigation attempts away from this section. - * Shows unsaved changes modal if there are changes, otherwise proceeds immediately. - */ + useEffect(() => { + if (hasSavedRef.current) return + const vars = Object.values(personalEnvData || {}).map((v) => ({ + ...v, + id: generateRowId(), + })) + setInitialPersonalVars(JSON.parse(JSON.stringify(vars))) + setPersonalVars(JSON.parse(JSON.stringify(vars))) + }, [personalEnvData]) + + useEffect(() => { + if (hasSavedRef.current) { + hasSavedRef.current = false + return + } + if (workspaceEnvData?.workspace) { + const vars = Object.entries(workspaceEnvData.workspace).map(([key, value]) => ({ + key, + value, + id: generateRowId(), + })) + setInitialWorkspaceVars(JSON.parse(JSON.stringify(vars))) + setWorkspaceVars(JSON.parse(JSON.stringify(vars))) + } + }, [workspaceEnvData]) + const handleBeforeLeave = useCallback((onProceed: () => void) => { if (hasChangesRef.current) { setShowUnsavedChanges(true) @@ -261,37 +284,6 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment } }, []) - useEffect(() => { - // Skip sync from server after a save - we already have correct local state - if (hasSavedRef.current) return - - const existingVars = Object.values(variables) - const initialVars = existingVars.length - ? existingVars.map((envVar) => ({ - ...envVar, - id: generateRowId(), - })) - : [createEmptyEnvVar()] - initialVarsRef.current = JSON.parse(JSON.stringify(initialVars)) - setEnvVars(JSON.parse(JSON.stringify(initialVars))) - pendingProceedCallback.current = null - }, [variables]) - - useEffect(() => { - if (workspaceEnvData) { - if (hasSavedRef.current) { - // After a save, only update conflicts - refs were already set optimistically - setConflicts(workspaceEnvData?.conflicts || []) - hasSavedRef.current = false - } else { - setWorkspaceVars(workspaceEnvData?.workspace || {}) - initialWorkspaceVarsRef.current = workspaceEnvData?.workspace || {} - setConflicts(workspaceEnvData?.conflicts || []) - } - } - }, [workspaceEnvData]) - - // Register the before-leave handler useEffect(() => { if (registerBeforeLeaveHandler) { registerBeforeLeaveHandler(handleBeforeLeave) @@ -308,213 +300,109 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment } }, [shouldScrollToBottom]) - useEffect(() => { - const personalKeys = envVars.map((envVar) => envVar.key.trim()).filter((key) => key.length > 0) - - const uniquePersonalKeys = Array.from(new Set(personalKeys)) - - const computedConflicts = uniquePersonalKeys.filter((key) => Object.hasOwn(workspaceVars, key)) - - setConflicts((prev) => { - if (prev.length === computedConflicts.length) { - const sameKeys = prev.every((key) => computedConflicts.includes(key)) - if (sameKeys) return prev - } - return computedConflicts - }) - }, [envVars, workspaceVars]) - - const handleWorkspaceKeyRename = useCallback( - (currentKey: string, currentValue: string) => { - const newKey = pendingKeyValue.trim() - if (!renamingKey || renamingKey !== currentKey) return - setRenamingKey(null) - if (!newKey || newKey === currentKey) return - - setWorkspaceVars((prev) => { - const next = { ...prev } - delete next[currentKey] - next[newKey] = currentValue - return next - }) - }, - [pendingKeyValue, renamingKey] - ) - - const handleDeleteWorkspaceVar = useCallback((key: string) => { - setWorkspaceVars((prev) => { - const next = { ...prev } - delete next[key] - return next - }) + const addWorkspaceVar = useCallback(() => { + setWorkspaceVars((prev) => [...prev, { key: '', value: '', id: generateRowId() }]) + setSearchTerm('') + setShouldScrollToBottom(true) }, []) - const addEnvVar = useCallback(() => { - setEnvVars((prev) => [...prev, createEmptyEnvVar()]) + const addPersonalVar = useCallback(() => { + setPersonalVars((prev) => [...prev, { key: '', value: '', id: generateRowId() }]) setSearchTerm('') setShouldScrollToBottom(true) }, []) - const updateEnvVar = useCallback((index: number, field: 'key' | 'value', value: string) => { - setEnvVars((prev) => { - const newEnvVars = [...prev] - newEnvVars[index][field] = value - return newEnvVars + const updateWorkspaceVar = useCallback((index: number, field: 'key' | 'value', value: string) => { + setWorkspaceVars((prev) => { + const updated = [...prev] + updated[index] = { ...updated[index], [field]: value } + return updated }) }, []) - const removeEnvVar = useCallback((index: number) => { - setEnvVars((prev) => { - const newEnvVars = prev.filter((_, i) => i !== index) - return newEnvVars.length ? newEnvVars : [createEmptyEnvVar()] + const updatePersonalVar = useCallback((index: number, field: 'key' | 'value', value: string) => { + setPersonalVars((prev) => { + const updated = [...prev] + updated[index] = { ...updated[index], [field]: value } + return updated }) }, []) - const handleValueFocus = useCallback((index: number, e: React.FocusEvent) => { - setFocusedValueIndex(index) - e.target.scrollLeft = 0 - }, []) - - const handleValueClick = useCallback((e: React.MouseEvent) => { - e.preventDefault() - e.currentTarget.scrollLeft = 0 + const removeWorkspaceVar = useCallback((index: number) => { + setWorkspaceVars((prev) => prev.filter((_, i) => i !== index)) }, []) - /** - * Parses a single line to extract environment variable key-value pair - */ - const parseEnvVarLine = useCallback((line: string): UIEnvironmentVariable | null => { - const equalIndex = line.indexOf('=') - if (equalIndex === -1 || equalIndex === 0) return null - - const potentialKey = line.substring(0, equalIndex).trim() - if (!ENV_VAR_PATTERN.test(potentialKey)) return null - - const value = line.substring(equalIndex + 1).trim() - return { key: potentialKey, value, id: generateRowId() } + const removePersonalVar = useCallback((index: number) => { + setPersonalVars((prev) => prev.filter((_, i) => i !== index)) }, []) - /** - * Handles pasting a single value into a specific field - */ - const handleSingleValuePaste = useCallback( - (text: string, index: number, inputType: 'key' | 'value') => { - setEnvVars((prev) => { - const newEnvVars = [...prev] - newEnvVars[index][inputType] = text - return newEnvVars - }) - }, - [] - ) - - /** - * Handles pasting multiple key=value pairs - */ - const handleKeyValuePaste = useCallback( - (lines: string[]) => { - const parsedVars = lines - .map(parseEnvVarLine) - .filter((parsed): parsed is UIEnvironmentVariable => parsed !== null) - .filter(({ key, value }) => key && value) - - if (parsedVars.length > 0) { - setEnvVars((prev) => { - const existingVars = prev.filter((v) => v.key || v.value) - return [...existingVars, ...parsedVars] - }) - setShouldScrollToBottom(true) - } - }, - [parseEnvVarLine] - ) + const promoteToWorkspace = useCallback( + (index: number) => { + const varToPromote = personalVars[index] + if (!varToPromote?.key || !varToPromote?.value) return - /** - * Handles paste events for environment variable inputs with smart detection - * of key=value pairs vs single values - */ - const handlePaste = useCallback( - (e: React.ClipboardEvent, index: number) => { - const text = e.clipboardData.getData('text').trim() - if (!text) return - - const lines = text.split('\n').filter((line) => line.trim()) - if (lines.length === 0) return - - e.preventDefault() - - const inputType = (e.target as HTMLInputElement).getAttribute('data-input-type') as - | 'key' - | 'value' - - if (inputType) { - const hasValidEnvVarPattern = lines.some((line) => parseEnvVarLine(line) !== null) - if (!hasValidEnvVarPattern) { - handleSingleValuePaste(text, index, inputType) - return - } - } + const keyExists = workspaceVars.some((ws) => ws.key === varToPromote.key) + if (keyExists) return - handleKeyValuePaste(lines) + setWorkspaceVars((prev) => [...prev, { ...varToPromote, id: generateRowId() }]) + setPersonalVars((prev) => prev.filter((_, i) => i !== index)) }, - [parseEnvVarLine, handleSingleValuePaste, handleKeyValuePaste] + [personalVars, workspaceVars] ) - /** - * Discards all changes and reverts to initial state - */ const handleCancel = useCallback(() => { - setEnvVars(JSON.parse(JSON.stringify(initialVarsRef.current))) - setWorkspaceVars({ ...initialWorkspaceVarsRef.current }) + setPersonalVars(JSON.parse(JSON.stringify(initialPersonalVars))) + setWorkspaceVars(JSON.parse(JSON.stringify(initialWorkspaceVars))) setShowUnsavedChanges(false) - pendingProceedCallback.current?.() pendingProceedCallback.current = null - }, []) + }, [initialPersonalVars, initialWorkspaceVars]) - /** - * Saves all personal and workspace environment variables with optimistic updates - */ const handleSave = useCallback(async () => { const onProceed = pendingProceedCallback.current - - // Save previous state for rollback on error - const prevInitialVars = [...initialVarsRef.current] - const prevInitialWorkspaceVars = { ...initialWorkspaceVarsRef.current } + const prevPersonal = [...initialPersonalVars] + const prevWorkspace = [...initialWorkspaceVars] try { setShowUnsavedChanges(false) hasSavedRef.current = true - // Optimistically update refs to mark current state as "saved" - initialWorkspaceVarsRef.current = { ...workspaceVars } - initialVarsRef.current = JSON.parse(JSON.stringify(envVars.filter((v) => v.key && v.value))) - - // Force recomputation of change tracking based on updated baselines - setChangeToken((prev) => prev + 1) + const newPersonalInitial = JSON.parse( + JSON.stringify(personalVars.filter((v) => v.key && v.value)) + ) + const newWorkspaceInitial = JSON.parse( + JSON.stringify(workspaceVars.filter((v) => v.key && v.value)) + ) - const validVariables = envVars + const personalToSave = personalVars .filter((v) => v.key && v.value) .reduce>((acc, { key, value }) => ({ ...acc, [key]: value }), {}) - await savePersonalMutation.mutateAsync({ variables: validVariables }) + await savePersonalMutation.mutateAsync({ variables: personalToSave }) - const before = prevInitialWorkspaceVars - const after = workspaceVars - const toUpsert: Record = {} - const toDelete: string[] = [] - - for (const [k, v] of Object.entries(after)) { - if (!(k in before) || before[k] !== v) { - toUpsert[k] = v + if (workspaceId) { + const currentWsMap = new Map( + workspaceVars.filter((v) => v.key && v.value).map((v) => [v.key, v.value]) + ) + const initialWsMap = new Map( + prevWorkspace.filter((v) => v.key && v.value).map((v) => [v.key, v.value]) + ) + + const toUpsert: Record = {} + const toDelete: string[] = [] + + for (const [k, v] of currentWsMap) { + if (!initialWsMap.has(k) || initialWsMap.get(k) !== v) { + toUpsert[k] = v + } } - } - for (const k of Object.keys(before)) { - if (!(k in after)) toDelete.push(k) - } + for (const k of initialWsMap.keys()) { + if (!currentWsMap.has(k)) { + toDelete.push(k) + } + } - if (workspaceId) { if (Object.keys(toUpsert).length) { await upsertWorkspaceMutation.mutateAsync({ workspaceId, variables: toUpsert }) } @@ -523,186 +411,49 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment } } + setInitialPersonalVars(newPersonalInitial) + setInitialWorkspaceVars(newWorkspaceInitial) + setPersonalVars(newPersonalInitial) + setWorkspaceVars(newWorkspaceInitial) + onProceed?.() pendingProceedCallback.current = null } catch (error) { - // Rollback optimistic updates on error hasSavedRef.current = false - initialVarsRef.current = prevInitialVars - initialWorkspaceVarsRef.current = prevInitialWorkspaceVars + setInitialPersonalVars(prevPersonal) + setInitialWorkspaceVars(prevWorkspace) logger.error('Failed to save environment variables:', error) } }, [ - envVars, + personalVars, workspaceVars, workspaceId, + initialPersonalVars, + initialWorkspaceVars, savePersonalMutation, upsertWorkspaceMutation, removeWorkspaceMutation, ]) - /** - * Promotes a personal environment variable to workspace scope - */ - const promoteToWorkspace = useCallback( - (envVar: UIEnvironmentVariable) => { - if (!envVar.key || !envVar.value || !workspaceId) return - setWorkspaceVars((prev) => ({ ...prev, [envVar.key]: envVar.value })) - setEnvVars((prev) => { - const filtered = prev.filter((entry) => entry !== envVar) - return filtered.length ? filtered : [createEmptyEnvVar()] - }) - }, - [workspaceId] - ) - - /** - * Demotes a newly promoted workspace variable back to personal scope. - * Only works for variables that were promoted during this session (before save). - */ - const demoteToPersonal = useCallback((key: string, value: string) => { - if (!key) return - setWorkspaceVars((prev) => { - const next = { ...prev } - delete next[key] - return next - }) - setEnvVars((prev) => [...prev, { key, value, id: generateRowId() }]) + const handleValueFocus = useCallback((index: number, e: React.FocusEvent) => { + setFocusedValueIndex(index) + e.target.scrollLeft = 0 }, []) - const conflictClassName = 'border-[var(--text-error)] bg-[#F6D2D2] dark:bg-[#442929]' - - /** - * Renders a single personal environment variable row with conflict detection - */ - const renderEnvVarRow = useCallback( - (envVar: UIEnvironmentVariable, originalIndex: number) => { - const isConflict = !!envVar.key && Object.hasOwn(workspaceVars, envVar.key) - const maskedValueStyle = - focusedValueIndex !== originalIndex && !isConflict - ? ({ WebkitTextSecurity: 'disc' } as React.CSSProperties) - : undefined - - return ( - <> -
- updateEnvVar(originalIndex, 'key', e.target.value)} - onPaste={(e) => handlePaste(e, originalIndex)} - placeholder='API_KEY' - name={`env_variable_name_${envVar.id || originalIndex}_${Math.random()}`} - autoComplete='off' - autoCapitalize='off' - spellCheck='false' - readOnly - onFocus={(e) => e.target.removeAttribute('readOnly')} - className={`h-9 ${isConflict ? conflictClassName : ''}`} - /> -
- updateEnvVar(originalIndex, 'value', e.target.value)} - type='text' - onFocus={(e) => { - if (!isConflict) { - e.target.removeAttribute('readOnly') - handleValueFocus(originalIndex, e) - } - }} - onClick={handleValueClick} - onBlur={() => setFocusedValueIndex(null)} - onPaste={(e) => handlePaste(e, originalIndex)} - placeholder={isConflict ? 'Workspace override active' : 'Enter value'} - disabled={isConflict} - aria-disabled={isConflict} - name={`env_variable_value_${envVar.id || originalIndex}_${Math.random()}`} - autoComplete='off' - autoCapitalize='off' - spellCheck='false' - readOnly={isConflict} - style={maskedValueStyle} - className={`h-9 ${isConflict ? `cursor-not-allowed ${conflictClassName}` : ''}`} - /> -
- - - - - Scope: personal - - - - - - Delete environment variable - -
-
- {isConflict && ( -
- Workspace variable with the same name overrides this. Rename your personal key to use - it. -
- )} - - ) - }, - [ - workspaceVars, - workspaceId, - focusedValueIndex, - updateEnvVar, - handlePaste, - handleValueFocus, - handleValueClick, - promoteToWorkspace, - removeEnvVar, - ] - ) - return ( <>
- {/* Hidden inputs to prevent browser password manager autofill interference */}
- + -
+
setSearchTerm(e.target.value)} - name='env_search_field' + name='env_search' autoComplete='off' autoCapitalize='off' spellCheck='false' @@ -722,109 +473,146 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' />
- - {hasConflicts && Resolve all conflicts before saving} + {(hasConflicts || hasDuplicateWorkspaceKeys || hasDuplicatePersonalKeys) && ( + + {hasDuplicateWorkspaceKeys || hasDuplicatePersonalKeys + ? 'Remove duplicate keys before saving' + : 'Resolve all conflicts before saving'} + + )}
- {/* Scrollable Content */}
-
+
{isLoading ? ( <>
-
- -
+
- {Array.from({ length: 2 }, (_, i) => ( -
- -
- -
- - -
-
- ))} +
) : ( <> - {/* Workspace section */} - {(!searchTerm.trim() || filteredWorkspaceEntries.length > 0) && ( -
-
+
+
+ Workspace + + +
+ {filteredWorkspaceVars.length === 0 && !searchTerm.trim() ? ( +
+ No workspace variables yet
- {!searchTerm.trim() && Object.keys(workspaceVars).length === 0 ? ( -
- No workspace variables yet -
- ) : ( - (searchTerm.trim() - ? filteredWorkspaceEntries - : Object.entries(workspaceVars) - ).map(([key, value]) => ( - { + const originalIndex = workspaceVars.findIndex((wv) => wv.id === v.id) + const isNew = !initialWorkspaceVars.some( + (iv) => iv.key === v.key && iv.value === v.value + ) + return ( + updateWorkspaceVar(originalIndex, 'key', val)} + onValueChange={(val) => updateWorkspaceVar(originalIndex, 'value', val)} + onValueFocus={handleValueFocus} + onValueBlur={() => setFocusedValueIndex(null)} + onDelete={() => removeWorkspaceVar(originalIndex)} /> - )) - )} -
- )} + ) + }) + )} +
- {/* Personal section */} - {(!searchTerm.trim() || filteredEnvVars.length > 0) && ( -
-
- Personal +
+
+
+ + Personal + + + + + + + Private to you and for testing purposes. Used solely for manual runs + unless you are the workflow owner. + +
- {filteredEnvVars.map(({ envVar, originalIndex }) => ( -
- {renderEnvVarRow(envVar, originalIndex)} -
- ))} +
- )} - {/* Show message when search has no results across both sections */} + {filteredPersonalVars.length === 0 && !searchTerm.trim() ? ( +
+ No personal variables yet +
+ ) : ( + filteredPersonalVars.map((v) => { + const originalIndex = personalVars.findIndex((pv) => pv.id === v.id) + const isConflict = Boolean(v.key && workspaceKeysSet.has(v.key)) + return ( + updatePersonalVar(originalIndex, 'key', val)} + onValueChange={(val) => updatePersonalVar(originalIndex, 'value', val)} + onValueFocus={handleValueFocus} + onValueBlur={() => setFocusedValueIndex(null)} + onDelete={() => removePersonalVar(originalIndex)} + onPromote={() => promoteToWorkspace(originalIndex)} + canPromote={!!v.key && !!v.value && !isConflict && !!workspaceId} + isConflict={isConflict} + /> + ) + }) + )} +
+ {searchTerm.trim() && - filteredEnvVars.length === 0 && - filteredWorkspaceEntries.length === 0 && - (envVars.length > 0 || Object.keys(workspaceVars).length > 0) && ( + filteredPersonalVars.length === 0 && + filteredWorkspaceVars.length === 0 && (
No environment variables found matching "{searchTerm}"
@@ -852,11 +640,7 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment {hasConflicts ? ( - diff --git a/apps/sim/background/schedule-execution.ts b/apps/sim/background/schedule-execution.ts index d51e2f290ac..868576678d8 100644 --- a/apps/sim/background/schedule-execution.ts +++ b/apps/sim/background/schedule-execution.ts @@ -208,8 +208,11 @@ async function runWorkflowExecution({ const mergedStates = mergeSubblockState(blocks) + // Use workflow owner's personal env vars for scheduled executions + const personalEnvUserId = workflowRecord.userId || actorUserId + const { personalEncrypted, workspaceEncrypted } = await getPersonalAndWorkspaceEnv( - actorUserId, + personalEnvUserId, workflowRecord.workspaceId || undefined ) @@ -243,6 +246,7 @@ async function runWorkflowExecution({ triggerBlockId: payload.blockId || undefined, useDraftState: false, startTime: new Date().toISOString(), + isClientSession: false, } const snapshot = new ExecutionSnapshot( diff --git a/apps/sim/background/webhook-execution.ts b/apps/sim/background/webhook-execution.ts index ea8bc11d28a..cda29c29ee9 100644 --- a/apps/sim/background/webhook-execution.ts +++ b/apps/sim/background/webhook-execution.ts @@ -226,6 +226,7 @@ async function executeWebhookJobInternal( triggerBlockId: payload.blockId, useDraftState: false, startTime: new Date().toISOString(), + isClientSession: false, } const snapshot = new ExecutionSnapshot( @@ -439,6 +440,7 @@ async function executeWebhookJobInternal( triggerBlockId: payload.blockId, useDraftState: false, startTime: new Date().toISOString(), + isClientSession: false, } const snapshot = new ExecutionSnapshot( diff --git a/apps/sim/background/workflow-execution.ts b/apps/sim/background/workflow-execution.ts index 4789ad2e7c7..a127e079c9f 100644 --- a/apps/sim/background/workflow-execution.ts +++ b/apps/sim/background/workflow-execution.ts @@ -77,6 +77,7 @@ export async function executeWorkflowJob(payload: WorkflowExecutionPayload) { triggerType: payload.triggerType || 'api', useDraftState: false, startTime: new Date().toISOString(), + isClientSession: false, } const snapshot = new ExecutionSnapshot( diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 573d1b44901..062d7f479fa 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -696,8 +696,8 @@ export function GrafanaIcon(props: SVGProps) { y2='5.356' gradientUnits='userSpaceOnUse' > - - + + diff --git a/apps/sim/executor/execution/snapshot-serializer.ts b/apps/sim/executor/execution/snapshot-serializer.ts index 594d7c067e2..4f796860490 100644 --- a/apps/sim/executor/execution/snapshot-serializer.ts +++ b/apps/sim/executor/execution/snapshot-serializer.ts @@ -1,18 +1,16 @@ import type { DAG } from '@/executor/dag/builder' -import type { SerializableExecutionState } from '@/executor/execution/snapshot' -import { ExecutionSnapshot } from '@/executor/execution/snapshot' -import type { ExecutionContext, ExecutionMetadata, SerializedSnapshot } from '@/executor/types' +import { + type ExecutionMetadata, + ExecutionSnapshot, + type SerializableExecutionState, +} from '@/executor/execution/snapshot' +import type { ExecutionContext, SerializedSnapshot } from '@/executor/types' function mapFromEntries(map?: Map): Record | undefined { if (!map) return undefined return Object.fromEntries(map) } -function setToArray(set?: Set): T[] | undefined { - if (!set) return undefined - return Array.from(set) -} - function serializeLoopExecutions( loopExecutions?: Map ): Record | undefined { @@ -96,20 +94,18 @@ export function serializePauseSnapshot( dagIncomingEdges, } - const executionMetadata = { + const executionMetadata: ExecutionMetadata = { requestId: - (context.metadata as any)?.requestId ?? - context.executionId ?? - context.workflowId ?? - 'unknown', + metadataFromContext?.requestId ?? context.executionId ?? context.workflowId ?? 'unknown', executionId: context.executionId ?? 'unknown', workflowId: context.workflowId, workspaceId: context.workspaceId, - userId: (context.metadata as any)?.userId ?? '', - triggerType: (context.metadata as any)?.triggerType ?? 'manual', + userId: metadataFromContext?.userId ?? '', + triggerType: metadataFromContext?.triggerType ?? 'manual', triggerBlockId: triggerBlockIds[0], useDraftState, - startTime: context.metadata.startTime ?? new Date().toISOString(), + startTime: metadataFromContext?.startTime ?? new Date().toISOString(), + isClientSession: metadataFromContext?.isClientSession, } const snapshot = new ExecutionSnapshot( diff --git a/apps/sim/executor/execution/snapshot.ts b/apps/sim/executor/execution/snapshot.ts index ebd559d03de..47bf41f3424 100644 --- a/apps/sim/executor/execution/snapshot.ts +++ b/apps/sim/executor/execution/snapshot.ts @@ -11,6 +11,7 @@ export interface ExecutionMetadata { triggerBlockId?: string useDraftState: boolean startTime: string + isClientSession?: boolean pendingBlocks?: string[] resumeFromSnapshot?: boolean workflowStateOverride?: { diff --git a/apps/sim/lib/workflows/executor/execution-core.ts b/apps/sim/lib/workflows/executor/execution-core.ts index 1096aa16237..e6aa27e17f8 100644 --- a/apps/sim/lib/workflows/executor/execution-core.ts +++ b/apps/sim/lib/workflows/executor/execution-core.ts @@ -152,8 +152,10 @@ export async function executeWorkflowCore( // Merge block states const mergedStates = mergeSubblockState(blocks) + const personalEnvUserId = metadata.isClientSession ? userId : workflow.userId || userId + const { personalEncrypted, workspaceEncrypted, personalDecrypted, workspaceDecrypted } = - await getPersonalAndWorkspaceEnv(userId, providedWorkspaceId) + await getPersonalAndWorkspaceEnv(personalEnvUserId, providedWorkspaceId) // Use encrypted values for logging (don't log decrypted secrets) const variables = EnvVarsSchema.parse({ ...personalEncrypted, ...workspaceEncrypted }) diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts index 08c0910de5a..762e6294769 100644 --- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts +++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts @@ -635,11 +635,12 @@ export class PauseResumeManager { const metadata = { ...baseSnapshot.metadata, - executionId: resumeExecutionId, // Same as original - requestId: baseSnapshot.metadata.requestId, // Keep original requestId + executionId: resumeExecutionId, + requestId: baseSnapshot.metadata.requestId, startTime: new Date().toISOString(), userId, useDraftState: baseSnapshot.metadata.useDraftState, + isClientSession: baseSnapshot.metadata.isClientSession, resumeFromSnapshot: true, } From 18dfa3975457e077854fa001fea8434edec8803f Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 12:47:38 -0800 Subject: [PATCH 2/9] add sessionuser id" --- apps/sim/app/api/workflows/[id]/execute/route.ts | 2 ++ apps/sim/executor/execution/snapshot-serializer.ts | 1 + apps/sim/executor/execution/snapshot.ts | 1 + apps/sim/lib/workflows/executor/execution-core.ts | 5 ++++- apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 11c0e4d4783..1df6e3bab98 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -505,6 +505,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: workflowId, workspaceId: workflow.workspaceId ?? undefined, userId: actorUserId, + sessionUserId: isClientSession ? userId : undefined, triggerType, useDraftState: shouldUseDraftState, startTime: new Date().toISOString(), @@ -772,6 +773,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: workflowId, workspaceId: workflow.workspaceId ?? undefined, userId: actorUserId, + sessionUserId: isClientSession ? userId : undefined, triggerType, useDraftState: shouldUseDraftState, startTime: new Date().toISOString(), diff --git a/apps/sim/executor/execution/snapshot-serializer.ts b/apps/sim/executor/execution/snapshot-serializer.ts index 4f796860490..f65705f7059 100644 --- a/apps/sim/executor/execution/snapshot-serializer.ts +++ b/apps/sim/executor/execution/snapshot-serializer.ts @@ -101,6 +101,7 @@ export function serializePauseSnapshot( workflowId: context.workflowId, workspaceId: context.workspaceId, userId: metadataFromContext?.userId ?? '', + sessionUserId: metadataFromContext?.sessionUserId, triggerType: metadataFromContext?.triggerType ?? 'manual', triggerBlockId: triggerBlockIds[0], useDraftState, diff --git a/apps/sim/executor/execution/snapshot.ts b/apps/sim/executor/execution/snapshot.ts index 47bf41f3424..9f3a101e392 100644 --- a/apps/sim/executor/execution/snapshot.ts +++ b/apps/sim/executor/execution/snapshot.ts @@ -7,6 +7,7 @@ export interface ExecutionMetadata { workflowId: string workspaceId?: string userId: string + sessionUserId?: string triggerType: string triggerBlockId?: string useDraftState: boolean diff --git a/apps/sim/lib/workflows/executor/execution-core.ts b/apps/sim/lib/workflows/executor/execution-core.ts index e6aa27e17f8..4764089a028 100644 --- a/apps/sim/lib/workflows/executor/execution-core.ts +++ b/apps/sim/lib/workflows/executor/execution-core.ts @@ -152,7 +152,10 @@ export async function executeWorkflowCore( // Merge block states const mergedStates = mergeSubblockState(blocks) - const personalEnvUserId = metadata.isClientSession ? userId : workflow.userId || userId + const personalEnvUserId = + metadata.isClientSession && metadata.sessionUserId + ? metadata.sessionUserId + : workflow.userId || userId const { personalEncrypted, workspaceEncrypted, personalDecrypted, workspaceDecrypted } = await getPersonalAndWorkspaceEnv(personalEnvUserId, providedWorkspaceId) diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts index 762e6294769..c7b8a940d24 100644 --- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts +++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts @@ -639,6 +639,7 @@ export class PauseResumeManager { requestId: baseSnapshot.metadata.requestId, startTime: new Date().toISOString(), userId, + sessionUserId: baseSnapshot.metadata.sessionUserId, useDraftState: baseSnapshot.metadata.useDraftState, isClientSession: baseSnapshot.metadata.isClientSession, resumeFromSnapshot: true, From dd73d94ecef328e56451fe8008413a9762780e01 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 14:55:04 -0800 Subject: [PATCH 3/9] cleanup code --- .../app/api/workflows/[id]/execute/route.ts | 3 -- apps/sim/background/schedule-execution.ts | 4 +- apps/sim/background/webhook-execution.ts | 10 +--- apps/sim/background/workflow-execution.ts | 1 - .../executor/execution/snapshot-serializer.ts | 1 - apps/sim/executor/execution/snapshot.ts | 7 +-- .../lib/workflows/executor/execution-core.ts | 7 +-- .../executor/human-in-the-loop-manager.ts | 1 - apps/sim/package.json | 4 +- bun.lock | 51 ++++++++++++++++--- 10 files changed, 54 insertions(+), 35 deletions(-) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 1df6e3bab98..1bb066255ec 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -102,7 +102,6 @@ export async function executeWorkflow( metadata, workflow, input, - {}, workflow.variables || {}, streamConfig?.selectedOutputs || [] ) @@ -517,7 +516,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: metadata, workflow, processedInput, - {}, workflow.variables || {}, selectedOutputs ) @@ -785,7 +783,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: metadata, workflow, processedInput, - {}, workflow.variables || {}, selectedOutputs ) diff --git a/apps/sim/background/schedule-execution.ts b/apps/sim/background/schedule-execution.ts index 868576678d8..1fe793cb28a 100644 --- a/apps/sim/background/schedule-execution.ts +++ b/apps/sim/background/schedule-execution.ts @@ -208,8 +208,7 @@ async function runWorkflowExecution({ const mergedStates = mergeSubblockState(blocks) - // Use workflow owner's personal env vars for scheduled executions - const personalEnvUserId = workflowRecord.userId || actorUserId + const personalEnvUserId = workflowRecord.userId const { personalEncrypted, workspaceEncrypted } = await getPersonalAndWorkspaceEnv( personalEnvUserId, @@ -253,7 +252,6 @@ async function runWorkflowExecution({ metadata, workflowRecord, input, - {}, workflowRecord.variables || {}, [] ) diff --git a/apps/sim/background/webhook-execution.ts b/apps/sim/background/webhook-execution.ts index cda29c29ee9..1afe0aba331 100644 --- a/apps/sim/background/webhook-execution.ts +++ b/apps/sim/background/webhook-execution.ts @@ -233,7 +233,6 @@ async function executeWebhookJobInternal( metadata, workflow, airtableInput, - {}, workflowVariables, [] ) @@ -443,14 +442,7 @@ async function executeWebhookJobInternal( isClientSession: false, } - const snapshot = new ExecutionSnapshot( - metadata, - workflow, - input || {}, - {}, - workflowVariables, - [] - ) + const snapshot = new ExecutionSnapshot(metadata, workflow, input || {}, workflowVariables, []) const executionResult = await executeWorkflowCore({ snapshot, diff --git a/apps/sim/background/workflow-execution.ts b/apps/sim/background/workflow-execution.ts index a127e079c9f..eb7f91b7716 100644 --- a/apps/sim/background/workflow-execution.ts +++ b/apps/sim/background/workflow-execution.ts @@ -84,7 +84,6 @@ export async function executeWorkflowJob(payload: WorkflowExecutionPayload) { metadata, workflow, payload.input, - {}, workflow.variables || {}, [] ) diff --git a/apps/sim/executor/execution/snapshot-serializer.ts b/apps/sim/executor/execution/snapshot-serializer.ts index f65705f7059..e99d098de30 100644 --- a/apps/sim/executor/execution/snapshot-serializer.ts +++ b/apps/sim/executor/execution/snapshot-serializer.ts @@ -113,7 +113,6 @@ export function serializePauseSnapshot( executionMetadata, context.workflow, {}, - context.environmentVariables ?? {}, context.workflowVariables ?? {}, context.selectedOutputs ?? [], state diff --git a/apps/sim/executor/execution/snapshot.ts b/apps/sim/executor/execution/snapshot.ts index 9f3a101e392..454de3b1e21 100644 --- a/apps/sim/executor/execution/snapshot.ts +++ b/apps/sim/executor/execution/snapshot.ts @@ -59,18 +59,20 @@ export class ExecutionSnapshot { public readonly metadata: ExecutionMetadata, public readonly workflow: any, public readonly input: any, - public readonly environmentVariables: Record, public readonly workflowVariables: Record, public readonly selectedOutputs: string[] = [], public readonly state?: SerializableExecutionState ) {} + get environmentVariables(): Record { + return {} + } + toJSON(): string { return JSON.stringify({ metadata: this.metadata, workflow: this.workflow, input: this.input, - environmentVariables: this.environmentVariables, workflowVariables: this.workflowVariables, selectedOutputs: this.selectedOutputs, state: this.state, @@ -83,7 +85,6 @@ export class ExecutionSnapshot { data.metadata, data.workflow, data.input, - data.environmentVariables, data.workflowVariables, data.selectedOutputs, data.state diff --git a/apps/sim/lib/workflows/executor/execution-core.ts b/apps/sim/lib/workflows/executor/execution-core.ts index 4764089a028..9ea07a0f62f 100644 --- a/apps/sim/lib/workflows/executor/execution-core.ts +++ b/apps/sim/lib/workflows/executor/execution-core.ts @@ -97,8 +97,7 @@ export async function executeWorkflowCore( options: ExecuteWorkflowCoreOptions ): Promise { const { snapshot, callbacks, loggingSession, skipLogCreation } = options - const { metadata, workflow, input, environmentVariables, workflowVariables, selectedOutputs } = - snapshot + const { metadata, workflow, input, workflowVariables, selectedOutputs } = snapshot const { requestId, workflowId, userId, triggerType, executionId, triggerBlockId, useDraftState } = metadata const { onBlockStart, onBlockComplete, onStream, onExecutorCreated } = callbacks @@ -153,9 +152,7 @@ export async function executeWorkflowCore( const mergedStates = mergeSubblockState(blocks) const personalEnvUserId = - metadata.isClientSession && metadata.sessionUserId - ? metadata.sessionUserId - : workflow.userId || userId + metadata.isClientSession && metadata.sessionUserId ? metadata.sessionUserId : workflow.userId const { personalEncrypted, workspaceEncrypted, personalDecrypted, workspaceDecrypted } = await getPersonalAndWorkspaceEnv(personalEnvUserId, providedWorkspaceId) diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts index c7b8a940d24..0670bf4d106 100644 --- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts +++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts @@ -649,7 +649,6 @@ export class PauseResumeManager { metadata, baseSnapshot.workflow, resumeInput ?? {}, - baseSnapshot.environmentVariables || {}, baseSnapshot.workflowVariables || {}, baseSnapshot.selectedOutputs || [], stateCopy diff --git a/apps/sim/package.json b/apps/sim/package.json index 4cc07f14c81..f0c9b29ce33 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -67,7 +67,7 @@ "@radix-ui/react-visually-hidden": "1.2.4", "@react-email/components": "^0.0.34", "@react-email/render": "2.0.0", - "@trigger.dev/sdk": "4.0.4", + "@trigger.dev/sdk": "4.1.2", "@types/three": "0.177.0", "better-auth": "1.3.12", "browser-image-compression": "^2.0.2", @@ -135,7 +135,7 @@ }, "devDependencies": { "@testing-library/jest-dom": "^6.6.3", - "@trigger.dev/build": "4.0.4", + "@trigger.dev/build": "4.1.2", "@types/html-to-text": "9.0.4", "@types/js-yaml": "4.0.9", "@types/jsdom": "21.1.7", diff --git a/bun.lock b/bun.lock index 4c0602c6183..5a62cc67723 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "simstudio", @@ -113,7 +112,7 @@ "@radix-ui/react-visually-hidden": "1.2.4", "@react-email/components": "^0.0.34", "@react-email/render": "2.0.0", - "@trigger.dev/sdk": "4.0.4", + "@trigger.dev/sdk": "4.1.2", "@types/three": "0.177.0", "better-auth": "1.3.12", "browser-image-compression": "^2.0.2", @@ -181,7 +180,7 @@ }, "devDependencies": { "@testing-library/jest-dom": "^6.6.3", - "@trigger.dev/build": "4.0.4", + "@trigger.dev/build": "4.1.2", "@types/html-to-text": "9.0.4", "@types/js-yaml": "4.0.9", "@types/jsdom": "21.1.7", @@ -538,7 +537,7 @@ "@e2b/code-interpreter": ["@e2b/code-interpreter@2.0.0", "", { "dependencies": { "e2b": "^2.0.1" } }, "sha512-rCIW4dV544sUx2YQB/hhwDrK6sQzIb5lr5h/1CIVoOIRgU9q3NUxsFj+2OsgWd4rMG8l6b/oA7FVZJwP4sGX/A=="], - "@electric-sql/client": ["@electric-sql/client@1.0.0-beta.1", "", { "optionalDependencies": { "@rollup/rollup-darwin-arm64": "^4.18.1" } }, "sha512-Ei9jN3pDoGzc+a/bGqnB5ajb52IvSv7/n2btuyzUlcOHIR2kM9fqtYTJXPwZYKLkGZlHWlpHgWyRtrinkP2nHg=="], + "@electric-sql/client": ["@electric-sql/client@1.0.14", "", { "dependencies": { "@microsoft/fetch-event-source": "^2.0.1" }, "optionalDependencies": { "@rollup/rollup-darwin-arm64": "^4.18.1" } }, "sha512-LtPAfeMxXRiYS0hyDQ5hue2PjljUiK9stvzsVyVb4nwxWQxfOWTSF42bHTs/o5i3x1T4kAQ7mwHpxa4A+f8X7Q=="], "@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], @@ -704,6 +703,8 @@ "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], + "@microsoft/fetch-event-source": ["@microsoft/fetch-event-source@2.0.1", "", {}, "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.20.2", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-6rqTdFt67AAAzln3NOKsXRmv5ZzPkgbfaebKBqUbts7vK1GZudqnrun5a8d3M/h955cam9RHZ6Jb4Y1XhnmFPg=="], "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.3.1", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg=="], @@ -870,6 +871,10 @@ "@posthog/core": ["@posthog/core@1.2.2", "", {}, "sha512-f16Ozx6LIigRG+HsJdt+7kgSxZTHeX5f1JlCGKI1lXcvlZgfsCR338FuMI2QRYXGl+jg/vYFzGOTQBxl90lnBg=="], + "@prisma/config": ["@prisma/config@6.19.0", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg=="], + + "@protobuf-ts/runtime": ["@protobuf-ts/runtime@2.11.1", "", {}, "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], @@ -1090,6 +1095,8 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.3", "", { "os": "win32", "cpu": "x64" }, "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA=="], + "@s2-dev/streamstore": ["@s2-dev/streamstore@0.17.3", "", { "dependencies": { "@protobuf-ts/runtime": "^2.11.1" }, "peerDependencies": { "typescript": "^5.9.3" } }, "sha512-UeXL5+MgZQfNkbhCgEDVm7PrV5B3bxh6Zp4C5pUzQQwaoA+iGh2QiiIptRZynWgayzRv4vh0PYfnKpTzJEXegQ=="], + "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], "@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="], @@ -1280,11 +1287,11 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@trigger.dev/build": ["@trigger.dev/build@4.0.4", "", { "dependencies": { "@trigger.dev/core": "4.0.4", "pkg-types": "^1.1.3", "tinyglobby": "^0.2.2", "tsconfck": "3.1.3" } }, "sha512-W3mP+RBkcYOrNYTTmQ/WdU6LB+2Tk1S6r3OjEWqXEPsXLEEw6BzHTHZBirHYX4lWRBL9jVkL+/H74ycyNfzRjg=="], + "@trigger.dev/build": ["@trigger.dev/build@4.1.2", "", { "dependencies": { "@prisma/config": "^6.10.0", "@trigger.dev/core": "4.1.2", "mlly": "^1.7.1", "pkg-types": "^1.1.3", "resolve": "^1.22.8", "tinyglobby": "^0.2.2", "tsconfck": "3.1.3" } }, "sha512-CwejY4n6nqls7Z2Vju3AF1FIRfBzGgogKxm22aqBKGlkjblfbvKcZE3kGSrJ+X6n90Fw5glZVoK3BiC32Sdv3g=="], - "@trigger.dev/core": ["@trigger.dev/core@4.0.4", "", { "dependencies": { "@bugsnag/cuid": "^3.1.1", "@electric-sql/client": "1.0.0-beta.1", "@google-cloud/precise-date": "^4.0.0", "@jsonhero/path": "^1.0.21", "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/exporter-logs-otlp-http": "0.203.0", "@opentelemetry/exporter-trace-otlp-http": "0.203.0", "@opentelemetry/instrumentation": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-trace-base": "2.0.1", "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "1.36.0", "dequal": "^2.0.3", "eventsource": "^3.0.5", "eventsource-parser": "^3.0.0", "execa": "^8.0.1", "humanize-duration": "^3.27.3", "jose": "^5.4.0", "nanoid": "3.3.8", "prom-client": "^15.1.0", "socket.io": "4.7.4", "socket.io-client": "4.7.5", "std-env": "^3.8.1", "superjson": "^2.2.1", "tinyexec": "^0.3.2", "uncrypto": "^0.1.3", "zod": "3.25.76", "zod-error": "1.5.0", "zod-validation-error": "^1.5.0" } }, "sha512-c5myttkNhqaqvLlEz3ttE1qEsULlD6ILBge5FAfEtMv9HVS/pNlgvMKrdFMefaGO/bE4HoxrNGdJsY683Kq32w=="], + "@trigger.dev/core": ["@trigger.dev/core@4.1.2", "", { "dependencies": { "@bugsnag/cuid": "^3.1.1", "@electric-sql/client": "1.0.14", "@google-cloud/precise-date": "^4.0.0", "@jsonhero/path": "^1.0.21", "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/exporter-logs-otlp-http": "0.203.0", "@opentelemetry/exporter-trace-otlp-http": "0.203.0", "@opentelemetry/instrumentation": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-trace-base": "2.0.1", "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "1.36.0", "@s2-dev/streamstore": "0.17.3", "dequal": "^2.0.3", "eventsource": "^3.0.5", "eventsource-parser": "^3.0.0", "execa": "^8.0.1", "humanize-duration": "^3.27.3", "jose": "^5.4.0", "nanoid": "3.3.8", "prom-client": "^15.1.0", "socket.io": "4.7.4", "socket.io-client": "4.7.5", "std-env": "^3.8.1", "superjson": "^2.2.1", "tinyexec": "^0.3.2", "uncrypto": "^0.1.3", "zod": "3.25.76", "zod-error": "1.5.0", "zod-validation-error": "^1.5.0" } }, "sha512-pQX1FA5zwB5jkcH2JGwpJllsA33t6sFI4dZKsPShrAKAdxb7/JHtcJwxeJSFJygxo4j4tP+o0wr6kX/lUpYCqg=="], - "@trigger.dev/sdk": ["@trigger.dev/sdk@4.0.4", "", { "dependencies": { "@opentelemetry/api": "1.9.0", "@opentelemetry/semantic-conventions": "1.36.0", "@trigger.dev/core": "4.0.4", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", "evt": "^2.4.13", "slug": "^6.0.0", "ulid": "^2.3.0", "uncrypto": "^0.1.3", "uuid": "^9.0.0", "ws": "^8.11.0" }, "peerDependencies": { "ai": "^4.2.0 || ^5.0.0", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai"] }, "sha512-54krRw9SN1CGm5u17JBzu0hNzRf1u37jKbSFFngPJjUOltOgi/owey5+KNu1rGthabhOBK2VKzvKEd4sn08RCA=="], + "@trigger.dev/sdk": ["@trigger.dev/sdk@4.1.2", "", { "dependencies": { "@opentelemetry/api": "1.9.0", "@opentelemetry/semantic-conventions": "1.36.0", "@trigger.dev/core": "4.1.2", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", "evt": "^2.4.13", "slug": "^6.0.0", "ulid": "^2.3.0", "uncrypto": "^0.1.3", "uuid": "^9.0.0", "ws": "^8.11.0" }, "peerDependencies": { "ai": "^4.2.0 || ^5.0.0", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai"] }, "sha512-BQLcskVAlUvOM4jN91PNpXtcmgjsHOL8IFes3aEt/fnBfnDRzRNIUlO+H4KkBy5/66pAOgODLmevMSCKd0keYw=="], "@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="], @@ -1586,6 +1593,8 @@ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -1784,6 +1793,8 @@ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], @@ -1796,6 +1807,8 @@ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], "detect-libc": ["detect-libc@2.1.1", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="], @@ -1850,10 +1863,14 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.227", "", {}, "sha512-ITxuoPfJu3lsNWUi2lBM2PaBPYgH3uqmxut5vmBxgYvyI4AlJ6P3Cai1O76mOrkJCBzq0IxWg/NtqOrpu/0gKA=="], "emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="], + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], @@ -1948,6 +1965,8 @@ "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + "fast-content-type-parse": ["fast-content-type-parse@2.0.1", "", {}, "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="], "fast-copy": ["fast-copy@3.0.2", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="], @@ -2040,6 +2059,8 @@ "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], + "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "glob": ["glob@11.0.3", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA=="], @@ -2536,6 +2557,8 @@ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + "node-forge": ["node-forge@1.3.1", "", {}, "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="], "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], @@ -2572,6 +2595,8 @@ "officeparser": ["officeparser@5.2.1", "", { "dependencies": { "@xmldom/xmldom": "^0.8.10", "concat-stream": "^2.0.0", "file-type": "^16.5.4", "node-ensure": "^0.0.0", "pdfjs-dist": "^5.3.31", "yauzl": "^3.1.3" }, "bin": { "officeparser": "officeParser.js" } }, "sha512-APK7/nlaeW3Q/HmzphazVlyktLoi2VfnBd8rrV1JptJuNpD6HtJIWw4h+c2pnlu1wp+ZyBjPxDFcHbC+3NUOpA=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + "ollama-ai-provider": ["ollama-ai-provider@1.2.0", "", { "dependencies": { "@ai-sdk/provider": "^1.0.0", "@ai-sdk/provider-utils": "^2.0.0", "partial-json": "0.1.7" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-jTNFruwe3O/ruJeppI/quoOUxG7NA6blG3ZyQj3lei4+NnJo7bi3eIRWqlVpRlu/mbzbFXeJSBuYQWF6pzGKww=="], "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], @@ -2646,6 +2671,8 @@ "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], @@ -2726,6 +2753,8 @@ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], "pvutils": ["pvutils@1.1.3", "", {}, "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ=="], @@ -2740,6 +2769,8 @@ "raw-body": ["raw-body@3.0.1", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.7.0", "unpipe": "1.0.0" } }, "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], "react-colorful": ["react-colorful@5.6.1", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw=="], @@ -3752,6 +3783,12 @@ "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "c12/confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + + "c12/jiti": ["jiti@2.6.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ=="], + + "c12/pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + "cheerio/htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="], "cli-truncate/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], From e836ad235b3a4e444d522ca962c5db5ebf1c8ced Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 15:08:27 -0800 Subject: [PATCH 4/9] add doc update --- .../docs/en/variables/environment-variables.mdx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/docs/content/docs/en/variables/environment-variables.mdx b/apps/docs/content/docs/en/variables/environment-variables.mdx index 12123678c56..b4bc264aa57 100644 --- a/apps/docs/content/docs/en/variables/environment-variables.mdx +++ b/apps/docs/content/docs/en/variables/environment-variables.mdx @@ -66,17 +66,16 @@ To reference environment variables in your workflows, use the `{{}}` notation. W height={350} /> -## Variable Precedence +## How Variables are Resolved -When you have both personal and workspace variables with the same name: +**Workspace variables always take precedence** over personal variables, regardless of who runs the workflow. -1. **Workspace variables take precedence** over personal variables -2. This prevents naming conflicts and ensures consistent behavior across team workflows -3. If a workspace variable exists, the personal variable with the same name is ignored +When no workspace variable exists for a key, personal variables are used: +- **Manual runs (UI)**: Your personal variables +- **Automated runs (API, webhook, schedule, deployed chat)**: Workflow owner's personal variables - -Choose variable names carefully to avoid unintended overrides. Consider prefixing personal variables with your initials or workspace variables with the project name. - + +Personal variables are best for testing. Use workspace variables for production workflows. ## Security Best Practices From 7e943ec7eefaaf08fccb7297f3a740b82b3ddf58 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 15:17:27 -0800 Subject: [PATCH 5/9] fix build --- .../en/variables/environment-variables.mdx | 3 ++- .../components/environment/environment.tsx | 19 ++++++++++--------- apps/sim/executor/execution/snapshot.ts | 4 ---- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/docs/content/docs/en/variables/environment-variables.mdx b/apps/docs/content/docs/en/variables/environment-variables.mdx index b4bc264aa57..76fb1946c20 100644 --- a/apps/docs/content/docs/en/variables/environment-variables.mdx +++ b/apps/docs/content/docs/en/variables/environment-variables.mdx @@ -75,7 +75,8 @@ When no workspace variable exists for a key, personal variables are used: - **Automated runs (API, webhook, schedule, deployed chat)**: Workflow owner's personal variables -Personal variables are best for testing. Use workspace variables for production workflows. +Personal variables are best for testing. Use workspace variables for production workflows. + ## Security Best Practices diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/environment/environment.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/environment/environment.tsx index be86a6f55da..409e2e368e1 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/environment/environment.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/environment/environment.tsx @@ -27,6 +27,7 @@ const logger = createLogger('EnvironmentVariables') const GRID_COLS = 'grid grid-cols-[minmax(0,1fr)_8px_minmax(0,1fr)_auto] items-center' const ENV_VAR_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/ +const PERSONAL_VAR_INDEX_OFFSET = 1000 const PRIMARY_BUTTON_STYLES = '!bg-[var(--brand-tertiary-2)] !text-[var(--text-inverse)] hover:!bg-[var(--brand-tertiary-2)]/90' @@ -92,7 +93,7 @@ function VariableRow({ value={envKey} onChange={(e) => isNew && onKeyChange(e.target.value)} placeholder='API_KEY' - name={`env_key_${rowIndex}_${Math.random()}`} + name={`env_key_${rowIndex}`} autoComplete='off' autoCapitalize='off' spellCheck='false' @@ -115,7 +116,7 @@ function VariableRow({ onBlur={onValueBlur} placeholder={isConflict ? 'Workspace override active' : 'Enter value'} disabled={!isNew || isConflict} - name={`env_value_${rowIndex}_${Math.random()}`} + name={`env_value_${rowIndex}`} autoComplete='off' autoCapitalize='off' spellCheck='false' @@ -255,8 +256,8 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment ...v, id: generateRowId(), })) - setInitialPersonalVars(JSON.parse(JSON.stringify(vars))) - setPersonalVars(JSON.parse(JSON.stringify(vars))) + setInitialPersonalVars(structuredClone(vars)) + setPersonalVars(structuredClone(vars)) }, [personalEnvData]) useEffect(() => { @@ -270,8 +271,8 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment value, id: generateRowId(), })) - setInitialWorkspaceVars(JSON.parse(JSON.stringify(vars))) - setWorkspaceVars(JSON.parse(JSON.stringify(vars))) + setInitialWorkspaceVars(structuredClone(vars)) + setWorkspaceVars(structuredClone(vars)) } }, [workspaceEnvData]) @@ -351,8 +352,8 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment ) const handleCancel = useCallback(() => { - setPersonalVars(JSON.parse(JSON.stringify(initialPersonalVars))) - setWorkspaceVars(JSON.parse(JSON.stringify(initialWorkspaceVars))) + setPersonalVars(structuredClone(initialPersonalVars)) + setWorkspaceVars(structuredClone(initialWorkspaceVars)) setShowUnsavedChanges(false) pendingProceedCallback.current?.() pendingProceedCallback.current = null @@ -607,7 +608,7 @@ export function EnvironmentVariables({ registerBeforeLeaveHandler }: Environment value={v.value} isNew={true} focusedValueIndex={focusedValueIndex} - rowIndex={originalIndex + 1000} + rowIndex={originalIndex + PERSONAL_VAR_INDEX_OFFSET} onKeyChange={(val) => updatePersonalVar(originalIndex, 'key', val)} onValueChange={(val) => updatePersonalVar(originalIndex, 'value', val)} onValueFocus={handleValueFocus} diff --git a/apps/sim/executor/execution/snapshot.ts b/apps/sim/executor/execution/snapshot.ts index 454de3b1e21..fa7e845c8db 100644 --- a/apps/sim/executor/execution/snapshot.ts +++ b/apps/sim/executor/execution/snapshot.ts @@ -64,10 +64,6 @@ export class ExecutionSnapshot { public readonly state?: SerializableExecutionState ) {} - get environmentVariables(): Record { - return {} - } - toJSON(): string { return JSON.stringify({ metadata: this.metadata, From f440af7c49eda87129701c666dde6a50904db6f2 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 16:48:54 -0800 Subject: [PATCH 6/9] fix client session pass through" --- .../w/[workflowId]/hooks/use-workflow-execution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index 042be27bf45..c0e8ebd6833 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -879,7 +879,7 @@ export function useWorkflowExecution() { selectedOutputs, triggerType: overrideTriggerType || 'manual', useDraftState: true, - // Pass diff workflow state if available for execution + isClientSession: true, workflowStateOverride: executionWorkflowState ? { blocks: executionWorkflowState.blocks, From a010ebd8f94c6ed3f8c1ecc2c8f22693584c028f Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 16:50:22 -0800 Subject: [PATCH 7/9] add type change --- apps/sim/hooks/use-execution-stream.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sim/hooks/use-execution-stream.ts b/apps/sim/hooks/use-execution-stream.ts index f18837c8113..f5fe2119081 100644 --- a/apps/sim/hooks/use-execution-stream.ts +++ b/apps/sim/hooks/use-execution-stream.ts @@ -61,6 +61,7 @@ export interface ExecuteStreamOptions { startBlockId?: string triggerType?: string useDraftState?: boolean + isClientSession?: boolean workflowStateOverride?: { blocks: Record edges: any[] From 3a15516369a3e17cb3e24e06c7603456c89eb59f Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 20:58:29 -0800 Subject: [PATCH 8/9] fix env var with hitl --- apps/sim/app/api/workflows/[id]/execute/route.ts | 3 +++ apps/sim/background/schedule-execution.ts | 2 ++ apps/sim/background/webhook-execution.ts | 4 ++++ apps/sim/background/workflow-execution.ts | 2 ++ apps/sim/executor/execution/executor.ts | 1 + apps/sim/executor/execution/snapshot-serializer.ts | 1 + apps/sim/executor/execution/snapshot.ts | 1 + apps/sim/lib/workflows/executor/execution-core.ts | 9 ++++++++- .../lib/workflows/executor/human-in-the-loop-manager.ts | 1 + 9 files changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 1bb066255ec..7cb248fab7b 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -92,6 +92,7 @@ export async function executeWorkflow( workflowId, workspaceId: workflow.workspaceId, userId: actorUserId, + workflowUserId: workflow.userId, triggerType, useDraftState: false, startTime: new Date().toISOString(), @@ -505,6 +506,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: workspaceId: workflow.workspaceId ?? undefined, userId: actorUserId, sessionUserId: isClientSession ? userId : undefined, + workflowUserId: workflow.userId, triggerType, useDraftState: shouldUseDraftState, startTime: new Date().toISOString(), @@ -772,6 +774,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: workspaceId: workflow.workspaceId ?? undefined, userId: actorUserId, sessionUserId: isClientSession ? userId : undefined, + workflowUserId: workflow.userId, triggerType, useDraftState: shouldUseDraftState, startTime: new Date().toISOString(), diff --git a/apps/sim/background/schedule-execution.ts b/apps/sim/background/schedule-execution.ts index 1fe793cb28a..d5acb38c133 100644 --- a/apps/sim/background/schedule-execution.ts +++ b/apps/sim/background/schedule-execution.ts @@ -241,6 +241,8 @@ async function runWorkflowExecution({ workflowId: payload.workflowId, workspaceId: workflowRecord.workspaceId || '', userId: actorUserId, + sessionUserId: undefined, + workflowUserId: workflowRecord.userId, triggerType: 'schedule', triggerBlockId: payload.blockId || undefined, useDraftState: false, diff --git a/apps/sim/background/webhook-execution.ts b/apps/sim/background/webhook-execution.ts index 1afe0aba331..07bab96df7f 100644 --- a/apps/sim/background/webhook-execution.ts +++ b/apps/sim/background/webhook-execution.ts @@ -222,6 +222,8 @@ async function executeWebhookJobInternal( workflowId: payload.workflowId, workspaceId, userId: payload.userId, + sessionUserId: undefined, + workflowUserId: workflow.userId, triggerType: payload.provider || 'webhook', triggerBlockId: payload.blockId, useDraftState: false, @@ -435,6 +437,8 @@ async function executeWebhookJobInternal( workflowId: payload.workflowId, workspaceId, userId: payload.userId, + sessionUserId: undefined, + workflowUserId: workflow.userId, triggerType: payload.provider || 'webhook', triggerBlockId: payload.blockId, useDraftState: false, diff --git a/apps/sim/background/workflow-execution.ts b/apps/sim/background/workflow-execution.ts index eb7f91b7716..96475787b9f 100644 --- a/apps/sim/background/workflow-execution.ts +++ b/apps/sim/background/workflow-execution.ts @@ -74,6 +74,8 @@ export async function executeWorkflowJob(payload: WorkflowExecutionPayload) { workflowId, workspaceId, userId: actorUserId, + sessionUserId: undefined, + workflowUserId: workflow.userId, triggerType: payload.triggerType || 'api', useDraftState: false, startTime: new Date().toISOString(), diff --git a/apps/sim/executor/execution/executor.ts b/apps/sim/executor/execution/executor.ts index 14ed7aa9a40..fad1ffb1ade 100644 --- a/apps/sim/executor/execution/executor.ts +++ b/apps/sim/executor/execution/executor.ts @@ -121,6 +121,7 @@ export class DAGExecutor { blockStates: state.getBlockStates(), blockLogs: snapshotState?.blockLogs ?? [], metadata: { + ...this.contextExtensions.metadata, startTime: new Date().toISOString(), duration: 0, useDraftState: this.contextExtensions.isDeployedContext !== true, diff --git a/apps/sim/executor/execution/snapshot-serializer.ts b/apps/sim/executor/execution/snapshot-serializer.ts index e99d098de30..b8a2392c30b 100644 --- a/apps/sim/executor/execution/snapshot-serializer.ts +++ b/apps/sim/executor/execution/snapshot-serializer.ts @@ -102,6 +102,7 @@ export function serializePauseSnapshot( workspaceId: context.workspaceId, userId: metadataFromContext?.userId ?? '', sessionUserId: metadataFromContext?.sessionUserId, + workflowUserId: metadataFromContext?.workflowUserId, triggerType: metadataFromContext?.triggerType ?? 'manual', triggerBlockId: triggerBlockIds[0], useDraftState, diff --git a/apps/sim/executor/execution/snapshot.ts b/apps/sim/executor/execution/snapshot.ts index fa7e845c8db..667d551b33c 100644 --- a/apps/sim/executor/execution/snapshot.ts +++ b/apps/sim/executor/execution/snapshot.ts @@ -8,6 +8,7 @@ export interface ExecutionMetadata { workspaceId?: string userId: string sessionUserId?: string + workflowUserId?: string triggerType: string triggerBlockId?: string useDraftState: boolean diff --git a/apps/sim/lib/workflows/executor/execution-core.ts b/apps/sim/lib/workflows/executor/execution-core.ts index 9ea07a0f62f..e94a855d252 100644 --- a/apps/sim/lib/workflows/executor/execution-core.ts +++ b/apps/sim/lib/workflows/executor/execution-core.ts @@ -152,7 +152,13 @@ export async function executeWorkflowCore( const mergedStates = mergeSubblockState(blocks) const personalEnvUserId = - metadata.isClientSession && metadata.sessionUserId ? metadata.sessionUserId : workflow.userId + metadata.isClientSession && metadata.sessionUserId + ? metadata.sessionUserId + : metadata.workflowUserId + + if (!personalEnvUserId) { + throw new Error('Missing workflowUserId in execution metadata') + } const { personalEncrypted, workspaceEncrypted, personalDecrypted, workspaceDecrypted } = await getPersonalAndWorkspaceEnv(personalEnvUserId, providedWorkspaceId) @@ -302,6 +308,7 @@ export async function executeWorkflowCore( remainingEdges: snapshot.state?.remainingEdges, dagIncomingEdges: snapshot.state?.dagIncomingEdges, snapshotState: snapshot.state, + metadata, } const executorInstance = new Executor({ diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts index 0670bf4d106..db30f81766e 100644 --- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts +++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts @@ -640,6 +640,7 @@ export class PauseResumeManager { startTime: new Date().toISOString(), userId, sessionUserId: baseSnapshot.metadata.sessionUserId, + workflowUserId: baseSnapshot.metadata.workflowUserId, useDraftState: baseSnapshot.metadata.useDraftState, isClientSession: baseSnapshot.metadata.isClientSession, resumeFromSnapshot: true, From 02396d56bf41d07938a9f541f6fc18d6526d03e9 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 4 Dec 2025 21:04:10 -0800 Subject: [PATCH 9/9] fix types --- apps/sim/executor/execution/types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/sim/executor/execution/types.ts b/apps/sim/executor/execution/types.ts index 81ec7773c82..041efa6e4af 100644 --- a/apps/sim/executor/execution/types.ts +++ b/apps/sim/executor/execution/types.ts @@ -1,3 +1,4 @@ +import type { ExecutionMetadata, SerializableExecutionState } from '@/executor/execution/snapshot' import type { BlockState, NormalizedBlockOutput } from '@/executor/types' import type { SubflowType } from '@/stores/workflows/workflow/types' @@ -19,7 +20,8 @@ export interface ContextExtensions { targetHandle?: string }> dagIncomingEdges?: Record - snapshotState?: import('@/executor/execution/snapshot').SerializableExecutionState + snapshotState?: SerializableExecutionState + metadata?: ExecutionMetadata onStream?: (streamingExecution: unknown) => Promise onBlockStart?: ( blockId: string,