@@ -38,6 +38,7 @@ import { StartKiloCliRunDialog } from './StartKiloCliRunDialog';
3838import { AnimatedDots } from './AnimatedDots' ;
3939import { OpenClawButton } from './OpenClawButton' ;
4040import { KiloClawUpdateAvailableBanner } from './KiloClawUpdateAvailableBanner' ;
41+ import { UpgradeKiloClawDialog } from './UpgradeKiloClawDialog' ;
4142
4243const VOLUME_SIZE_GB = 10 ;
4344// Default machine spec fallback (matches kiloclaw DEFAULT_MACHINE_GUEST)
@@ -54,15 +55,13 @@ export function InstanceControls({
5455 status,
5556 mutations,
5657 onRedeploySuccess,
57- upgradeRequested,
58- onUpgradeHandled,
58+ onRequestUpgrade,
5959 gatewayReady,
6060} : {
6161 status : KiloClawDashboardStatus ;
6262 mutations : ClawMutations ;
6363 onRedeploySuccess ?: ( ) => void ;
64- upgradeRequested ?: boolean ;
65- onUpgradeHandled ?: ( ) => void ;
64+ onRequestUpgrade ?: ( ) => void ;
6665 gatewayReady ?: boolean ;
6766} ) {
6867 const posthog = usePostHog ( ) ;
@@ -84,6 +83,7 @@ export function InstanceControls({
8483 const [ kiloRunOpen , setKiloRunOpen ] = useState ( false ) ;
8584 const [ confirmRestart , setConfirmRestart ] = useState ( false ) ;
8685 const [ confirmRedeploy , setConfirmRedeploy ] = useState ( false ) ;
86+ const [ confirmUpgrade , setConfirmUpgrade ] = useState ( false ) ;
8787 const [ redeployMode , setRedeployMode ] = useState < 'redeploy' | 'upgrade' > ( 'redeploy' ) ;
8888
8989 const { updateAvailable, catalogNewerThanImage, latestAvailableVersion, latestVersion } =
@@ -113,6 +113,33 @@ export function InstanceControls({
113113 setManuallyDismissed ( true ) ;
114114 } , [ dismissKey ] ) ;
115115
116+ const openUpgradeConfirmation = useCallback ( ( ) => {
117+ if ( onRequestUpgrade ) {
118+ onRequestUpgrade ( ) ;
119+ return ;
120+ }
121+
122+ setConfirmUpgrade ( true ) ;
123+ } , [ onRequestUpgrade ] ) ;
124+
125+ const handleUpgradeConfirm = useCallback ( ( ) => {
126+ posthog ?. capture ( 'claw_redeploy_clicked' , {
127+ instance_status : status . status ,
128+ redeploy_mode : 'upgrade' ,
129+ } ) ;
130+ mutations . restartMachine . mutate (
131+ { imageTag : 'latest' } ,
132+ {
133+ onSuccess : ( ) => {
134+ toast . success ( 'Upgrading KiloClaw' ) ;
135+ setConfirmUpgrade ( false ) ;
136+ onRedeploySuccess ?.( ) ;
137+ } ,
138+ onError : err => toast . error ( err . message , { duration : 10000 } ) ,
139+ }
140+ ) ;
141+ } , [ mutations . restartMachine , onRedeploySuccess , posthog , status . status ] ) ;
142+
116143 const showUpgradeBanner =
117144 isFlyProvider && updateAvailable && ! isDismissedInStorage && ! manuallyDismissed ;
118145
@@ -132,18 +159,6 @@ export function InstanceControls({
132159 ) ;
133160 } ;
134161
135- // Toggle-flag pattern: parent sets upgradeRequested=true, we open the dialog
136- // with "upgrade" preselected, then immediately reset via onUpgradeHandled.
137- // Safe for single-click flows; won't re-fire if already true (no state change).
138- useEffect ( ( ) => {
139- if ( ! upgradeRequested ) return ;
140- if ( isFlyProvider ) {
141- setRedeployMode ( 'upgrade' ) ;
142- setConfirmRedeploy ( true ) ;
143- }
144- onUpgradeHandled ?.( ) ;
145- } , [ upgradeRequested , isFlyProvider , onUpgradeHandled ] ) ;
146-
147162 return (
148163 < div >
149164 < div className = "flex items-center gap-2" >
@@ -216,10 +231,7 @@ export function InstanceControls({
216231 < KiloClawUpdateAvailableBanner
217232 className = "mb-4"
218233 catalogNewerThanImage = { catalogNewerThanImage }
219- onUpgrade = { ( ) => {
220- setRedeployMode ( 'upgrade' ) ;
221- setConfirmRedeploy ( true ) ;
222- } }
234+ onUpgrade = { openUpgradeConfirmation }
223235 onDismiss = { dismissBanner }
224236 />
225237 ) }
@@ -458,6 +470,16 @@ export function InstanceControls({
458470 </ DialogFooter >
459471 </ DialogContent >
460472 </ Dialog >
473+ < UpgradeKiloClawDialog
474+ open = { confirmUpgrade }
475+ onOpenChange = { open => {
476+ if ( mutations . restartMachine . isPending ) return ;
477+ if ( ! open ) posthog ?. capture ( 'claw_redeploy_cancelled' ) ;
478+ setConfirmUpgrade ( open ) ;
479+ } }
480+ isPending = { mutations . restartMachine . isPending }
481+ onConfirm = { handleUpgradeConfirm }
482+ />
461483 < RunDoctorDialog
462484 open = { doctorOpen }
463485 onOpenChange = { setDoctorOpen }
0 commit comments