@@ -23,8 +23,13 @@ import { useWorkflowKeyboard } from "./hooks/use-workflow-keyboard"
2323import { calculateVisibleItems } from "./constants"
2424import type { WorkflowEventBus } from "../../../../workflows/events/index.js"
2525import { setAutonomousMode as persistAutonomousMode , loadControllerConfig } from "../../../../shared/workflows/index.js"
26+ import { debug } from "../../../../shared/logging/logger.js"
2627import path from "path"
2728
29+ /** Expand ~ to home directory if present */
30+ const resolvePath = ( dir : string ) : string =>
31+ dir . startsWith ( '~' ) ? dir . replace ( '~' , process . env . HOME || '' ) : dir
32+
2833export interface WorkflowShellProps {
2934 version : string
3035 currentDir : string
@@ -105,16 +110,28 @@ export function WorkflowShell(props: WorkflowShellProps) {
105110 setErrorMessage ( null )
106111 }
107112
113+ // Mode change listener - syncs UI state when autonomousMode changes
114+ const handleModeChange = ( data : { autonomousMode : boolean } ) => {
115+ debug ( '[MODE-CHANGE] Received event: autonomousMode=%s' , data . autonomousMode )
116+ ui . actions . setAutonomousMode ( data . autonomousMode )
117+ }
118+
108119 onMount ( async ( ) => {
109120 ; ( process as NodeJS . EventEmitter ) . on ( 'workflow:error' , handleWorkflowError )
110121 ; ( process as NodeJS . EventEmitter ) . on ( 'workflow:stopping' , handleStopping )
111122 ; ( process as NodeJS . EventEmitter ) . on ( 'workflow:user-stop' , handleUserStop )
123+ ; ( process as NodeJS . EventEmitter ) . on ( 'workflow:mode-change' , handleModeChange )
112124
113125 // Load initial autonomous mode state
114- const cmRoot = path . join ( props . currentDir , '.codemachine' )
126+ const cmRoot = path . join ( resolvePath ( props . currentDir ) , '.codemachine' )
127+ debug ( 'onMount - loading controller config from: %s' , cmRoot )
115128 const controllerState = await loadControllerConfig ( cmRoot )
129+ debug ( 'onMount - controllerState: %s' , JSON . stringify ( controllerState ) )
116130 if ( controllerState ?. autonomousMode ) {
131+ debug ( 'onMount - setting autonomousMode to true' )
117132 ui . actions . setAutonomousMode ( true )
133+ } else {
134+ debug ( 'onMount - autonomousMode not enabled in config' )
118135 }
119136
120137 if ( props . eventBus ) {
@@ -129,6 +146,7 @@ export function WorkflowShell(props: WorkflowShellProps) {
129146 ; ( process as NodeJS . EventEmitter ) . off ( 'workflow:error' , handleWorkflowError )
130147 ; ( process as NodeJS . EventEmitter ) . off ( 'workflow:stopping' , handleStopping )
131148 ; ( process as NodeJS . EventEmitter ) . off ( 'workflow:user-stop' , handleUserStop )
149+ ; ( process as NodeJS . EventEmitter ) . off ( 'workflow:mode-change' , handleModeChange )
132150 if ( adapter ) {
133151 adapter . stop ( )
134152 adapter . disconnect ( )
@@ -284,15 +302,43 @@ export function WorkflowShell(props: WorkflowShellProps) {
284302 ; ( process as NodeJS . EventEmitter ) . emit ( "workflow:pause" )
285303 }
286304
287- // Disable autonomous mode
288- const disableAutonomousMode = ( ) => {
289- const cwd = props . currentDir
290- const cmRoot = path . join ( cwd , '.codemachine' )
291- ui . actions . setAutonomousMode ( false )
292- persistAutonomousMode ( cmRoot , false ) . catch ( ( ) => {
293- // best-effort persistence
294- } )
295- toast . show ( { variant : "warning" , message : "Autonomous mode disabled" , duration : 3000 } )
305+ // Toggle autonomous mode on/off
306+ const toggleAutonomousMode = async ( ) => {
307+ const cmRoot = path . join ( resolvePath ( props . currentDir ) , '.codemachine' )
308+
309+ // Read current state from file (source of truth)
310+ const controllerState = await loadControllerConfig ( cmRoot )
311+ debug ( '[TOGGLE] controllerState: %s' , JSON . stringify ( controllerState ) )
312+ const currentMode = controllerState ?. autonomousMode ?? false
313+ const newMode = ! currentMode
314+
315+ debug ( '[TOGGLE] Current mode from file: %s, new mode: %s' , currentMode , newMode )
316+
317+ // Check if controller is configured (required for autonomous mode)
318+ if ( newMode && ! controllerState ?. controllerConfig ) {
319+ debug ( '[TOGGLE] Cannot enable autonomous mode - no controller configured' )
320+ toast . show ( { variant : "error" , message : "Cannot enable: No controller configured" , duration : 3000 } )
321+ return
322+ }
323+
324+ // Update UI state
325+ ui . actions . setAutonomousMode ( newMode )
326+
327+ // Persist to file (this also emits workflow:mode-change event)
328+ try {
329+ await persistAutonomousMode ( cmRoot , newMode )
330+ debug ( '[TOGGLE] Successfully persisted autonomousMode=%s' , newMode )
331+ toast . show ( {
332+ variant : newMode ? "success" : "warning" ,
333+ message : newMode ? "Autonomous mode enabled" : "Autonomous mode disabled" ,
334+ duration : 3000
335+ } )
336+ } catch ( err ) {
337+ debug ( '[TOGGLE] Failed to persist autonomousMode: %s' , err )
338+ // Revert UI state on error
339+ ui . actions . setAutonomousMode ( currentMode )
340+ toast . show ( { variant : "error" , message : "Failed to toggle autonomous mode" , duration : 3000 } )
341+ }
296342 }
297343
298344 const getMonitoringId = ( uiAgentId : string ) : number | undefined => {
@@ -329,7 +375,7 @@ export function WorkflowShell(props: WorkflowShellProps) {
329375 focusPromptBox : ( ) => setIsPromptBoxFocused ( true ) ,
330376 exitPromptBoxFocus : ( ) => setIsPromptBoxFocused ( false ) ,
331377 isAutonomousMode : ( ) => state ( ) . autonomousMode ,
332- disableAutonomousMode ,
378+ toggleAutonomousMode ,
333379 } )
334380
335381 return (
0 commit comments