@@ -122,38 +122,60 @@ function skeletonFromDeclaredOutput(node: unknown, depth = 0): unknown {
122122 return null
123123}
124124
125+ /** A dependency path as the segments the mock skeleton is built from. */
126+ function pathSegments ( path : string ) : string [ ] {
127+ return path
128+ . split ( '.' )
129+ . filter ( Boolean )
130+ . flatMap ( ( part ) => {
131+ const { property, pathParts } = splitLeadingBracketPath ( part )
132+ return [ property , ...pathParts ]
133+ } )
134+ }
135+
125136/**
126- * Replaces each null leaf of an MCP block's mocked `structuredContent` with the
127- * skeleton its operation's declared output schema gives at that path, so a mock
128- * of a declaring operation starts from the reply's real fields rather than from
129- * whatever names the consuming block guessed. A leaf the schema does not declare
130- * stays null .
137+ * Fills an MCP block's mocked `structuredContent` from its operation's declared
138+ * output schema, so a mock of a declaring operation starts from the reply's real
139+ * fields rather than from whatever names the consuming block guessed. Where the
140+ * consumer reads a value whole, every declared field below it is added beside
141+ * the ones it reads by name; an undeclared path it reads stays as read .
131142 */
132143function fillDeclaredStructuredContent (
133144 mock : Record < string , unknown > ,
134- block : Record < string , unknown > | undefined
145+ block : Record < string , unknown > | undefined ,
146+ paths : readonly string [ ]
135147) : void {
136148 if ( block ?. type !== 'mcp' || ! Object . hasOwn ( mock , MCP_STRUCTURED_CONTENT_OUTPUT ) ) return
137149 // An invalid mode makes resolveMcpBlockConfig throw, which reads as no typed output.
138150 const modes = toRecord ( toRecord ( block . data ) . canonicalModes ) as CanonicalModeOverrides
139151 const outputs = getMcpStructuredContentOutputs ( toRecord ( block . subBlocks ) , modes )
140152 const declared = outputs ?. [ MCP_STRUCTURED_CONTENT_OUTPUT ]
141153 if ( ! declared ) return
154+ const readWhole = new Set ( paths . map ( ( path ) => pathSegments ( path ) . join ( '.' ) ) )
142155
143- const fill = ( value : unknown , node : unknown ) : unknown => {
156+ const fill = ( value : unknown , node : unknown , at : string ) : unknown => {
144157 if ( value === null ) return skeletonFromDeclaredOutput ( node )
145158 if ( ! isRecordLike ( node ) ) return value
146- if ( Array . isArray ( value ) ) return value . map ( ( item ) => fill ( item , node . items ) )
159+ if ( Array . isArray ( value ) )
160+ return value . map ( ( item , index ) => fill ( item , node . items , `${ at } .${ index } ` ) )
147161 if ( ! isRecordLike ( value ) || ! isRecordLike ( node . properties ) ) return value
148162 const properties = node . properties
149- return Object . fromEntries (
150- Object . entries ( value ) . map ( ( [ key , child ] ) => [
151- key ,
152- Object . hasOwn ( properties , key ) ? fill ( child , properties [ key ] ) : child ,
153- ] )
154- )
163+ const read = Object . entries ( value ) . map ( ( [ key , child ] ) : [ string , unknown ] => [
164+ key ,
165+ Object . hasOwn ( properties , key ) ? fill ( child , properties [ key ] , `${ at } .${ key } ` ) : child ,
166+ ] )
167+ const added = readWhole . has ( at )
168+ ? Object . entries ( properties )
169+ . filter ( ( [ key ] ) => ! Object . hasOwn ( value , key ) )
170+ . map ( ( [ key , child ] ) : [ string , unknown ] => [ key , skeletonFromDeclaredOutput ( child ) ] )
171+ : [ ]
172+ return Object . fromEntries ( [ ...added , ...read ] )
155173 }
156- mock [ MCP_STRUCTURED_CONTENT_OUTPUT ] = fill ( mock [ MCP_STRUCTURED_CONTENT_OUTPUT ] , declared )
174+ mock [ MCP_STRUCTURED_CONTENT_OUTPUT ] = fill (
175+ mock [ MCP_STRUCTURED_CONTENT_OUTPUT ] ,
176+ declared ,
177+ MCP_STRUCTURED_CONTENT_OUTPUT
178+ )
157179}
158180
159181interface DepView {
@@ -330,7 +352,11 @@ export const workflowDepsCommand: AgentCliEngine = {
330352 // parent it never reads at all — run_block refuses to start without them.
331353 const shapes = blockDeps . map ( ( d ) => {
332354 const shape = skeletonFromPaths ( d . paths ?? [ ] )
333- fillDeclaredStructuredContent ( shape . value , d . blockId ? blocks [ d . blockId ] : undefined )
355+ fillDeclaredStructuredContent (
356+ shape . value ,
357+ d . blockId ? blocks [ d . blockId ] : undefined ,
358+ d . paths ?? [ ]
359+ )
334360 return { name : d . blockName ?? d . blockId ?? d . token , ...shape }
335361 } )
336362 const mock : Record < string , Record < string , unknown > > = Object . fromEntries (
0 commit comments