diff --git a/lib/app/app_controller_desktop.dart b/lib/app/app_controller_desktop.dart index 51f7c237..0ae1bca4 100644 --- a/lib/app/app_controller_desktop.dart +++ b/lib/app/app_controller_desktop.dart @@ -3577,12 +3577,17 @@ class AppController extends ChangeNotifier { RuntimeConnectionMode _modeFromHost(String host) { final trimmed = host.trim().toLowerCase(); - if (trimmed == '127.0.0.1' || trimmed == 'localhost') { + if (_isLoopbackHost(trimmed)) { return RuntimeConnectionMode.local; } return RuntimeConnectionMode.remote; } + bool _isLoopbackHost(String host) { + final trimmed = host.trim().toLowerCase(); + return trimmed == '127.0.0.1' || trimmed == 'localhost'; + } + AssistantExecutionTarget _assistantExecutionTargetForMode( RuntimeConnectionMode mode, ) { @@ -3628,10 +3633,14 @@ class AppController extends ChangeNotifier { } final defaults = GatewayConnectionProfile.defaults(); - final savedHost = savedProfile.host.trim().isEmpty + final useDefaultRemoteEndpoint = + savedProfile.host.trim().isEmpty || + _isLoopbackHost(savedProfile.host) || + savedProfile.port <= 0; + final savedHost = useDefaultRemoteEndpoint ? defaults.host : savedProfile.host.trim(); - final savedPort = savedProfile.port <= 0 + final savedPort = useDefaultRemoteEndpoint ? defaults.port : savedProfile.port; return savedProfile.copyWith( @@ -3640,7 +3649,7 @@ class AppController extends ChangeNotifier { setupCode: '', host: savedHost, port: savedPort, - tls: savedProfile.tls, + tls: useDefaultRemoteEndpoint ? defaults.tls : savedProfile.tls, ); } } diff --git a/test/runtime/app_controller_execution_target_switch_suite.dart b/test/runtime/app_controller_execution_target_switch_suite.dart index 0499b210..74014945 100644 --- a/test/runtime/app_controller_execution_target_switch_suite.dart +++ b/test/runtime/app_controller_execution_target_switch_suite.dart @@ -376,6 +376,10 @@ void main() { controller.assistantExecutionTarget, AssistantExecutionTarget.remote, ); + expect( + controller.assistantConnectionTargetLabel, + 'gateway.example.com:9443', + ); expect(completed, isFalse); connectGate.complete(); @@ -389,6 +393,68 @@ void main() { }, ); + test( + 'AppController does not leak the local endpoint into remote thread status while reconnecting', + () async { + SharedPreferences.setMockInitialValues({}); + final tempDirectory = await Directory.systemTemp.createTemp( + 'xworkmate-execution-target-remote-fallback-', + ); + addTearDown(() async { + await _deleteDirectoryWithRetry(tempDirectory); + }); + final store = SecureConfigStore( + enableSecureStorage: false, + databasePathResolver: () async => '${tempDirectory.path}/settings.db', + fallbackDirectoryPathResolver: () async => tempDirectory.path, + ); + final gateway = _FakeGatewayRuntime(store: store); + final controller = AppController( + store: store, + runtimeCoordinator: RuntimeCoordinator( + gateway: gateway, + codex: _FakeCodexRuntime(), + ), + ); + addTearDown(controller.dispose); + + await _waitFor(() => !controller.initializing); + await controller.saveSettings( + controller.settings.copyWith( + gateway: controller.settings.gateway.copyWith( + mode: RuntimeConnectionMode.local, + host: '127.0.0.1', + port: 18789, + tls: false, + ), + ), + refreshAfterSave: false, + ); + + final connectGate = Completer(); + gateway.holdNextConnect(connectGate); + + final switchFuture = controller.setAssistantExecutionTarget( + AssistantExecutionTarget.remote, + ); + + await Future.delayed(Duration.zero); + + expect( + controller.assistantExecutionTarget, + AssistantExecutionTarget.remote, + ); + expect(controller.assistantConnectionStatusLabel, '离线'); + expect( + controller.assistantConnectionTargetLabel, + 'openclaw.svc.plus:443', + ); + + connectGate.complete(); + await switchFuture; + }, + ); + test( 'AppController notifies aiGatewayOnly target changes before disconnect completes', () async {