Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 0 additions & 24 deletions apps/sim/lib/copilot/orchestrator/tool-executor/vfs-tools.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ 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 {
getExecutionState,
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,
Expand Down Expand Up @@ -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) }
Expand Down
6 changes: 1 addition & 5 deletions apps/sim/lib/copilot/process-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -757,7 +753,7 @@ export async function resolveActiveResourceContext(
resourceId,
undefined,
'@active_resource',
'workflow',
'current_workflow',
undefined,
chatId
)
Expand Down
49 changes: 11 additions & 38 deletions apps/sim/lib/copilot/tools/server/workflow/edit-workflow/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -98,33 +93,19 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, unknown>
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
Expand Down Expand Up @@ -318,20 +299,12 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, unknown>
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('; ')}`,
Expand Down
Loading