diff --git a/app/api/workflow/[id]/execute/route.ts b/app/api/workflow/[id]/execute/route.ts index 7bfa9723a9..9488bc6290 100644 --- a/app/api/workflow/[id]/execute/route.ts +++ b/app/api/workflow/[id]/execute/route.ts @@ -113,7 +113,7 @@ async function executeWorkflow(workflow: any, input?: any) { // Serialize and execute the workflow const serializedWorkflow = new Serializer().serializeWorkflow(mergedStates, edges, loops) - const executor = new Executor(serializedWorkflow, currentBlockStates, decryptedEnvVars) + const executor = new Executor(serializedWorkflow, currentBlockStates, decryptedEnvVars, input) const result = await executor.execute(workflowId) // Log each execution step diff --git a/blocks/blocks/starter.ts b/blocks/blocks/starter.ts index ff16c4197b..f3cec60d16 100644 --- a/blocks/blocks/starter.ts +++ b/blocks/blocks/starter.ts @@ -131,15 +131,12 @@ export const StarterBlock: BlockConfig = { access: [], }, inputs: { - code: { type: 'string', required: true }, - executionMode: { type: 'string', required: true }, + input: { type: 'json', required: false }, }, outputs: { response: { type: { - result: 'any', - stdout: 'string', - executionTime: 'number', + input: 'any', }, }, }, diff --git a/executor/handlers.ts b/executor/handlers.ts index 8ef44e071a..682380d8c3 100644 --- a/executor/handlers.ts +++ b/executor/handlers.ts @@ -103,12 +103,15 @@ export class AgentBlockHandler implements BlockHandler { .filter((t): t is NonNullable => t !== null) : [] + // Ensure context is properly formatted for the provider const response = await executeProviderRequest(providerId, { model, systemPrompt: inputs.systemPrompt, context: Array.isArray(inputs.context) ? JSON.stringify(inputs.context, null, 2) - : inputs.context, + : typeof inputs.context === 'string' + ? inputs.context + : JSON.stringify(inputs.context, null, 2), tools: formattedTools.length > 0 ? formattedTools : undefined, temperature: inputs.temperature, maxTokens: inputs.maxTokens, diff --git a/executor/index.ts b/executor/index.ts index 56fbb8e430..588e9063ab 100644 --- a/executor/index.ts +++ b/executor/index.ts @@ -28,13 +28,16 @@ export class Executor { private loopManager: LoopManager private pathTracker: PathTracker private blockHandlers: BlockHandler[] + private workflowInput: any constructor( private workflow: SerializedWorkflow, private initialBlockStates: Record = {}, - private environmentVariables: Record = {} + private environmentVariables: Record = {}, + workflowInput?: any ) { this.validateWorkflow() + this.workflowInput = workflowInput || {} this.resolver = new InputResolver(workflow, environmentVariables) this.loopManager = new LoopManager(workflow.loops || {}) @@ -211,11 +214,22 @@ export class Executor { const starterBlock = this.workflow.blocks.find((block) => block.metadata?.id === 'starter') if (starterBlock) { + // Initialize the starter block with the workflow input + const starterOutput = { + response: { + type: { + input: this.workflowInput, + }, + }, + } + context.blockStates.set(starterBlock.id, { - output: { response: { result: true } }, + output: starterOutput, executed: true, executionTime: 0, }) + + // Mark the starter block as executed and add its connections to the active path context.executedBlocks.add(starterBlock.id) const connectedToStarter = this.workflow.connections @@ -371,6 +385,15 @@ export class Executor { throw new Error(`Block ${blockId} not found`) } + // Special case for starter block - it's already been initialized in createExecutionContext + // This ensures we don't re-execute the starter block and just return its existing state + if (block.metadata?.id === 'starter') { + const starterState = context.blockStates.get(blockId) + if (starterState) { + return starterState.output as NormalizedBlockOutput + } + } + const blockLog = this.createBlockLog(block) const addConsole = useConsoleStore.getState().addConsole diff --git a/executor/resolver.ts b/executor/resolver.ts index 30b9419e8f..ab999f83bb 100644 --- a/executor/resolver.ts +++ b/executor/resolver.ts @@ -14,12 +14,27 @@ export class InputResolver { ) { // Create maps for efficient lookups this.blockById = new Map(workflow.blocks.map((block) => [block.id, block])) + + // Initialize the normalized name map this.blockByNormalizedName = new Map( workflow.blocks.map((block) => [ block.metadata?.name ? this.normalizeBlockName(block.metadata.name) : block.id, block, ]) ) + + // Add special handling for the starter block - allow referencing it as "start" + const starterBlock = workflow.blocks.find((block) => block.metadata?.id === 'starter') + if (starterBlock) { + this.blockByNormalizedName.set('start', starterBlock) + // Also add the normalized actual name if it exists + if (starterBlock.metadata?.name) { + this.blockByNormalizedName.set( + this.normalizeBlockName(starterBlock.metadata.name), + starterBlock + ) + } + } } /** @@ -99,15 +114,53 @@ export class InputResolver { const path = match.slice(1, -1) const [blockRef, ...pathParts] = path.split('.') - let sourceBlock = this.blockById.get(blockRef) + // Special case for "start" references + // This allows users to reference the starter block using + // regardless of the actual name of the starter block + if (blockRef.toLowerCase() === 'start') { + // Find the starter block + const starterBlock = this.workflow.blocks.find((block) => block.metadata?.id === 'starter') + if (starterBlock) { + const blockState = context.blockStates.get(starterBlock.id) + if (blockState) { + // Navigate through the path parts + let replacementValue: any = blockState.output + for (const part of pathParts) { + if (!replacementValue || typeof replacementValue !== 'object') { + throw new Error(`Invalid path "${part}" in "${path}" for starter block.`) + } + replacementValue = replacementValue[part] + if (replacementValue === undefined) { + throw new Error(`No value found at path "${path}" in starter block.`) + } + } + // Format the value + const formattedValue = + typeof replacementValue === 'object' + ? JSON.stringify(replacementValue) + : String(replacementValue) + + resolvedValue = resolvedValue.replace(match, formattedValue) + continue + } + } + } + + // Standard block reference resolution + let sourceBlock = this.blockById.get(blockRef) if (!sourceBlock) { const normalizedRef = this.normalizeBlockName(blockRef) sourceBlock = this.blockByNormalizedName.get(normalizedRef) } if (!sourceBlock) { - throw new Error(`Block reference "${blockRef}" was not found.`) + // Provide a more helpful error message with available block names + const availableBlocks = Array.from(this.blockByNormalizedName.keys()).join(', ') + throw new Error( + `Block reference "${blockRef}" was not found. Available blocks: ${availableBlocks}. ` + + `For the starter block, try using "start" or the exact block name.` + ) } if (sourceBlock.enabled === false) { @@ -156,6 +209,7 @@ export class InputResolver { `Invalid path "${part}" in "${path}" for block "${currentBlock.metadata?.name || currentBlock.id}".` ) } + replacementValue = replacementValue[part] if (replacementValue === undefined) { diff --git a/stores/workflow/registry/store.ts b/stores/workflow/registry/store.ts index 1ef9fb3776..84b6782f19 100644 --- a/stores/workflow/registry/store.ts +++ b/stores/workflow/registry/store.ts @@ -176,9 +176,7 @@ export const useWorkflowRegistry = create()( outputs: { response: { type: { - result: 'any', - stdout: 'string', - executionTime: 'number', + input: 'any', }, }, },