@@ -26,9 +26,6 @@ import { CLASS_TOOL_METADATA } from '@/stores/panel/copilot/store'
2626import type { SubAgentContentBlock } from '@/stores/panel/copilot/types'
2727import { useWorkflowStore } from '@/stores/workflows/workflow/store'
2828
29- /**
30- * Parse special tags from content
31- */
3229/**
3330 * Plan step can be either a string or an object with title and plan
3431 */
@@ -47,6 +44,56 @@ interface ParsedTags {
4744 cleanContent : string
4845}
4946
47+ /**
48+ * Extract plan steps from plan_respond tool calls in subagent blocks.
49+ * Returns { steps, isComplete } where steps is in the format expected by PlanSteps component.
50+ */
51+ function extractPlanFromBlocks ( blocks : SubAgentContentBlock [ ] | undefined ) : {
52+ steps : Record < string , PlanStep > | undefined
53+ isComplete : boolean
54+ } {
55+ if ( ! blocks ) return { steps : undefined , isComplete : false }
56+
57+ // Find the plan_respond tool call
58+ const planRespondBlock = blocks . find (
59+ ( b ) => b . type === 'subagent_tool_call' && b . toolCall ?. name === 'plan_respond'
60+ )
61+
62+ if ( ! planRespondBlock ?. toolCall ) {
63+ return { steps : undefined , isComplete : false }
64+ }
65+
66+ // Tool call arguments can be in different places depending on the source
67+ // Also handle nested data.arguments structure from the schema
68+ const tc = planRespondBlock . toolCall as any
69+ const args = tc . params || tc . parameters || tc . input || tc . arguments || tc . data ?. arguments || { }
70+ const stepsArray = args . steps
71+
72+ if ( ! Array . isArray ( stepsArray ) || stepsArray . length === 0 ) {
73+ return { steps : undefined , isComplete : false }
74+ }
75+
76+ // Convert array format to Record<string, PlanStep> format
77+ // From: [{ number: 1, title: "..." }, { number: 2, title: "..." }]
78+ // To: { "1": "...", "2": "..." }
79+ const steps : Record < string , PlanStep > = { }
80+ for ( const step of stepsArray ) {
81+ if ( step . number !== undefined && step . title ) {
82+ steps [ String ( step . number ) ] = step . title
83+ }
84+ }
85+
86+ // Check if the tool call is complete (not pending/executing)
87+ const isComplete =
88+ planRespondBlock . toolCall . state === ClientToolCallState . success ||
89+ planRespondBlock . toolCall . state === ClientToolCallState . error
90+
91+ return {
92+ steps : Object . keys ( steps ) . length > 0 ? steps : undefined ,
93+ isComplete,
94+ }
95+ }
96+
5097/**
5198 * Try to parse partial JSON for streaming options.
5299 * Attempts to extract complete key-value pairs from incomplete JSON.
@@ -654,11 +701,20 @@ function SubAgentThinkingContent({
654701 }
655702 }
656703
704+ // Extract plan from plan_respond tool call (preferred) or fall back to <plan> tags
705+ const { steps : planSteps , isComplete : planComplete } = extractPlanFromBlocks ( blocks )
657706 const allParsed = parseSpecialTags ( allRawText )
658707
659- if ( ! cleanText . trim ( ) && ! allParsed . plan ) return null
708+ // Prefer plan_respond tool data over <plan> tags
709+ const hasPlan =
710+ ! ! ( planSteps && Object . keys ( planSteps ) . length > 0 ) ||
711+ ! ! ( allParsed . plan && Object . keys ( allParsed . plan ) . length > 0 )
712+ const planToRender = planSteps || allParsed . plan
713+ const isPlanStreaming = planSteps ? ! planComplete : isStreaming
660714
661- const hasSpecialTags = ! ! ( allParsed . plan && Object . keys ( allParsed . plan ) . length > 0 )
715+ if ( ! cleanText . trim ( ) && ! hasPlan ) return null
716+
717+ const hasSpecialTags = hasPlan
662718
663719 return (
664720 < div className = 'space-y-1.5' >
@@ -670,9 +726,7 @@ function SubAgentThinkingContent({
670726 hasSpecialTags = { hasSpecialTags }
671727 />
672728 ) }
673- { allParsed . plan && Object . keys ( allParsed . plan ) . length > 0 && (
674- < PlanSteps steps = { allParsed . plan } streaming = { isStreaming } />
675- ) }
729+ { hasPlan && planToRender && < PlanSteps steps = { planToRender } streaming = { isPlanStreaming } /> }
676730 </ div >
677731 )
678732}
@@ -744,8 +798,19 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
744798 }
745799
746800 const allParsed = parseSpecialTags ( allRawText )
801+
802+ // Extract plan from plan_respond tool call (preferred) or fall back to <plan> tags
803+ const { steps : planSteps , isComplete : planComplete } = extractPlanFromBlocks (
804+ toolCall . subAgentBlocks
805+ )
806+ const hasPlan =
807+ ! ! ( planSteps && Object . keys ( planSteps ) . length > 0 ) ||
808+ ! ! ( allParsed . plan && Object . keys ( allParsed . plan ) . length > 0 )
809+ const planToRender = planSteps || allParsed . plan
810+ const isPlanStreaming = planSteps ? ! planComplete : isStreaming
811+
747812 const hasSpecialTags = ! ! (
748- ( allParsed . plan && Object . keys ( allParsed . plan ) . length > 0 ) ||
813+ hasPlan ||
749814 ( allParsed . options && Object . keys ( allParsed . options ) . length > 0 )
750815 )
751816
@@ -757,8 +822,6 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
757822 const outerLabel = getSubagentCompletionLabel ( toolCall . name )
758823 const durationText = `${ outerLabel } for ${ formatDuration ( duration ) } `
759824
760- const hasPlan = allParsed . plan && Object . keys ( allParsed . plan ) . length > 0
761-
762825 const renderCollapsibleContent = ( ) => (
763826 < >
764827 { segments . map ( ( segment , index ) => {
@@ -800,7 +863,7 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
800863 return (
801864 < div className = 'w-full space-y-1.5' >
802865 { renderCollapsibleContent ( ) }
803- { hasPlan && < PlanSteps steps = { allParsed . plan ! } streaming = { isStreaming } /> }
866+ { hasPlan && planToRender && < PlanSteps steps = { planToRender } streaming = { isPlanStreaming } /> }
804867 </ div >
805868 )
806869 }
@@ -832,7 +895,7 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
832895 </ div >
833896
834897 { /* Plan stays outside the collapsible */ }
835- { hasPlan && < PlanSteps steps = { allParsed . plan ! } /> }
898+ { hasPlan && planToRender && < PlanSteps steps = { planToRender } /> }
836899 </ div >
837900 )
838901} )
@@ -1412,7 +1475,11 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
14121475 if (
14131476 toolCall . name === 'checkoff_todo' ||
14141477 toolCall . name === 'mark_todo_in_progress' ||
1415- toolCall . name === 'tool_search_tool_regex'
1478+ toolCall . name === 'tool_search_tool_regex' ||
1479+ toolCall . name === 'user_memory' ||
1480+ toolCall . name === 'edit_responsd' ||
1481+ toolCall . name === 'debug_respond' ||
1482+ toolCall . name === 'plan_respond'
14161483 )
14171484 return null
14181485
0 commit comments