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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useRouter } from 'next/navigation'
import {
Badge,
Button,
Code,
Input,
Label,
Table,
Expand Down Expand Up @@ -777,15 +776,6 @@ export default function ResumeExecutionPage({
refreshSelectedDetail,
])

const pauseResponsePreview = useMemo(() => {
if (!selectedDetail?.pausePoint.response?.data) return '{}'
try {
return JSON.stringify(selectedDetail.pausePoint.response.data, null, 2)
} catch {
return String(selectedDetail.pausePoint.response.data)
}
}, [selectedDetail])

const isFormComplete = useMemo(() => {
if (!isHumanMode || !hasInputFormat) return true
return inputFormatFields.every((field) => {
Expand Down Expand Up @@ -1155,10 +1145,12 @@ export default function ResumeExecutionPage({
borderBottom: '1px solid var(--border)',
}}
>
<Label>Pause Data</Label>
<Label>Display Data</Label>
</div>
<div style={{ padding: '16px' }}>
<Code.Viewer code={pauseResponsePreview} language='json' />
<p style={{ fontSize: '13px', color: 'var(--text-muted)' }}>
No display data configured
</p>
</div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1183,19 +1183,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
const outputPaths = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks, true)
blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
}
} else if (sourceBlock.type === 'approval') {
const dynamicOutputs = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks)

const isSelfReference = activeSourceBlockId === blockId

if (dynamicOutputs.length > 0) {
const allTags = dynamicOutputs.map((path) => `${normalizedBlockName}.${path}`)
blockTags = isSelfReference ? allTags.filter((tag) => tag.endsWith('.url')) : allTags
} else {
const outputPaths = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks)
const allTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
blockTags = isSelfReference ? allTags.filter((tag) => tag.endsWith('.url')) : allTags
}
} else if (sourceBlock.type === 'human_in_the_loop') {
const dynamicOutputs = getBlockOutputPaths(sourceBlock.type, mergedSubBlocks)

Expand Down Expand Up @@ -1400,13 +1387,8 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
if (!accessibleBlock) continue

// Skip the current block - blocks cannot reference their own outputs
// Exception: approval and human_in_the_loop blocks can reference their own outputs
if (
accessibleBlockId === blockId &&
accessibleBlock.type !== 'approval' &&
accessibleBlock.type !== 'human_in_the_loop'
)
continue
// Exception: human_in_the_loop blocks can reference their own outputs (url, resumeEndpoint)
if (accessibleBlockId === blockId && accessibleBlock.type !== 'human_in_the_loop') continue

const blockConfig = getBlock(accessibleBlock.type)

Expand Down Expand Up @@ -1520,19 +1502,6 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
const outputPaths = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks, true)
blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
}
} else if (accessibleBlock.type === 'approval') {
const dynamicOutputs = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks)

const isSelfReference = accessibleBlockId === blockId

