1+ import { v4 as uuidv4 } from 'uuid'
12import type { ExecutionResult , StreamingExecution } from '@/executor/types'
3+ import { useTerminalConsoleStore } from '@/stores/terminal'
4+ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
25import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
36
47export interface WorkflowExecutionOptions {
@@ -11,7 +14,7 @@ export interface WorkflowExecutionOptions {
1114
1215/**
1316 * Execute workflow with full logging (used by copilot tools)
14- * This now delegates to the server-side executor via API
17+ * Handles SSE streaming and populates console logs in real-time
1518 */
1619export async function executeWorkflowWithFullLogging (
1720 options : WorkflowExecutionOptions = { }
@@ -22,25 +25,148 @@ export async function executeWorkflowWithFullLogging(
2225 throw new Error ( 'No active workflow' )
2326 }
2427
25- // For copilot tool calls, we use non-SSE execution to get a simple result
28+ // Check if there's an active diff workflow to execute
29+ const { diffWorkflow, isDiffReady, isShowingDiff } = useWorkflowDiffStore . getState ( )
30+ const hasActiveDiffWorkflow =
31+ isDiffReady &&
32+ isShowingDiff &&
33+ ! ! diffWorkflow &&
34+ Object . keys ( diffWorkflow . blocks || { } ) . length > 0
35+
36+ const executionId = options . executionId || uuidv4 ( )
37+ const { addConsole } = useTerminalConsoleStore . getState ( )
38+
39+ // Build request payload
40+ const payload : any = {
41+ input : options . workflowInput ,
42+ stream : true ,
43+ triggerType : options . overrideTriggerType || 'manual' ,
44+ useDraftState : true ,
45+ }
46+
47+ // Add diff workflow override if active
48+ if ( hasActiveDiffWorkflow ) {
49+ payload . workflowStateOverride = {
50+ blocks : diffWorkflow . blocks ,
51+ edges : diffWorkflow . edges ,
52+ loops : diffWorkflow . loops ,
53+ parallels : diffWorkflow . parallels ,
54+ }
55+ }
56+
2657 const response = await fetch ( `/api/workflows/${ activeWorkflowId } /execute` , {
2758 method : 'POST' ,
2859 headers : {
2960 'Content-Type' : 'application/json' ,
3061 } ,
31- body : JSON . stringify ( {
32- input : options . workflowInput ,
33- stream : false , // Copilot doesn't need SSE streaming
34- triggerType : options . overrideTriggerType || 'manual' ,
35- useDraftState : true ,
36- } ) ,
62+ body : JSON . stringify ( payload ) ,
3763 } )
3864
3965 if ( ! response . ok ) {
4066 const error = await response . json ( )
4167 throw new Error ( error . error || 'Workflow execution failed' )
4268 }
4369
44- const result = await response . json ( )
45- return result as ExecutionResult
70+ if ( ! response . body ) {
71+ throw new Error ( 'No response body' )
72+ }
73+
74+ // Parse SSE stream
75+ const reader = response . body . getReader ( )
76+ const decoder = new TextDecoder ( )
77+ let buffer = ''
78+ let executionResult : ExecutionResult = {
79+ success : false ,
80+ output : { } ,
81+ logs : [ ] ,
82+ }
83+
84+ try {
85+ while ( true ) {
86+ const { done, value } = await reader . read ( )
87+ if ( done ) break
88+
89+ buffer += decoder . decode ( value , { stream : true } )
90+ const lines = buffer . split ( '\n\n' )
91+ buffer = lines . pop ( ) || ''
92+
93+ for ( const line of lines ) {
94+ if ( ! line . trim ( ) || ! line . startsWith ( 'data: ' ) ) continue
95+
96+ const data = line . substring ( 6 ) . trim ( )
97+ if ( data === '[DONE]' ) continue
98+
99+ try {
100+ const event = JSON . parse ( data )
101+
102+ switch ( event . type ) {
103+ case 'block:completed' :
104+ addConsole ( {
105+ input : event . data . input || { } ,
106+ output : event . data . output ,
107+ success : true ,
108+ durationMs : event . data . durationMs ,
109+ startedAt : new Date ( Date . now ( ) - event . data . durationMs ) . toISOString ( ) ,
110+ endedAt : new Date ( ) . toISOString ( ) ,
111+ workflowId : activeWorkflowId ,
112+ blockId : event . data . blockId ,
113+ executionId,
114+ blockName : event . data . blockName ,
115+ blockType : event . data . blockType ,
116+ iterationCurrent : event . data . iterationCurrent ,
117+ iterationTotal : event . data . iterationTotal ,
118+ iterationType : event . data . iterationType ,
119+ } )
120+
121+ if ( options . onBlockComplete ) {
122+ options . onBlockComplete ( event . data . blockId , event . data . output ) . catch ( ( ) => { } )
123+ }
124+ break
125+
126+ case 'block:error' :
127+ addConsole ( {
128+ input : event . data . input || { } ,
129+ output : { } ,
130+ success : false ,
131+ error : event . data . error ,
132+ durationMs : event . data . durationMs ,
133+ startedAt : new Date ( Date . now ( ) - event . data . durationMs ) . toISOString ( ) ,
134+ endedAt : new Date ( ) . toISOString ( ) ,
135+ workflowId : activeWorkflowId ,
136+ blockId : event . data . blockId ,
137+ executionId,
138+ blockName : event . data . blockName ,
139+ blockType : event . data . blockType ,
140+ iterationCurrent : event . data . iterationCurrent ,
141+ iterationTotal : event . data . iterationTotal ,
142+ iterationType : event . data . iterationType ,
143+ } )
144+ break
145+
146+ case 'execution:completed' :
147+ executionResult = {
148+ success : event . data . success ,
149+ output : event . data . output ,
150+ logs : [ ] ,
151+ metadata : {
152+ duration : event . data . duration ,
153+ startTime : event . data . startTime ,
154+ endTime : event . data . endTime ,
155+ } ,
156+ }
157+ break
158+
159+ case 'execution:error' :
160+ throw new Error ( event . data . error || 'Execution failed' )
161+ }
162+ } catch ( parseError ) {
163+ // Skip malformed SSE events
164+ }
165+ }
166+ }
167+ } finally {
168+ reader . releaseLock ( )
169+ }
170+
171+ return executionResult
46172}
0 commit comments