mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(mothership): fix edit hashing (#3711)
This commit is contained in:
committed by
GitHub
parent
4cb5e3469f
commit
9d6a7f3970
@@ -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>,
|
||||
@@ -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,
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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<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
|
||||
@@ -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('; ')}`,
|
||||
|
||||
Reference in New Issue
Block a user