@@ -28,6 +28,7 @@ import type {
2828} from '@/executor/types'
2929import { streamingResponseFormatProcessor } from '@/executor/utils'
3030import { buildBlockExecutionError , normalizeError } from '@/executor/utils/errors'
31+ import { isJSONString } from '@/executor/utils/json'
3132import { filterOutputForLog } from '@/executor/utils/output-filter'
3233import { validateBlockType } from '@/executor/utils/permission-check'
3334import type { VariableResolver } from '@/executor/variables/resolver'
@@ -86,7 +87,7 @@ export class BlockExecutor {
8687 resolvedInputs = this . resolver . resolveInputs ( ctx , node . id , block . config . params , block )
8788
8889 if ( blockLog ) {
89- blockLog . input = resolvedInputs
90+ blockLog . input = this . parseJsonInputs ( resolvedInputs )
9091 }
9192 } catch ( error ) {
9293 cleanupSelfReference ?.( )
@@ -157,7 +158,14 @@ export class BlockExecutor {
157158 const displayOutput = filterOutputForLog ( block . metadata ?. id || '' , normalizedOutput , {
158159 block,
159160 } )
160- this . callOnBlockComplete ( ctx , node , block , resolvedInputs , displayOutput , duration )
161+ this . callOnBlockComplete (
162+ ctx ,
163+ node ,
164+ block ,
165+ this . parseJsonInputs ( resolvedInputs ) ,
166+ displayOutput ,
167+ duration
168+ )
161169 }
162170
163171 return normalizedOutput
@@ -233,7 +241,7 @@ export class BlockExecutor {
233241 blockLog . durationMs = duration
234242 blockLog . success = false
235243 blockLog . error = errorMessage
236- blockLog . input = input
244+ blockLog . input = this . parseJsonInputs ( input )
237245 blockLog . output = filterOutputForLog ( block . metadata ?. id || '' , errorOutput , { block } )
238246 }
239247
@@ -248,7 +256,14 @@ export class BlockExecutor {
248256
249257 if ( ! isSentinel ) {
250258 const displayOutput = filterOutputForLog ( block . metadata ?. id || '' , errorOutput , { block } )
251- this . callOnBlockComplete ( ctx , node , block , input , displayOutput , duration )
259+ this . callOnBlockComplete (
260+ ctx ,
261+ node ,
262+ block ,
263+ this . parseJsonInputs ( input ) ,
264+ displayOutput ,
265+ duration
266+ )
252267 }
253268
254269 const hasErrorPort = this . hasErrorPortEdge ( node )
@@ -336,6 +351,36 @@ export class BlockExecutor {
336351 return { result : output }
337352 }
338353
354+ /**
355+ * Parse JSON string inputs to objects for log display only.
356+ * Attempts to parse any string that looks like JSON.
357+ * Returns a new object - does not mutate the original inputs.
358+ */
359+ private parseJsonInputs ( inputs : Record < string , any > ) : Record < string , any > {
360+ let result = inputs
361+ let hasChanges = false
362+
363+ for ( const [ key , value ] of Object . entries ( inputs ) ) {
364+ // isJSONString is a quick heuristic (checks for { or [), not a validator.
365+ // Invalid JSON is safely caught below - this just avoids JSON.parse on every string.
366+ if ( typeof value !== 'string' || ! isJSONString ( value ) ) {
367+ continue
368+ }
369+
370+ try {
371+ if ( ! hasChanges ) {
372+ result = { ...inputs }
373+ hasChanges = true
374+ }
375+ result [ key ] = JSON . parse ( value . trim ( ) )
376+ } catch {
377+ // Not valid JSON, keep original string
378+ }
379+ }
380+
381+ return result
382+ }
383+
339384 private callOnBlockStart ( ctx : ExecutionContext , node : DAGNode , block : SerializedBlock ) : void {
340385 const blockId = node . id
341386 const blockName = block . metadata ?. name ?? blockId
0 commit comments