Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions apps/sim/lib/logs/execution/snapshot/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ describe('SnapshotService', () => {
type: 'agent',
position: { x: 100, y: 200 },

subBlocks: {},
subBlocks: {
prompt: {
id: 'prompt',
type: 'short-input',
value: 'Hello world',
},
},
outputs: {},
enabled: true,
horizontalHandles: true,
Expand All @@ -104,8 +110,14 @@ describe('SnapshotService', () => {
blocks: {
block1: {
...baseState.blocks.block1,
// Different block state - we can change outputs to make it different
outputs: { response: { type: 'string', description: 'different result' } },
// Different subBlock value - this is a meaningful change
subBlocks: {
prompt: {
id: 'prompt',
type: 'short-input',
value: 'Different prompt',
},
},
},
},
}
Expand Down
81 changes: 8 additions & 73 deletions apps/sim/lib/logs/execution/snapshot/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@ import type {
WorkflowExecutionSnapshotInsert,
WorkflowState,
} from '@/lib/logs/types'
import {
normalizedStringify,
normalizeEdge,
normalizeValue,
sortEdges,
} from '@/lib/workflows/comparison'
import { normalizedStringify, normalizeWorkflowState } from '@/lib/workflows/comparison'

const logger = createLogger('SnapshotService')

Expand All @@ -38,7 +33,9 @@ export class SnapshotService implements ISnapshotService {

const existingSnapshot = await this.getSnapshotByHash(workflowId, stateHash)
if (existingSnapshot) {
logger.debug(`Reusing existing snapshot for workflow ${workflowId} with hash ${stateHash}`)
logger.info(
`Reusing existing snapshot for workflow ${workflowId} (hash: ${stateHash.slice(0, 12)}...)`
)
return {
snapshot: existingSnapshot,
isNew: false,
Expand All @@ -59,8 +56,9 @@ export class SnapshotService implements ISnapshotService {
.values(snapshotData)
.returning()

logger.debug(`Created new snapshot for workflow ${workflowId} with hash ${stateHash}`)
logger.debug(`Stored full state with ${Object.keys(state.blocks || {}).length} blocks`)
logger.info(
`Created new snapshot for workflow ${workflowId} (hash: ${stateHash.slice(0, 12)}..., blocks: ${Object.keys(state.blocks || {}).length})`
)
return {
snapshot: {
...newSnapshot,
Expand Down Expand Up @@ -112,7 +110,7 @@ export class SnapshotService implements ISnapshotService {
}

computeStateHash(state: WorkflowState): string {
const normalizedState = this.normalizeStateForHashing(state)
const normalizedState = normalizeWorkflowState(state)
const stateString = normalizedStringify(normalizedState)
return createHash('sha256').update(stateString).digest('hex')
}
Expand All @@ -130,69 +128,6 @@ export class SnapshotService implements ISnapshotService {
logger.info(`Cleaned up ${deletedCount} orphaned snapshots older than ${olderThanDays} days`)
return deletedCount
}

private normalizeStateForHashing(state: WorkflowState): any {
// 1. Normalize and sort edges
const normalizedEdges = sortEdges((state.edges || []).map(normalizeEdge))

// 2. Normalize blocks
const normalizedBlocks: Record<string, any> = {}

for (const [blockId, block] of Object.entries(state.blocks || {})) {
const { position, layout, height, ...blockWithoutLayoutFields } = block

// Also exclude width/height from data object (container dimensions from autolayout)
const {
width: _dataWidth,
height: _dataHeight,
...dataRest
} = blockWithoutLayoutFields.data || {}

// Normalize subBlocks
const subBlocks = blockWithoutLayoutFields.subBlocks || {}
const normalizedSubBlocks: Record<string, any> = {}

for (const [subBlockId, subBlock] of Object.entries(subBlocks)) {
const value = subBlock.value ?? null

normalizedSubBlocks[subBlockId] = {
type: subBlock.type,
value: normalizeValue(value),
...Object.fromEntries(
Object.entries(subBlock).filter(([key]) => key !== 'value' && key !== 'type')
),
}
}

normalizedBlocks[blockId] = {
...blockWithoutLayoutFields,
data: dataRest,
subBlocks: normalizedSubBlocks,
}
}

// 3. Normalize loops and parallels
const normalizedLoops: Record<string, any> = {}
for (const [loopId, loop] of Object.entries(state.loops || {})) {
normalizedLoops[loopId] = normalizeValue(loop)
}

const normalizedParallels: Record<string, any> = {}
for (const [parallelId, parallel] of Object.entries(state.parallels || {})) {
normalizedParallels[parallelId] = normalizeValue(parallel)
}

// 4. Normalize variables (if present)
const normalizedVariables = state.variables ? normalizeValue(state.variables) : undefined

return {
blocks: normalizedBlocks,
edges: normalizedEdges,
loops: normalizedLoops,
parallels: normalizedParallels,
...(normalizedVariables !== undefined && { variables: normalizedVariables }),
}
}
}

export const snapshotService = new SnapshotService()
Loading