@@ -105,7 +105,7 @@ export async function runControllerView(
105105 debug ( '[ControllerView] Recovery: originalControllerView=%s' , originalControllerView ) ;
106106
107107 if ( originalControllerView ) {
108- // Resume to controller view - wait for user to trigger workflow:controller-continue
108+ // Resume to controller view - set up conversation loop for existing session
109109 await setAutonomousMode ( cmRoot , 'never' ) ;
110110 emitter . setWorkflowView ( 'controller' ) ;
111111 emitter . updateControllerStatus ( 'awaiting' ) ;
@@ -114,15 +114,90 @@ export async function runControllerView(
114114 monitoringId : existingConfig . controllerConfig . monitoringId ,
115115 } ) ;
116116
117- debug ( '[ControllerView] Recovery: waiting for workflow:controller-continue' ) ;
118- await new Promise < void > ( ( resolve ) => {
119- const onContinue = ( ) => {
117+ debug ( '[ControllerView] Recovery: starting conversation loop for existing session' ) ;
118+
119+ // Conversation loop for recovery - handles both workflow:input and workflow:controller-continue
120+ await new Promise < void > ( ( resolve , reject ) => {
121+ const controllerConfig = {
122+ agentId : existingConfig . controllerConfig ! . agentId ,
123+ sessionId : existingConfig . controllerConfig ! . sessionId ,
124+ monitoringId : existingConfig . controllerConfig ! . monitoringId ,
125+ engine : controllerAgent ?. engine ,
126+ model : controllerAgent ?. modelName ,
127+ } ;
128+
129+ const cleanup = ( ) => {
130+ ; ( process as NodeJS . EventEmitter ) . off ( 'workflow:input' , onInput ) ;
120131 ; ( process as NodeJS . EventEmitter ) . off ( 'workflow:controller-continue' , onContinue ) ;
132+ } ;
133+
134+ const onInput = async ( data : { prompt ?: string ; skip ?: boolean } ) => {
135+ debug ( '[ControllerView] Recovery: received input: prompt="%s" skip=%s' ,
136+ data . prompt ?. slice ( 0 , 50 ) ?? '(empty)' , data . skip ?? false ) ;
137+
138+ // Skip signal means user wants to end controller view
139+ if ( data . skip ) {
140+ debug ( '[ControllerView] Recovery: skip signal - ending conversation' ) ;
141+ cleanup ( ) ;
142+ resolve ( ) ;
143+ return ;
144+ }
145+
146+ // Empty prompt means user is done
147+ if ( ! data . prompt || data . prompt . trim ( ) === '' ) {
148+ debug ( '[ControllerView] Recovery: empty prompt - ending conversation' ) ;
149+ cleanup ( ) ;
150+ resolve ( ) ;
151+ return ;
152+ }
153+
154+ // Run conversation turn
155+ emitter . updateControllerStatus ( 'running' ) ;
156+ emitter . setInputState ( null ) ;
157+
158+ try {
159+ // Log input and execute agent
160+ const formatted = formatUserInput ( data . prompt ) ;
161+ AgentLoggerService . getInstance ( ) . write ( controllerConfig . monitoringId , `\n${ formatted } \n` ) ;
162+
163+ // Add USER prefix with system username so controller knows input source
164+ const prefixedPrompt = controllerPrefixUser ( os . userInfo ( ) . username , data . prompt ) ;
165+
166+ await executeAgent ( controllerConfig . agentId , prefixedPrompt , {
167+ workingDir : cwd ,
168+ resumeSessionId : controllerConfig . sessionId ,
169+ resumePrompt : prefixedPrompt ,
170+ resumeMonitoringId : controllerConfig . monitoringId ,
171+ engine : controllerConfig . engine ,
172+ model : controllerConfig . model ,
173+ } ) ;
174+
175+ // After response, go back to awaiting input
176+ emitter . updateControllerStatus ( 'awaiting' ) ;
177+ emitter . setInputState ( {
178+ active : true ,
179+ monitoringId : controllerConfig . monitoringId ,
180+ } ) ;
181+ debug ( '[ControllerView] Recovery: turn complete, waiting for next input' ) ;
182+ } catch ( error ) {
183+ debug ( '[ControllerView] Recovery: error during conversation: %s' , ( error as Error ) . message ) ;
184+ cleanup ( ) ;
185+ reject ( error ) ;
186+ }
187+ } ;
188+
189+ const onContinue = ( ) => {
190+ debug ( '[ControllerView] Recovery: received controller-continue signal' ) ;
191+ cleanup ( ) ;
121192 resolve ( ) ;
122193 } ;
194+
195+ ; ( process as NodeJS . EventEmitter ) . on ( 'workflow:input' , onInput ) ;
123196 ; ( process as NodeJS . EventEmitter ) . on ( 'workflow:controller-continue' , onContinue ) ;
124197 } ) ;
125198
199+ debug ( '[ControllerView] Recovery: conversation loop ended, transitioning to executing view' ) ;
200+
126201 // User triggered continue - transition to executing
127202 emitter . setInputState ( null ) ;
128203 emitter . updateControllerStatus ( 'completed' ) ;
0 commit comments