@@ -20,7 +20,10 @@ import type {
2020} from '@codebuff/common/types/messages/content-part'
2121import type { PrintModeEvent } from '@codebuff/common/types/print-mode'
2222import type { AgentState } from '@codebuff/common/types/session-state'
23- import type { ParamsExcluding , ParamsOf } from '@codebuff/common/types/function-params'
23+ import type {
24+ ParamsExcluding ,
25+ ParamsOf ,
26+ } from '@codebuff/common/types/function-params'
2427import type { Logger } from '@codebuff/common/types/contracts/logger'
2528import type { WebSocket } from 'ws'
2629
@@ -271,6 +274,7 @@ export async function runProgrammaticStep(
271274 }
272275
273276 // Execute the tool synchronously and get the result immediately
277+ // Wrap onResponseChunk to add parentAgentId to nested agent events
274278 await executeToolCall ( {
275279 ...params ,
276280 toolName : toolCall . toolName ,
@@ -284,7 +288,65 @@ export async function runProgrammaticStep(
284288 fullResponse : '' ,
285289 state,
286290 autoInsertEndStepParam : true ,
287- excludeToolFromMessageHistory,
291+ excludeToolFromMessageHistory, onResponseChunk : ( chunk : string | PrintModeEvent ) => {
292+ if ( typeof chunk === 'string' ) {
293+ onResponseChunk ( chunk )
294+ return
295+ }
296+
297+ // Only add parentAgentId if this programmatic agent has a parent (i.e., it's nested)
298+ // This ensures we don't add parentAgentId to top-level spawns
299+ if ( state . agentState . parentId ) {
300+ // Add parentAgentId to nested agent events for proper nesting in UI
301+ if (
302+ chunk . type === 'subagent_start' ||
303+ chunk . type === 'subagent_finish'
304+ ) {
305+ // Only add parentAgentId if it's not already set (e.g., by spawn_agents)
306+ if ( ! ( chunk as any ) . parentAgentId ) {
307+ logger . debug (
308+ {
309+ eventType : chunk . type ,
310+ agentId : chunk . agentId ,
311+ parentId : state . agentState . agentId ,
312+ } ,
313+ `run-programmatic-step: Adding parentAgentId to ${ chunk . type } event` ,
314+ )
315+ onResponseChunk ( {
316+ ...chunk ,
317+ parentAgentId : state . agentState . agentId ,
318+ } )
319+ return
320+ }
321+ }
322+
323+ // Add parentAgentId to tool calls and results from nested agents
324+ if (
325+ chunk . type === 'tool_call' ||
326+ chunk . type === 'tool_result'
327+ ) {
328+ // Only add parentAgentId if it's not already set
329+ if ( ! ( chunk as any ) . parentAgentId ) {
330+ logger . debug (
331+ {
332+ eventType : chunk . type ,
333+ agentId : ( chunk as any ) . agentId ,
334+ parentId : state . agentState . agentId ,
335+ } ,
336+ `run-programmatic-step: Adding parentAgentId to ${ chunk . type } event` ,
337+ )
338+ onResponseChunk ( {
339+ ...chunk ,
340+ parentAgentId : state . agentState . agentId ,
341+ } )
342+ return
343+ }
344+ }
345+ }
346+
347+ // For other events or top-level spawns, send as-is
348+ onResponseChunk ( chunk )
349+ } ,
288350 } )
289351
290352 // TODO: Remove messages from state and always use agentState.messageHistory.
0 commit comments