@@ -9,6 +9,7 @@ import SwiftUI
99final class AppState {
1010 private let isPreview : Bool
1111 private var isInitializing = true
12+ private var isApplyingRemoteTokenConfig = false
1213 private var configWatcher : ConfigFileWatcher ?
1314 private var suppressVoiceWakeGlobalSync = false
1415 private var voiceWakeGlobalSyncTask : Task < Void , Never > ?
@@ -214,9 +215,17 @@ final class AppState {
214215 }
215216
216217 var remoteToken : String {
217- didSet { self . syncGatewayConfigIfNeeded ( ) }
218+ didSet {
219+ guard !self . isApplyingRemoteTokenConfig else { return }
220+ self . remoteTokenDirty = true
221+ self . remoteTokenUnsupported = false
222+ self . syncGatewayConfigIfNeeded ( )
223+ }
218224 }
219225
226+ private( set) var remoteTokenDirty = false
227+ private( set) var remoteTokenUnsupported = false
228+
220229 var remoteIdentity : String {
221230 didSet { self . ifNotPreview { UserDefaults . standard. set ( self . remoteIdentity, forKey: remoteIdentityKey) } }
222231 }
@@ -285,6 +294,7 @@ final class AppState {
285294
286295 let configRoot = OpenClawConfigFile . loadDict ( )
287296 let configRemoteUrl = GatewayRemoteConfig . resolveUrlString ( root: configRoot)
297+ let configRemoteToken = GatewayRemoteConfig . resolveTokenValue ( root: configRoot)
288298 let configRemoteTransport = GatewayRemoteConfig . resolveTransport ( root: configRoot)
289299 let resolvedConnectionMode = ConnectionModeResolver . resolve ( root: configRoot) . mode
290300 self . remoteTransport = configRemoteTransport
@@ -301,7 +311,9 @@ final class AppState {
301311 self . remoteTarget = storedRemoteTarget
302312 }
303313 self . remoteUrl = configRemoteUrl ?? " "
304- self . remoteToken = GatewayRemoteConfig . resolveTokenString ( root: configRoot) ?? " "
314+ self . remoteToken = configRemoteToken. textFieldValue
315+ self . remoteTokenDirty = false
316+ self . remoteTokenUnsupported = configRemoteToken. isUnsupportedNonString
305317 self . remoteIdentity = UserDefaults . standard. string ( forKey: remoteIdentityKey) ?? " "
306318 self . remoteProjectRoot = UserDefaults . standard. string ( forKey: remoteProjectRootKey) ?? " "
307319 self . remoteCliPath = UserDefaults . standard. string ( forKey: remoteCliPathKey) ?? " "
@@ -379,14 +391,29 @@ final class AppState {
379391 return false
380392 }
381393
394+ private func applyRemoteTokenState( _ tokenValue: GatewayRemoteConfig . TokenValue ) {
395+ let nextToken = tokenValue. textFieldValue
396+ let unsupported = tokenValue. isUnsupportedNonString
397+ guard self . remoteToken != nextToken || self . remoteTokenDirty || self . remoteTokenUnsupported != unsupported
398+ else {
399+ return
400+ }
401+ self . isApplyingRemoteTokenConfig = true
402+ self . remoteToken = nextToken
403+ self . isApplyingRemoteTokenConfig = false
404+ self . remoteTokenDirty = false
405+ self . remoteTokenUnsupported = unsupported
406+ }
407+
382408 private static func updatedRemoteGatewayConfig(
383409 current: [ String : Any ] ,
384410 transport: RemoteTransport ,
385411 remoteUrl: String ,
386412 remoteHost: String ? ,
387413 remoteTarget: String ,
388414 remoteIdentity: String ,
389- remoteToken: String ) -> ( remote: [ String : Any ] , changed: Bool )
415+ remoteToken: String ,
416+ remoteTokenDirty: Bool ) -> ( remote: [ String : Any ] , changed: Bool )
390417 {
391418 var remote = current
392419 var changed = false
@@ -423,7 +450,9 @@ final class AppState {
423450 changed = Self . updateGatewayString ( & remote, key: " sshIdentity " , value: remoteIdentity) || changed
424451 }
425452
426- changed = Self . updateGatewayString ( & remote, key: " token " , value: remoteToken) || changed
453+ if remoteTokenDirty {
454+ changed = Self . updateGatewayString ( & remote, key: " token " , value: remoteToken) || changed
455+ }
427456
428457 return ( remote, changed)
429458 }
@@ -447,7 +476,7 @@ final class AppState {
447476 let gateway = root [ " gateway " ] as? [ String : Any ]
448477 let modeRaw = ( gateway ? [ " mode " ] as? String ) ? . trimmingCharacters ( in: . whitespacesAndNewlines)
449478 let remoteUrl = GatewayRemoteConfig . resolveUrlString ( root: root)
450- let remoteToken = GatewayRemoteConfig . resolveTokenString ( root: root) ?? " "
479+ let remoteToken = GatewayRemoteConfig . resolveTokenValue ( root: root)
451480 let hasRemoteUrl = !( remoteUrl?
452481 . trimmingCharacters ( in: . whitespacesAndNewlines)
453482 . isEmpty ?? true )
@@ -479,9 +508,7 @@ final class AppState {
479508 if remoteUrlText != self . remoteUrl {
480509 self . remoteUrl = remoteUrlText
481510 }
482- if remoteToken != self . remoteToken {
483- self . remoteToken = remoteToken
484- }
511+ self . applyRemoteTokenState ( remoteToken)
485512
486513 let targetMode = desiredMode ?? self . connectionMode
487514 if targetMode == . remote,
@@ -515,7 +542,8 @@ final class AppState {
515542 remoteTarget: String ,
516543 remoteIdentity: String ,
517544 remoteUrl: String ,
518- remoteToken: String ) -> ( root: [ String : Any ] , changed: Bool )
545+ remoteToken: String ,
546+ remoteTokenDirty: Bool ) -> ( root: [ String : Any ] , changed: Bool )
519547 {
520548 var root = currentRoot
521549 var gateway = root [ " gateway " ] as? [ String : Any ] ?? [ : ]
@@ -551,7 +579,8 @@ final class AppState {
551579 remoteHost: remoteHost,
552580 remoteTarget: remoteTarget,
553581 remoteIdentity: remoteIdentity,
554- remoteToken: remoteToken)
582+ remoteToken: remoteToken,
583+ remoteTokenDirty: remoteTokenDirty)
555584 if updated. changed {
556585 gateway [ " remote " ] = updated. remote
557586 changed = true
@@ -577,6 +606,7 @@ final class AppState {
577606 let remoteTransport = self . remoteTransport
578607 let remoteUrl = self . remoteUrl
579608 let remoteToken = self . remoteToken
609+ let remoteTokenDirty = self . remoteTokenDirty
580610
581611 Task { @MainActor in
582612 // Keep app-only connection settings local to avoid overwriting remote gateway config.
@@ -587,7 +617,8 @@ final class AppState {
587617 remoteTarget: remoteTarget,
588618 remoteIdentity: remoteIdentity,
589619 remoteUrl: remoteUrl,
590- remoteToken: remoteToken)
620+ remoteToken: remoteToken,
621+ remoteTokenDirty: remoteTokenDirty)
591622 guard synced. changed else { return }
592623 OpenClawConfigFile . saveDict ( synced. root)
593624 }
@@ -750,7 +781,8 @@ extension AppState {
750781 remoteHost: String ? ,
751782 remoteTarget: String ,
752783 remoteIdentity: String ,
753- remoteToken: String ) -> [ String : Any ]
784+ remoteToken: String ,
785+ remoteTokenDirty: Bool ) -> [ String : Any ]
754786 {
755787 Self . updatedRemoteGatewayConfig (
756788 current: current,
@@ -759,7 +791,8 @@ extension AppState {
759791 remoteHost: remoteHost,
760792 remoteTarget: remoteTarget,
761793 remoteIdentity: remoteIdentity,
762- remoteToken: remoteToken) . remote
794+ remoteToken: remoteToken,
795+ remoteTokenDirty: remoteTokenDirty) . remote
763796 }
764797
765798 static func _testSyncedGatewayRoot(
@@ -769,7 +802,8 @@ extension AppState {
769802 remoteTarget: String ,
770803 remoteIdentity: String ,
771804 remoteUrl: String ,
772- remoteToken: String ) -> [ String : Any ]
805+ remoteToken: String ,
806+ remoteTokenDirty: Bool ) -> [ String : Any ]
773807 {
774808 Self . syncedGatewayRoot (
775809 currentRoot: currentRoot,
@@ -778,7 +812,8 @@ extension AppState {
778812 remoteTarget: remoteTarget,
779813 remoteIdentity: remoteIdentity,
780814 remoteUrl: remoteUrl,
781- remoteToken: remoteToken) . root
815+ remoteToken: remoteToken,
816+ remoteTokenDirty: remoteTokenDirty) . root
782817 }
783818}
784819#endif
0 commit comments