diff --git a/apps/sim/lib/copilot/orchestrator/tool-executor/vfs-tools.ts b/apps/sim/lib/copilot/orchestrator/tool-executor/vfs-tools.ts index bb3b3671d2f..c2e37759436 100644 --- a/apps/sim/lib/copilot/orchestrator/tool-executor/vfs-tools.ts +++ b/apps/sim/lib/copilot/orchestrator/tool-executor/vfs-tools.ts @@ -1,11 +1,9 @@ import { createLogger } from '@sim/logger' import type { ExecutionContext, ToolCallResult } from '@/lib/copilot/orchestrator/types' import { getOrMaterializeVFS } from '@/lib/copilot/vfs' -import { upsertWorkflowReadHashForSanitizedState } from '@/lib/copilot/workflow-read-hashes' import { listChatUploads, readChatUpload } from './upload-file-reader' const logger = createLogger('VfsTools') -const WORKFLOW_STATE_PATH_REGEX = /^workflows\/[^/]+\/state\.json$/ export async function executeVfsGrep( params: Record, @@ -145,28 +143,6 @@ export async function executeVfsRead( return { success: false, error: `File not found: ${path}.${hint}` } } logger.debug('vfs_read result', { path, totalLines: result.totalLines }) - if (context.chatId && WORKFLOW_STATE_PATH_REGEX.test(path)) { - try { - const fullState = vfs.read(path) - const fullMeta = vfs.read(path.replace(/state\.json$/, 'meta.json')) - if (fullState?.content && fullMeta?.content) { - const workflowMeta = JSON.parse(fullMeta.content) as { id?: string } - const sanitizedState = JSON.parse(fullState.content) - if (workflowMeta.id) { - await upsertWorkflowReadHashForSanitizedState( - context.chatId, - workflowMeta.id, - sanitizedState - ) - } - } - } catch (hashErr) { - logger.warn('Failed to persist workflow read hash from VFS read', { - path, - error: hashErr instanceof Error ? hashErr.message : String(hashErr), - }) - } - } return { success: true, output: result, diff --git a/apps/sim/lib/copilot/orchestrator/tool-executor/workflow-tools/mutations.ts b/apps/sim/lib/copilot/orchestrator/tool-executor/workflow-tools/mutations.ts index 4d0adf9fe0a..ad43e8f5ab2 100644 --- a/apps/sim/lib/copilot/orchestrator/tool-executor/workflow-tools/mutations.ts +++ b/apps/sim/lib/copilot/orchestrator/tool-executor/workflow-tools/mutations.ts @@ -2,7 +2,6 @@ import crypto from 'crypto' import { createLogger } from '@sim/logger' import { createWorkspaceApiKey } from '@/lib/api-key/auth' import type { ExecutionContext, ToolCallResult } from '@/lib/copilot/orchestrator/types' -import { upsertWorkflowReadHashForWorkflowState } from '@/lib/copilot/workflow-read-hashes' import { generateRequestId } from '@/lib/core/utils/request' import { executeWorkflow } from '@/lib/workflows/executor/execute-workflow' import { @@ -10,6 +9,7 @@ import { getLatestExecutionState, } from '@/lib/workflows/executor/execution-state' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' +import { sanitizeForCopilot } from '@/lib/workflows/sanitization/json-sanitizer' import { createFolderRecord, createWorkflowRecord, @@ -132,36 +132,26 @@ export async function executeCreateWorkflow( folderId, }) + const normalized = await loadWorkflowFromNormalizedTables(result.workflowId) + let copilotSanitizedWorkflowState: unknown + if (normalized) { + copilotSanitizedWorkflowState = sanitizeForCopilot({ + blocks: normalized.blocks || {}, + edges: normalized.edges || [], + loops: normalized.loops || {}, + parallels: normalized.parallels || {}, + } as any) + } + return { success: true, - output: await (async () => { - let workflowReadHash: string | undefined - if (context.chatId) { - assertWorkflowMutationNotAborted(context) - const normalized = await loadWorkflowFromNormalizedTables(result.workflowId) - if (normalized) { - const seeded = await upsertWorkflowReadHashForWorkflowState( - context.chatId, - result.workflowId, - { - blocks: normalized.blocks || {}, - edges: normalized.edges || [], - loops: normalized.loops || {}, - parallels: normalized.parallels || {}, - } - ) - workflowReadHash = seeded.hash - } - } - - return { - workflowId: result.workflowId, - workflowName: result.name, - workspaceId: result.workspaceId, - folderId: result.folderId, - ...(workflowReadHash ? { workflowReadHash } : {}), - } - })(), + output: { + workflowId: result.workflowId, + workflowName: result.name, + workspaceId: result.workspaceId, + folderId: result.folderId, + ...(copilotSanitizedWorkflowState ? { copilotSanitizedWorkflowState } : {}), + }, } } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) } diff --git a/apps/sim/lib/copilot/process-contents.ts b/apps/sim/lib/copilot/process-contents.ts index fa60d6f6709..5378b678f5c 100644 --- a/apps/sim/lib/copilot/process-contents.ts +++ b/apps/sim/lib/copilot/process-contents.ts @@ -7,7 +7,6 @@ import { serializeTableMeta, serializeWorkflowMeta, } from '@/lib/copilot/vfs/serializers' -import { upsertWorkflowReadHashForSanitizedState } from '@/lib/copilot/workflow-read-hashes' import { getAllowedIntegrationsFromEnv } from '@/lib/core/config/feature-flags' import { getTableById } from '@/lib/table/service' import { canAccessTemplate } from '@/lib/templates/permissions' @@ -371,9 +370,6 @@ async function processWorkflowFromDb( parallels: normalized.parallels || {}, } const sanitizedState = sanitizeForCopilot(workflowState) - if (chatId) { - await upsertWorkflowReadHashForSanitizedState(chatId, workflowId, sanitizedState) - } const content = JSON.stringify( { workflowId, @@ -757,7 +753,7 @@ export async function resolveActiveResourceContext( resourceId, undefined, '@active_resource', - 'workflow', + 'current_workflow', undefined, chatId ) diff --git a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow/index.ts b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow/index.ts index bbf6177d49a..0f05376f527 100644 --- a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow/index.ts +++ b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow/index.ts @@ -7,11 +7,6 @@ import { type BaseServerTool, type ServerToolContext, } from '@/lib/copilot/tools/server/base-tool' -import { - computeWorkflowReadHashFromWorkflowState, - getWorkflowReadHash, - upsertWorkflowReadHashForWorkflowState, -} from '@/lib/copilot/workflow-read-hashes' import { applyTargetedLayout } from '@/lib/workflows/autolayout' import { DEFAULT_HORIZONTAL_SPACING, @@ -98,33 +93,19 @@ export const editWorkflowServerTool: BaseServerTool chatId: context.chatId, }) - if (!context.chatId) { - throw new Error( - 'Workflow has changed or was not read in this chat. Re-read the workflow before editing.' - ) - } - assertServerToolNotAborted(context) - const fromDb = await getCurrentWorkflowStateFromDb(workflowId) - const workflowState = fromDb.workflowState - const storedReadHash = await getWorkflowReadHash(context.chatId, workflowId) - if (!storedReadHash) { - throw new Error( - 'Workflow has changed or was not read in this chat. Re-read the workflow before editing.' - ) - } - - const currentReadState = computeWorkflowReadHashFromWorkflowState({ - blocks: workflowState.blocks || {}, - edges: workflowState.edges || [], - loops: workflowState.loops || {}, - parallels: workflowState.parallels || {}, - }) - if (storedReadHash !== currentReadState.hash) { - throw new Error( - 'Workflow changed since it was last read in this chat. Re-read the workflow before editing.' - ) + let workflowState: any + if (currentUserWorkflow) { + try { + workflowState = JSON.parse(currentUserWorkflow) + } catch (error) { + logger.error('Failed to parse currentUserWorkflow', error) + throw new Error('Invalid currentUserWorkflow format') + } + } else { + const fromDb = await getCurrentWorkflowStateFromDb(workflowId) + workflowState = fromDb.workflowState } // Get permission config for the user @@ -318,20 +299,12 @@ export const editWorkflowServerTool: BaseServerTool logger.info('Workflow state persisted to database', { workflowId }) const sanitizationWarnings = validation.warnings.length > 0 ? validation.warnings : undefined - assertServerToolNotAborted(context) - const updatedReadState = await upsertWorkflowReadHashForWorkflowState( - context.chatId, - workflowId, - workflowStateForDb - ) return { success: true, workflowId, workflowName: workflowName ?? 'Workflow', workflowState: { ...finalWorkflowState, blocks: layoutedBlocks }, - copilotSanitizedWorkflowState: updatedReadState.sanitizedState, - workflowReadHash: updatedReadState.hash, ...(inputErrors && { inputValidationErrors: inputErrors, inputValidationMessage: `${inputErrors.length} input(s) were rejected due to validation errors. The workflow was still updated with valid inputs only. Errors: ${inputErrors.join('; ')}`,