Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0b9019d
v0.6.23: MCP fixes, remove local state in favor of server state, moth…
waleedlatif1 Apr 4, 2026
a54dcbe
v0.6.24: copilot feedback wiring, captcha fixes
waleedlatif1 Apr 4, 2026
28af223
v0.6.25: cloudwatch, cloudformation, live kb sync, linear fixes, post…
waleedlatif1 Apr 5, 2026
d889f32
v0.6.26: ui improvements, multiple response blocks, docx previews, ol…
waleedlatif1 Apr 5, 2026
316bc8c
v0.6.27: new triggers, mothership improvements, files archive, queuei…
waleedlatif1 Apr 7, 2026
3f508e4
v0.6.28: new docs, delete confirmation standardization, dagster integ…
waleedlatif1 Apr 7, 2026
d6ec115
v0.6.29: login improvements, posthog telemetry (#4026)
TheodoreSpeaks Apr 7, 2026
d7da35b
v0.6.30: slack trigger enhancements, connectors performance improveme…
waleedlatif1 Apr 8, 2026
cf233bb
v0.6.31: elevenlabs voice, trigger.dev fixes, cloud whitelabeling for…
waleedlatif1 Apr 8, 2026
f8f3758
v0.6.32: BYOK fixes, ui improvements, cloudwatch tools, jsm tools ext…
waleedlatif1 Apr 9, 2026
3c8bb40
v0.6.33: polling improvements, jsm forms tools, credentials reactquer…
waleedlatif1 Apr 9, 2026
d33acf4
v0.6.34: trigger.dev fixes, CI speedup, atlassian error extractor
waleedlatif1 Apr 9, 2026
4f40c4c
v0.6.35: additional jira fields, HITL docs, logs cleanup efficiency
waleedlatif1 Apr 10, 2026
cbfab1c
v0.6.36: new chunkers, sockets state machine, google sheets/drive/cal…
waleedlatif1 Apr 11, 2026
4309d06
v0.6.37: audit logs page, isolated-vm worker rotation, permission gro…
waleedlatif1 Apr 12, 2026
8b57476
v0.6.38: models page
waleedlatif1 Apr 12, 2026
e3d0e74
v0.6.39: billing fixes, tools audit, landing fix
waleedlatif1 Apr 13, 2026
0ac0539
v0.6.40: mothership tool loop, new skills, agiloft, STS, IAM integrat…
waleedlatif1 Apr 14, 2026
3838b6e
v0.6.41: webhooks fix, workers removal
waleedlatif1 Apr 14, 2026
fc07922
v0.6.42: mothership nested file reads, search modal improvements
waleedlatif1 Apr 14, 2026
3a1b1a8
v0.6.43: mothership billing idempotency, env var resolution fixes
waleedlatif1 Apr 14, 2026
46ffc49
v0.6.44: streamdown, mothership intelligence, excel extension
waleedlatif1 Apr 15, 2026
010435c
v0.6.45: superagent, csp, brightdata integration, gemini response for…
Sg312 Apr 15, 2026
c0bc62c
Merge pull request #4190 from simstudioai/staging
icecrasher321 Apr 16, 2026
387cc97
v0.6.46: mothership queueing, web vitals
waleedlatif1 Apr 16, 2026
2dbc7fd
v0.6.47: files focusing, documentation, opus 4.7
waleedlatif1 Apr 16, 2026
8a50f18
v0.6.48: import csv into tables, subflow fixes, CSP updates
waleedlatif1 Apr 16, 2026
dcf3302
v0.6.49: deploy sockets event, resolver, logs improvements, monday.co…
waleedlatif1 Apr 17, 2026
6734052
fix: use context variables for block outputs in function block code
Apr 18, 2026
2783606
fix: address Cursor and Greptile bot review comments
Apr 18, 2026
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
10 changes: 9 additions & 1 deletion apps/sim/app/api/function/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,7 @@ export async function POST(req: NextRequest) {
blockNameMapping = {},
blockOutputSchemas = {},
workflowVariables = {},
contextVariables: preResolvedContextVariables = {},
workflowId,
workspaceId,
isCustomTool = false,
Expand All @@ -743,6 +744,10 @@ export async function POST(req: NextRequest) {
// For shell, env vars are injected as OS env vars via shellEnvs.
// Replace {{VAR}} placeholders with $VAR so the shell can access them natively.
resolvedCode = code.replace(/\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}/g, '$$$1')
// Carry pre-resolved block output variables (e.g. __blockRef_N) so they can be
// injected as shell env vars below. The executor replaces block references in the
// code with these names, so the values must be present at runtime.
contextVariables = { ...preResolvedContextVariables }
} else {
const codeResolution = resolveCodeVariables(
code,
Expand All @@ -755,7 +760,10 @@ export async function POST(req: NextRequest) {
lang
)
resolvedCode = codeResolution.resolvedCode
contextVariables = codeResolution.contextVariables
// Merge pre-resolved block output variables from the executor. These take precedence
// because they were produced by the resolver using full execution-state context
// (including loop/parallel scope) and should not be overwritten.
contextVariables = { ...codeResolution.contextVariables, ...preResolvedContextVariables }
Comment thread
cursor[bot] marked this conversation as resolved.
}

let jsImports = ''
Expand Down
15 changes: 12 additions & 3 deletions apps/sim/executor/execution/block-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ import {
} from '@/executor/utils/iteration-context'
import { isJSONString } from '@/executor/utils/json'
import { filterOutputForLog } from '@/executor/utils/output-filter'
import type { VariableResolver } from '@/executor/variables/resolver'
import {
FUNCTION_BLOCK_CONTEXT_VARS_KEY,
type VariableResolver,
} from '@/executor/variables/resolver'
import type { SerializedBlock } from '@/serializer/types'
import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants'

Expand Down Expand Up @@ -108,7 +111,13 @@ export class BlockExecutor {
await validateBlockType(ctx.userId, blockType, ctx)
}

resolvedInputs = this.resolver.resolveInputs(ctx, node.id, block.config.params, block)
if (block.metadata?.id === BlockType.FUNCTION) {
const { resolvedInputs: fnInputs, contextVariables } =
this.resolver.resolveInputsForFunctionBlock(ctx, node.id, block.config.params, block)
resolvedInputs = { ...fnInputs, [FUNCTION_BLOCK_CONTEXT_VARS_KEY]: contextVariables }
} else {
resolvedInputs = this.resolver.resolveInputs(ctx, node.id, block.config.params, block)
}

if (blockLog) {
blockLog.input = this.sanitizeInputsForLog(resolvedInputs)
Expand Down Expand Up @@ -418,7 +427,7 @@ export class BlockExecutor {
const result: Record<string, any> = {}

for (const [key, value] of Object.entries(inputs)) {
if (SYSTEM_SUBBLOCK_IDS.includes(key) || key === 'triggerMode') {
if (SYSTEM_SUBBLOCK_IDS.includes(key) || key === 'triggerMode' || key === FUNCTION_BLOCK_CONTEXT_VARS_KEY) {
continue
}

Expand Down
6 changes: 6 additions & 0 deletions apps/sim/executor/handlers/function/function-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DEFAULT_CODE_LANGUAGE } from '@/lib/execution/languages'
import { BlockType } from '@/executor/constants'
import type { BlockHandler, ExecutionContext } from '@/executor/types'
import { collectBlockData } from '@/executor/utils/block-data'
import { FUNCTION_BLOCK_CONTEXT_VARS_KEY } from '@/executor/variables/resolver'
import type { SerializedBlock } from '@/serializer/types'
import { executeTool } from '@/tools'

Expand All @@ -25,6 +26,10 @@ export class FunctionBlockHandler implements BlockHandler {

const { blockData, blockNameMapping, blockOutputSchemas } = collectBlockData(ctx)

const contextVariables = (inputs[FUNCTION_BLOCK_CONTEXT_VARS_KEY] as
| Record<string, unknown>
| undefined) ?? {}

const result = await executeTool(
'function_execute',
{
Expand All @@ -36,6 +41,7 @@ export class FunctionBlockHandler implements BlockHandler {
blockData,
blockNameMapping,
blockOutputSchemas,
contextVariables,
_context: {
workflowId: ctx.workflowId,
workspaceId: ctx.workspaceId,
Expand Down
137 changes: 137 additions & 0 deletions apps/sim/executor/variables/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {
import { WorkflowResolver } from '@/executor/variables/resolvers/workflow'
import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types'

/** Key used to carry pre-resolved context variables through the inputs map. */
export const FUNCTION_BLOCK_CONTEXT_VARS_KEY = '_runtimeContextVars'

const logger = createLogger('VariableResolver')

export class VariableResolver {
Expand All @@ -36,6 +39,63 @@ export class VariableResolver {
]
}

/**
* Resolves inputs for function blocks. Block output references in the `code` field
* are stored as named context variables instead of being embedded as JavaScript
* literals, preventing large values from bloating the code string.
*
* Returns the resolved inputs and a `contextVariables` map. Callers should inject
* contextVariables into the function execution request body so the isolated VM can
* access them as global variables.
*/
resolveInputsForFunctionBlock(
ctx: ExecutionContext,
currentNodeId: string,
params: Record<string, any>,
block: SerializedBlock
): { resolvedInputs: Record<string, any>; contextVariables: Record<string, unknown> } {
const contextVariables: Record<string, unknown> = {}
const resolved: Record<string, any> = {}

for (const [key, value] of Object.entries(params)) {
if (key === 'code') {
if (typeof value === 'string') {
resolved[key] = this.resolveCodeWithContextVars(
ctx,
currentNodeId,
value,
undefined,
block,
contextVariables
)
} else if (Array.isArray(value)) {
resolved[key] = value.map((item: any) => {
if (item && typeof item === 'object' && typeof item.content === 'string') {
return {
...item,
content: this.resolveCodeWithContextVars(
ctx,
currentNodeId,
item.content,
undefined,
block,
contextVariables
),
}
}
return item
})
} else {
resolved[key] = this.resolveValue(ctx, currentNodeId, value, undefined, block)
}
} else {
resolved[key] = this.resolveValue(ctx, currentNodeId, value, undefined, block)
}
}

return { resolvedInputs: resolved, contextVariables }
}

resolveInputs(
ctx: ExecutionContext,
currentNodeId: string,
Expand Down Expand Up @@ -149,6 +209,83 @@ export class VariableResolver {
}
return value
}
/**
* Resolves a code template for a function block. Block output references are stored
* in `contextVarAccumulator` as named variables (e.g. `__blockRef_0`) and replaced
* with those variable names in the returned code string. Non-block references (loop
* items, workflow variables, env vars) are still inlined as literals so they remain
* available without any extra passing mechanism.
*/
private resolveCodeWithContextVars(
ctx: ExecutionContext,
currentNodeId: string,
template: string,
loopScope: LoopScope | undefined,
block: SerializedBlock,
contextVarAccumulator: Record<string, unknown>
): string {
const resolutionContext: ResolutionContext = {
executionContext: ctx,
executionState: this.state,
currentNodeId,
loopScope,
}

const language = (block.config?.params as Record<string, unknown> | undefined)?.language as
| string
| undefined

let replacementError: Error | null = null

const blockRefByMatch = new Map<string, string>()

let result = replaceValidReferences(template, (match) => {
if (replacementError) return match

try {
if (this.blockResolver.canResolve(match)) {
// Deduplicate: identical references in the same template share a single
// accumulator slot so we do not duplicate large payloads.
const existing = blockRefByMatch.get(match)
if (existing !== undefined) return existing

const resolved = this.resolveReference(match, resolutionContext)
if (resolved === undefined) return match

const effectiveValue = resolved === RESOLVED_EMPTY ? null : resolved

// Block output: store in contextVarAccumulator, replace with variable name
const varName = `__blockRef_${Object.keys(contextVarAccumulator).length}`
contextVarAccumulator[varName] = effectiveValue
blockRefByMatch.set(match, varName)
return varName
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shell block references break: missing $ prefix and wrong serialization

Medium Severity

For shell function blocks, resolveCodeWithContextVars replaces <BlockA.result> with bare __blockRef_0, but shell requires $__blockRef_0 to dereference an environment variable. The code runs echo __blockRef_0 and outputs the literal string instead of the value. Additionally, in the route, String(v) on complex objects stored as shell env vars produces [object Object] rather than a JSON representation. Before this PR, values were inlined via formatLiteralForCode, so this is a regression for shell function blocks referencing other block outputs.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2783606. Configure here.

}

const resolved = this.resolveReference(match, resolutionContext)
if (resolved === undefined) return match

const effectiveValue = resolved === RESOLVED_EMPTY ? null : resolved

// Non-block reference (loop, parallel, workflow, env): embed as literal
return this.blockResolver.formatValueForBlock(effectiveValue, BlockType.FUNCTION, language)
} catch (error) {
replacementError = error instanceof Error ? error : new Error(String(error))
return match
}
})

if (replacementError !== null) {
throw replacementError
}

result = result.replace(createEnvVarPattern(), (match) => {
const resolved = this.resolveReference(match, resolutionContext)
return typeof resolved === 'string' ? resolved : match
})

return result
}

private resolveTemplate(
ctx: ExecutionContext,
currentNodeId: string,
Expand Down
1 change: 1 addition & 0 deletions apps/sim/tools/function/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export const functionExecuteTool: ToolConfig<CodeExecutionInput, CodeExecutionOu
blockData: params.blockData || {},
blockNameMapping: params.blockNameMapping || {},
blockOutputSchemas: params.blockOutputSchemas || {},
contextVariables: params.contextVariables || {},
workflowId: params._context?.workflowId,
userId: params._context?.userId,
workspaceId: params._context?.workspaceId,
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/tools/function/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface CodeExecutionInput {
blockData?: Record<string, unknown>
blockNameMapping?: Record<string, string>
blockOutputSchemas?: Record<string, Record<string, unknown>>
/** Pre-resolved block output variables from the executor, injected as VM globals. */
contextVariables?: Record<string, unknown>
_context?: {
workflowId?: string
userId?: string
Expand Down