@@ -15,6 +15,8 @@ import {
1515 hasDirectModeTask ,
1616 getGitHubDropDefaults ,
1717 setPrefillPrompt ,
18+ setDockerAvailable ,
19+ setDockerImage ,
1820} from '../store/store' ;
1921import { toBranchName , sanitizeBranchPrefix } from '../lib/branch-name' ;
2022import { cleanTaskName } from '../lib/clean-task-name' ;
@@ -42,6 +44,7 @@ export function NewTaskDialog(props: NewTaskDialogProps) {
4244 const [ selectedDirs , setSelectedDirs ] = createSignal < Set < string > > ( new Set ( ) ) ;
4345 const [ directMode , setDirectMode ] = createSignal ( false ) ;
4446 const [ skipPermissions , setSkipPermissions ] = createSignal ( false ) ;
47+ const [ dockerMode , setDockerMode ] = createSignal ( false ) ;
4548 const [ branchPrefix , setBranchPrefix ] = createSignal ( '' ) ;
4649 let promptRef ! : HTMLTextAreaElement ;
4750 let formRef ! : HTMLFormElement ;
@@ -105,8 +108,14 @@ export function NewTaskDialog(props: NewTaskDialogProps) {
105108 setLoading ( false ) ;
106109 setDirectMode ( false ) ;
107110 setSkipPermissions ( false ) ;
111+ setDockerMode ( false ) ;
108112
109113 void ( async ( ) => {
114+ // Check Docker availability in background
115+ invoke < boolean > ( IPC . CheckDockerAvailable ) . then (
116+ ( available ) => setDockerAvailable ( available ) ,
117+ ( ) => setDockerAvailable ( false ) ,
118+ ) ;
110119 if ( store . availableAgents . length === 0 ) {
111120 await loadAgents ( ) ;
112121 }
@@ -296,6 +305,8 @@ export function NewTaskDialog(props: NewTaskDialogProps) {
296305 initialPrompt : isFromDrop ? undefined : p ,
297306 githubUrl : ghUrl ,
298307 skipPermissions : agentSupportsSkipPermissions ( ) && skipPermissions ( ) ,
308+ dockerMode : dockerMode ( ) || undefined ,
309+ dockerImage : dockerMode ( ) ? store . dockerImage : undefined ,
299310 } ) ;
300311 } else {
301312 taskId = await createTask ( {
@@ -307,6 +318,8 @@ export function NewTaskDialog(props: NewTaskDialogProps) {
307318 branchPrefixOverride : prefix ,
308319 githubUrl : ghUrl ,
309320 skipPermissions : agentSupportsSkipPermissions ( ) && skipPermissions ( ) ,
321+ dockerMode : dockerMode ( ) || undefined ,
322+ dockerImage : dockerMode ( ) ? store . dockerImage : undefined ,
310323 } ) ;
311324 }
312325 // Drop flow: prefill prompt without auto-sending
@@ -589,6 +602,72 @@ export function NewTaskDialog(props: NewTaskDialogProps) {
589602 </ div >
590603 </ Show >
591604
605+ { /* Docker isolation toggle */ }
606+ < Show when = { store . dockerAvailable } >
607+ < div
608+ data-nav-field = "docker-mode"
609+ style = { { display : 'flex' , 'flex-direction' : 'column' , gap : '8px' } }
610+ >
611+ < label
612+ style = { {
613+ display : 'flex' ,
614+ 'align-items' : 'center' ,
615+ gap : '8px' ,
616+ 'font-size' : '12px' ,
617+ color : theme . fg ,
618+ cursor : 'pointer' ,
619+ } }
620+ >
621+ < input
622+ type = "checkbox"
623+ checked = { dockerMode ( ) }
624+ onChange = { ( e ) => setDockerMode ( e . currentTarget . checked ) }
625+ style = { { 'accent-color' : theme . accent , cursor : 'inherit' } }
626+ />
627+ Run in Docker container
628+ </ label >
629+ < Show when = { dockerMode ( ) } >
630+ < div
631+ style = { {
632+ 'font-size' : '12px' ,
633+ color : theme . success ?? theme . accent ,
634+ background : `color-mix(in srgb, ${ theme . success ?? theme . accent } 8%, transparent)` ,
635+ padding : '8px 12px' ,
636+ 'border-radius' : '8px' ,
637+ border : `1px solid color-mix(in srgb, ${ theme . success ?? theme . accent } 20%, transparent)` ,
638+ } }
639+ >
640+ The agent will run inside a Docker container. Only the project directory is mounted
641+ — files outside the project are protected from accidental deletion.
642+ </ div >
643+ < div style = { { display : 'flex' , 'align-items' : 'center' , gap : '8px' } } >
644+ < label
645+ style = { { 'font-size' : '11px' , color : theme . fgMuted , 'white-space' : 'nowrap' } }
646+ >
647+ Image:
648+ </ label >
649+ < input
650+ type = "text"
651+ value = { store . dockerImage }
652+ onInput = { ( e ) => setDockerImage ( e . currentTarget . value ) }
653+ placeholder = "ubuntu:latest"
654+ style = { {
655+ flex : '1' ,
656+ background : theme . bgInput ,
657+ border : `1px solid ${ theme . border } ` ,
658+ 'border-radius' : '6px' ,
659+ padding : '5px 10px' ,
660+ color : theme . fg ,
661+ 'font-size' : '12px' ,
662+ 'font-family' : "'JetBrains Mono', monospace" ,
663+ outline : 'none' ,
664+ } }
665+ />
666+ </ div >
667+ </ Show >
668+ </ div >
669+ </ Show >
670+
592671 < Show when = { ignoredDirs ( ) . length > 0 && ! directMode ( ) } >
593672 < SymlinkDirPicker
594673 dirs = { ignoredDirs ( ) }
0 commit comments