if (dynamicOutputs.length > 0) {
const allTags = dynamicOutputs.map((path) => `${normalizedBlockName}.${path}`)
blockTags = isSelfReference ? allTags.filter((tag) => tag.endsWith('.url')) : allTags
} else {
const outputPaths = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks)
const allTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`)
blockTags = isSelfReference ? allTags.filter((tag) => tag.endsWith('.url')) : allTags
}
} else if (accessibleBlock.type === 'human_in_the_loop') {
const dynamicOutputs = getBlockOutputPaths(accessibleBlock.type, mergedSubBlocks)

Expand Down
10 changes: 8 additions & 2 deletions apps/sim/blocks/blocks/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,13 @@ Example 3 (Array Input):
outputs: {
content: { type: 'string', description: 'Generated response content' },
model: { type: 'string', description: 'Model used for generation' },
tokens: { type: 'any', description: 'Token usage statistics' },
toolCalls: { type: 'any', description: 'Tool calls made' },
tokens: { type: 'json', description: 'Token usage statistics' },
toolCalls: { type: 'json', description: 'Tool calls made' },
providerTiming: {
type: 'json',
description: 'Provider timing information',
hiddenFromDisplay: true,
},
cost: { type: 'number', description: 'Cost of the API call', hiddenFromDisplay: true },
},
}
16 changes: 16 additions & 0 deletions apps/sim/blocks/blocks/human_in_the_loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,21 @@ export const HumanInTheLoopBlock: BlockConfig<ResponseBlockOutput> = {
type: 'string',
description: 'Resume API endpoint URL for direct curl requests',
},
response: {
type: 'json',
description: 'Display data shown to the approver',
hiddenFromDisplay: true,
},
submission: {
type: 'json',
description: 'Form submission data from the approver',
hiddenFromDisplay: true,
},
resumeInput: {
type: 'json',
description: 'Raw input data submitted when resuming',
hiddenFromDisplay: true,
},
submittedAt: { type: 'string', description: 'ISO timestamp when the workflow was resumed' },
},
}
1 change: 1 addition & 0 deletions apps/sim/blocks/blocks/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
tokens: { type: 'json', description: 'Token usage' },
cost: { type: 'json', description: 'Cost information' },
selectedPath: { type: 'json', description: 'Selected routing path' },
selectedRoute: { type: 'string', description: 'Selected route ID' },
},
}

Expand Down
5 changes: 5 additions & 0 deletions apps/sim/blocks/blocks/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export const WorkflowBlock: BlockConfig = {
childWorkflowName: { type: 'string', description: 'Child workflow name' },
result: { type: 'json', description: 'Workflow execution result' },
error: { type: 'string', description: 'Error message' },
childTraceSpans: {
type: 'json',
description: 'Child workflow trace spans',
hiddenFromDisplay: true,
},
},
hideFromToolbar: true,
}
5 changes: 5 additions & 0 deletions apps/sim/blocks/blocks/workflow_input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,10 @@ export const WorkflowInputBlock: BlockConfig = {
childWorkflowName: { type: 'string', description: 'Child workflow name' },
result: { type: 'json', description: 'Workflow execution result' },
error: { type: 'string', description: 'Error message' },
childTraceSpans: {
type: 'json',
description: 'Child workflow trace spans',
hiddenFromDisplay: true,
},
},
}
11 changes: 11 additions & 0 deletions apps/sim/blocks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,19 @@ export type OutputFieldDefinition =
* Uses the same condition format as subBlocks.
*/
condition?: OutputCondition
/**
* If true, this output is hidden from display in the tag dropdown and logs,
* but still available for resolution and execution.
*/
hiddenFromDisplay?: boolean
}

export function isHiddenFromDisplay(def: unknown): boolean {
return Boolean(
def && typeof def === 'object' && 'hiddenFromDisplay' in def && def.hiddenFromDisplay
)
}

export interface ParamConfig {
type: ParamType
description?: string
Expand Down
11 changes: 11 additions & 0 deletions apps/sim/executor/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import type { LoopType, ParallelType } from '@/lib/workflows/types'

/**
* Runtime-injected keys for trigger blocks that should be hidden from logs/display.
* These are added during execution but aren't part of the block's static output schema.
*/
export const TRIGGER_INTERNAL_KEYS = ['webhook', 'workflowId'] as const
export type TriggerInternalKey = (typeof TRIGGER_INTERNAL_KEYS)[number]

export function isTriggerInternalKey(key: string): key is TriggerInternalKey {
return TRIGGER_INTERNAL_KEYS.includes(key as TriggerInternalKey)
}

export enum BlockType {
PARALLEL = 'parallel',
LOOP = 'loop',
Expand Down
58 changes: 7 additions & 51 deletions apps/sim/executor/execution/block-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import {
DEFAULTS,
EDGE,
isSentinelBlockType,
isTriggerBehavior,
isWorkflowBlockType,
} from '@/executor/constants'
import type { DAGNode } from '@/executor/dag/builder'
import { ChildWorkflowError } from '@/executor/errors/child-workflow-error'
Expand All @@ -30,6 +28,7 @@ import type {
} from '@/executor/types'
import { streamingResponseFormatProcessor } from '@/executor/utils'
import { buildBlockExecutionError, normalizeError } from '@/executor/utils/errors'
import { filterOutputForLog } from '@/executor/utils/output-filter'
import { validateBlockType } from '@/executor/utils/permission-check'
import type { VariableResolver } from '@/executor/variables/resolver'
import type { SerializedBlock } from '@/serializer/types'
Expand Down Expand Up @@ -149,13 +148,15 @@ export class BlockExecutor {
blockLog.endedAt = new Date().toISOString()
blockLog.durationMs = duration
blockLog.success = true
blockLog.output = this.filterOutputForLog(block, normalizedOutput)
blockLog.output = filterOutputForLog(block.metadata?.id || '', normalizedOutput, { block })
}

this.state.setBlockOutput(node.id, normalizedOutput, duration)

if (!isSentinel) {
const displayOutput = this.filterOutputForDisplay(block, normalizedOutput)
const displayOutput = filterOutputForLog(block.metadata?.id || '', normalizedOutput, {
block,
})
this.callOnBlockComplete(ctx, node, block, resolvedInputs, displayOutput, duration)
}

Expand Down Expand Up @@ -233,7 +234,7 @@ export class BlockExecutor {
blockLog.success = false
blockLog.error = errorMessage
blockLog.input = input
blockLog.output = this.filterOutputForLog(block, errorOutput)
blockLog.output = filterOutputForLog(block.metadata?.id || '', errorOutput, { block })
}

logger.error(
Expand All @@ -246,7 +247,7 @@ export class BlockExecutor {
)

if (!isSentinel) {
const displayOutput = this.filterOutputForDisplay(block, errorOutput)
const displayOutput = filterOutputForLog(block.metadata?.id || '', errorOutput, { block })
this.callOnBlockComplete(ctx, node, block, input, displayOutput, duration)
}

Expand Down Expand Up @@ -335,51 +336,6 @@ export class BlockExecutor {
return { result: output }
}

private filterOutputForLog(
block: SerializedBlock,
output: NormalizedBlockOutput
): NormalizedBlockOutput {
const blockType = block.metadata?.id

if (blockType === BlockType.HUMAN_IN_THE_LOOP) {
const filtered: NormalizedBlockOutput = {}
for (const [key, value] of Object.entries(output)) {
if (key.startsWith('_')) continue
if (key === 'response') continue
filtered[key] = value
}
return filtered
}

if (isTriggerBehavior(block)) {
const filtered: NormalizedBlockOutput = {}
const internalKeys = ['webhook', 'workflowId']
for (const [key, value] of Object.entries(output)) {
if (internalKeys.includes(key)) continue
filtered[key] = value
}
return filtered
}

return output
}

private filterOutputForDisplay(
block: SerializedBlock,
output: NormalizedBlockOutput
): NormalizedBlockOutput {
const filtered = this.filterOutputForLog(block, output)

if (isWorkflowBlockType(block.metadata?.id)) {
const { childTraceSpans: _, ...displayOutput } = filtered as {
childTraceSpans?: unknown
} & Record<string, unknown>
return displayOutput
}

return filtered
}

private callOnBlockStart(ctx: ExecutionContext, node: DAGNode, block: SerializedBlock): void {
const blockId = node.id
const blockName = block.metadata?.name ?? blockId
Expand Down
9 changes: 7 additions & 2 deletions apps/sim/executor/handlers/trigger/trigger-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createLogger } from '@sim/logger'
import { BlockType, isTriggerBehavior } from '@/executor/constants'
import { BlockType, isTriggerBehavior, isTriggerInternalKey } from '@/executor/constants'
import type { BlockHandler, ExecutionContext } from '@/executor/types'
import type { SerializedBlock } from '@/serializer/types'

Expand Down Expand Up @@ -33,7 +33,12 @@ export class TriggerBlockHandler implements BlockHandler {
const starterOutput = starterState.output

if (starterOutput.webhook?.data) {
const { webhook, workflowId, ...cleanOutput } = starterOutput
const cleanOutput: Record<string, unknown> = {}
for (const [key, value] of Object.entries(starterOutput)) {
if (!isTriggerInternalKey(key)) {
cleanOutput[key] = value
}
}
return cleanOutput
}

Expand Down
51 changes: 51 additions & 0 deletions apps/sim/executor/utils/output-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { getBlock } from '@/blocks'
import { isHiddenFromDisplay } from '@/blocks/types'
import { isTriggerBehavior, isTriggerInternalKey } from '@/executor/constants'
import type { NormalizedBlockOutput } from '@/executor/types'
import type { SerializedBlock } from '@/serializer/types'

/**
* Filters block output for logging/display purposes.
* Removes internal fields and fields marked with hiddenFromDisplay.
*
* @param blockType - The block type string (e.g., 'human_in_the_loop', 'workflow')
* @param output - The raw block output to filter
* @param options - Optional configuration
* @param options.block - Full SerializedBlock for trigger behavior detection
* @param options.additionalHiddenKeys - Extra keys to filter out (e.g., 'resume')
*/
export function filterOutputForLog(
blockType: string,
output: NormalizedBlockOutput,
options?: {
block?: SerializedBlock
additionalHiddenKeys?: string[]
}
): NormalizedBlockOutput {
const blockConfig = blockType ? getBlock(blockType) : undefined
const filtered: NormalizedBlockOutput = {}
const additionalHiddenKeys = options?.additionalHiddenKeys ?? []

for (const [key, value] of Object.entries(output)) {
// Skip internal keys (underscore prefix)
if (key.startsWith('_')) continue

if (blockConfig?.outputs && isHiddenFromDisplay(blockConfig.outputs[key])) {
continue
}

// Skip runtime-injected trigger keys not in block config
if (options?.block && isTriggerBehavior(options.block) && isTriggerInternalKey(key)) {
continue
}

// Skip additional hidden keys specified by caller
if (additionalHiddenKeys.includes(key)) {
continue
}

filtered[key] = value
}

return filtered
}
7 changes: 6 additions & 1 deletion apps/sim/lib/copilot/process-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, isNull } from 'drizzle-orm'
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
import { sanitizeForCopilot } from '@/lib/workflows/sanitization/json-sanitizer'
import { isHiddenFromDisplay } from '@/blocks/types'
import { escapeRegExp } from '@/executor/constants'
import { getUserPermissionConfig } from '@/executor/utils/permission-check'
import type { ChatContext } from '@/stores/panel/copilot/types'
Expand Down Expand Up @@ -397,7 +398,11 @@ async function processBlockMetadata(
category: blockConfig.category,
bgColor: blockConfig.bgColor,
inputs: blockConfig.inputs || {},
outputs: blockConfig.outputs || {},
outputs: blockConfig.outputs
? Object.fromEntries(
Object.entries(blockConfig.outputs).filter(([_, def]) => !isHiddenFromDisplay(def))
)
: {},
tools: blockConfig.tools?.access || [],
hideFromToolbar: blockConfig.hideFromToolbar,
}
Expand Down
Loading