From 64e14beb702f7467cb0d23f97dead65ac71c68c7 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 12 Apr 2026 12:23:00 +0800 Subject: [PATCH] Remove local CLI and provider mirror decisions --- lib/app/app_controller_desktop_core.dart | 37 +++--- ...ler_desktop_runtime_coordination_impl.dart | 17 --- ...pp_controller_desktop_runtime_helpers.dart | 41 +------ ...p_controller_desktop_settings_runtime.dart | 5 +- ...ler_desktop_single_agent_go_task_flow.dart | 55 +++++++-- ...pp_controller_desktop_thread_sessions.dart | 12 +- ...op_thread_sessions_collaboration_impl.dart | 7 -- lib/features/modules/modules_page.dart | 2 +- lib/runtime/code_agent_node_orchestrator.dart | 10 -- .../go_multi_agent_mount_desktop_client.dart | 2 - lib/runtime/multi_agent_mount_resolver.dart | 1 - lib/runtime/multi_agent_mounts.dart | 53 ++------- lib/runtime/runtime_models_account.dart | 20 ---- .../runtime_models_settings_snapshot.dart | 112 +----------------- ...ntroller_desktop_runtime_cleanup_test.dart | 85 +++++++++---- ...sktop_working_directory_dispatch_test.dart | 13 +- ...t_execution_target_picker_widget_test.dart | 9 -- .../assistant_page_composer_golden_test.dart | 9 -- ...apshot_provider_sync_definitions_test.dart | 78 ++++++------ 19 files changed, 182 insertions(+), 386 deletions(-) diff --git a/lib/app/app_controller_desktop_core.dart b/lib/app/app_controller_desktop_core.dart index 7585d6c6..dd5b2022 100644 --- a/lib/app/app_controller_desktop_core.dart +++ b/lib/app/app_controller_desktop_core.dart @@ -38,7 +38,6 @@ import '../runtime/agent_registry.dart'; import '../runtime/multi_agent_mounts.dart'; import '../runtime/multi_agent_orchestrator.dart'; import '../runtime/platform_environment.dart'; -import '../runtime/single_agent_capabilities.dart'; import '../runtime/skill_directory_access.dart'; import 'task_thread_repositories.dart'; import 'app_controller_desktop_navigation.dart'; @@ -294,9 +293,6 @@ class AppController extends ChangeNotifier { GatewayAcpClient get gatewayAcpClientForTest => gatewayAcpClientInternal; - Map - singleAgentCapabilitiesByProviderInternal = - const {}; List bridgeAdvertisedProvidersInternal = const []; final Map> assistantThreadMessagesInternal = @@ -439,7 +435,6 @@ class AppController extends ChangeNotifier { bool isCodexBridgeBusyInternal = false; String? codexBridgeErrorInternal; String? codexRuntimeWarningInternal; - String? resolvedCodexCliPathInternal; CodexCooperationState codexCooperationStateInternal = CodexCooperationState.notStarted; SettingsController get settingsController => settingsControllerInternal; @@ -528,9 +523,6 @@ class AppController extends ChangeNotifier { bool get isCodexBridgeBusy => isCodexBridgeBusyInternal; String? get codexBridgeError => codexBridgeErrorInternal; String? get codexRuntimeWarning => codexRuntimeWarningInternal; - String? get resolvedCodexCliPath => resolvedCodexCliPathInternal; - bool get hasDetectedCodexCli => resolvedCodexCliPathInternal != null; - String get configuredCodexCliPath => settings.codexCliPath.trim(); CodeAgentRuntimeMode get configuredCodeAgentRuntimeMode => settings.codeAgentRuntimeMode; CodeAgentRuntimeMode get effectiveCodeAgentRuntimeMode => @@ -583,18 +575,13 @@ class AppController extends ChangeNotifier { bridgeAdvertisedProvidersInternal, ); - List get availableSingleAgentProviders => - configuredSingleAgentProviders - .where(canUseSingleAgentProviderInternal) - .toList(growable: false); - List visibleAssistantExecutionTargets( Iterable supportedTargets, ) { final supported = supportedTargets.toSet(); final visible = []; if (supported.contains(AssistantExecutionTarget.singleAgent) && - availableSingleAgentProviders.isNotEmpty) { + configuredSingleAgentProviders.isNotEmpty) { visible.add(AssistantExecutionTarget.singleAgent); } if (supported.contains(AssistantExecutionTarget.gateway)) { @@ -612,25 +599,29 @@ class AppController extends ChangeNotifier { ]; } - bool get hasAnyAvailableSingleAgentProvider => - availableSingleAgentProviders.isNotEmpty; - - bool canUseSingleAgentProviderInternal(SingleAgentProvider provider) { + bool isBridgeAdvertisedSingleAgentProviderInternal( + SingleAgentProvider provider, + ) { if (provider.isUnspecified) { return false; } - final capabilities = singleAgentCapabilitiesByProviderInternal[provider]; - return capabilities?.available == true && - capabilities!.supportsProvider(provider); + return configuredSingleAgentProviders.any( + (item) => item.providerId == provider.providerId, + ); } - SingleAgentProvider? resolvedSingleAgentProviderInternal( + SingleAgentProvider? advertisedSingleAgentProviderInternal( SingleAgentProvider selection, ) { if (selection.isUnspecified) { return null; } - return canUseSingleAgentProviderInternal(selection) ? selection : null; + for (final provider in configuredSingleAgentProviders) { + if (provider.providerId == selection.providerId) { + return provider; + } + } + return null; } List get aiGatewayConversationModelChoices { diff --git a/lib/app/app_controller_desktop_runtime_coordination_impl.dart b/lib/app/app_controller_desktop_runtime_coordination_impl.dart index 8359ed0e..1020d640 100644 --- a/lib/app/app_controller_desktop_runtime_coordination_impl.dart +++ b/lib/app/app_controller_desktop_runtime_coordination_impl.dart @@ -75,7 +75,6 @@ Future refreshAcpCapabilitiesRuntimeInternal( .reconcile( config: currentConfig, aiGatewayUrl: controller.aiGatewayUrl, - configuredCodexCliPath: controller.configuredCodexCliPath, ); if (jsonEncode(nextConfig.toJson()) != jsonEncode(currentConfig.toJson())) { await controller.settingsControllerInternal.saveSnapshot( @@ -100,15 +99,6 @@ Future refreshSingleAgentCapabilitiesRuntimeInternal( ); controller.bridgeAdvertisedProvidersInternal = normalizeSingleAgentProviderList(capabilities.providerCatalog); - final next = {}; - for (final provider in controller.bridgeAdvertisedProvidersInternal) { - next[provider] = SingleAgentCapabilities( - available: true, - supportedProviders: [provider], - endpoint: 'go-task-service', - ); - } - controller.singleAgentCapabilitiesByProviderInternal = next; if (!controller.disposedInternal) { controller.notifyListeners(); } @@ -248,8 +238,6 @@ CodeAgentNodeState buildCodeAgentNodeStateRuntimeInternal( bridgeEnabled: controller.isCodexBridgeEnabledInternal, bridgeState: controller.codexCooperationStateInternal.name, preferredProviderId: 'codex', - resolvedCodexCliPath: controller.resolvedCodexCliPathInternal, - configuredCodexCliPath: controller.configuredCodexCliPath, ); } @@ -313,11 +301,6 @@ Future ensureCodexGatewayRegistrationRuntimeInternal( 'providerId': 'codex', 'runtimeMode': controller.effectiveCodeAgentRuntimeMode.name, 'gatewayMode': bridgeGatewayModeRuntimeInternal(controller).name, - 'binaryConfigured': - (controller.resolvedCodexCliPath ?? - controller.configuredCodexCliPath) - .trim() - .isNotEmpty, 'capabilities': const [ 'chat', 'code-edit', diff --git a/lib/app/app_controller_desktop_runtime_helpers.dart b/lib/app/app_controller_desktop_runtime_helpers.dart index f722d7c9..f9fbc6b3 100644 --- a/lib/app/app_controller_desktop_runtime_helpers.dart +++ b/lib/app/app_controller_desktop_runtime_helpers.dart @@ -242,7 +242,7 @@ extension AppControllerDesktopRuntimeHelpers on AppController { sessionsControllerInternal.currentSessionKey, ); final provider = - resolvedSingleAgentProviderInternal(selection) ?? selection; + advertisedSingleAgentProviderInternal(selection) ?? selection; final providerLabel = provider.isUnspecified ? appText('Bridge Provider', 'Bridge Provider') : provider.label; @@ -436,14 +436,11 @@ extension AppControllerDesktopRuntimeHelpers on AppController { 'Built-in Codex runtime is reserved for a future release; XWorkmate switched back to External Codex CLI automatically.', ) : null; - final normalizedPath = snapshot.codexCliPath.trim(); - if (normalizedPath == snapshot.codexCliPath && - normalizedRuntimeMode == snapshot.codeAgentRuntimeMode) { + if (normalizedRuntimeMode == snapshot.codeAgentRuntimeMode) { return snapshot; } return snapshot.copyWith( codeAgentRuntimeMode: normalizedRuntimeMode, - codexCliPath: normalizedPath, ); } @@ -463,36 +460,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController { forceRefresh: forceRefresh, ); - Future refreshResolvedCodexCliPathInternal() async { - if (effectiveCodeAgentRuntimeMode != CodeAgentRuntimeMode.externalCli) { - resolvedCodexCliPathInternal = null; - return; - } - if (shouldBlockEmbeddedAgentLaunch( - isAppleHost: Platform.isIOS || Platform.isMacOS, - )) { - resolvedCodexCliPathInternal = null; - return; - } - - final configuredPath = configuredCodexCliPath; - String? detectedPath; - if (configuredPath.isNotEmpty) { - try { - if (await File(configuredPath).exists()) { - detectedPath = configuredPath; - } - } catch (_) { - detectedPath = null; - } - } - detectedPath ??= await runtimeCoordinatorInternal.codex.findCodexBinary(); - if (disposedInternal) { - return; - } - resolvedCodexCliPathInternal = detectedPath; - } - List mergeAcpCapabilitiesIntoMountTargetsInternal( List current, GatewayAcpCapabilities capabilities, @@ -637,9 +604,7 @@ extension AppControllerDesktopRuntimeHelpers on AppController { } setActiveAppLanguage(current.appLanguage); multiAgentOrchestratorInternal.updateConfig(current.multiAgent); - if (previous.codexCliPath != current.codexCliPath || - previous.codeAgentRuntimeMode != current.codeAgentRuntimeMode) { - await refreshResolvedCodexCliPathInternal(); + if (previous.codeAgentRuntimeMode != current.codeAgentRuntimeMode) { registerCodexExternalProviderInternal(); if (disposedInternal) { return; diff --git a/lib/app/app_controller_desktop_settings_runtime.dart b/lib/app/app_controller_desktop_settings_runtime.dart index a4f09a25..5fd318fd 100644 --- a/lib/app/app_controller_desktop_settings_runtime.dart +++ b/lib/app/app_controller_desktop_settings_runtime.dart @@ -526,7 +526,6 @@ extension AppControllerDesktopSettingsRuntime on AppController { await desktopPlatformServiceInternal.setLaunchAtLogin( settings.launchAtLogin, ); - await refreshResolvedCodexCliPathInternal(); registerCodexExternalProviderInternal(); await refreshSingleAgentCapabilitiesInternal(); await refreshAcpCapabilitiesInternal(persistMountTargets: true); @@ -787,9 +786,7 @@ extension AppControllerDesktopSettingsRuntime on AppController { if (disposedInternal) { return; } - if (previous.codexCliPath != current.codexCliPath || - previous.codeAgentRuntimeMode != current.codeAgentRuntimeMode) { - await refreshResolvedCodexCliPathInternal(); + if (previous.codeAgentRuntimeMode != current.codeAgentRuntimeMode) { registerCodexExternalProviderInternal(); } unawaited(refreshSingleAgentCapabilitiesInternal().catchError((_) {})); diff --git a/lib/app/app_controller_desktop_single_agent_go_task_flow.dart b/lib/app/app_controller_desktop_single_agent_go_task_flow.dart index b70487ed..62e01f9e 100644 --- a/lib/app/app_controller_desktop_single_agent_go_task_flow.dart +++ b/lib/app/app_controller_desktop_single_agent_go_task_flow.dart @@ -57,9 +57,6 @@ Future sendSingleAgentMessageDesktopGoTaskFlowInternal( sessionKey, ); final selection = controller.singleAgentProviderForSession(sessionKey); - final effectiveProvider = - controller.resolvedSingleAgentProviderInternal(selection) ?? - selection; final preflightWorkingDirectory = controller .resolveSingleAgentWorkingDirectoryForSessionInternal(sessionKey); if (preflightWorkingDirectory == null || @@ -79,8 +76,50 @@ Future sendSingleAgentMessageDesktopGoTaskFlowInternal( ); throw error; } + if (controller.resolveExternalAcpEndpointForTargetInternal( + AssistantExecutionTarget.singleAgent, + ) == + null) { + controller.upsertTaskThreadInternal( + sessionKey, + lifecycleStatus: 'ready', + lastRunAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(), + lastResultCode: 'error', + updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(), + ); + controller.appendAssistantThreadMessageInternal( + sessionKey, + assistantErrorMessageSingleAgentDesktopInternal( + controller, + appText( + 'Bridge ACP 入口当前不可用。', + 'The bridge ACP entrypoint is currently unavailable.', + ), + ), + ); + return; + } + final routingResolution = await controller.goTaskServiceClientInternal + .resolveExternalAcpRouting( + taskPrompt: message, + workingDirectory: preflightWorkingDirectory, + routing: routing, + ); + final resolvedProvider = SingleAgentProviderCopy.fromJsonValue( + routingResolution.resolvedProviderId, + ); + final effectiveProvider = !resolvedProvider.isUnspecified + ? resolvedProvider + : controller.advertisedSingleAgentProviderInternal(selection) ?? + selection; final unavailableReason = - controller.singleAgentShouldSuggestAcpSwitchForSession(sessionKey) + routingResolution.unavailable + ? singleAgentUnavailableLabelDesktopInternal( + controller, + sessionKey, + routingResolution.unavailableMessage, + ) + : controller.singleAgentShouldSuggestAcpSwitchForSession(sessionKey) ? singleAgentUnavailableLabelDesktopInternal( controller, sessionKey, @@ -95,14 +134,6 @@ Future sendSingleAgentMessageDesktopGoTaskFlowInternal( 'The bridge does not currently have any synced providers.', ), ) - : controller.resolveExternalAcpEndpointForTargetInternal( - AssistantExecutionTarget.singleAgent, - ) == - null - ? appText( - 'Bridge ACP 入口当前不可用。', - 'The bridge ACP entrypoint is currently unavailable.', - ) : null; if (unavailableReason != null) { controller.upsertTaskThreadInternal( diff --git a/lib/app/app_controller_desktop_thread_sessions.dart b/lib/app/app_controller_desktop_thread_sessions.dart index 55e93f16..bd4d1b7e 100644 --- a/lib/app/app_controller_desktop_thread_sessions.dart +++ b/lib/app/app_controller_desktop_thread_sessions.dart @@ -269,7 +269,7 @@ extension AppControllerDesktopThreadSessions on AppController { final normalizedSessionKey = normalizedAssistantSessionKeyInternal( sessionKey, ); - return resolvedSingleAgentProviderInternal( + return advertisedSingleAgentProviderInternal( singleAgentProviderForSession(normalizedSessionKey), ); } @@ -285,7 +285,7 @@ extension AppControllerDesktopThreadSessions on AppController { AssistantExecutionTarget.singleAgent) { return false; } - return !hasAnyAvailableSingleAgentProvider; + return configuredSingleAgentProviders.isEmpty; } bool get currentSingleAgentNeedsBridgeProvider => @@ -310,8 +310,8 @@ extension AppControllerDesktopThreadSessions on AppController { if (selection.isUnspecified) { return false; } - return !canUseSingleAgentProviderInternal(selection) && - hasAnyAvailableSingleAgentProvider; + return !isBridgeAdvertisedSingleAgentProviderInternal(selection) && + configuredSingleAgentProviders.isNotEmpty; } bool get currentSingleAgentShouldSuggestAcpSwitch => @@ -371,9 +371,7 @@ extension AppControllerDesktopThreadSessions on AppController { singleAgentShouldShowModelControlForSession(currentSessionKey); List get singleAgentProviderOptions => - availableSingleAgentProviders.isNotEmpty - ? availableSingleAgentProviders - : configuredSingleAgentProviders; + configuredSingleAgentProviders; String singleAgentProviderLabelForSession(String sessionKey) { return singleAgentProviderForSession(sessionKey).label; diff --git a/lib/app/app_controller_desktop_thread_sessions_collaboration_impl.dart b/lib/app/app_controller_desktop_thread_sessions_collaboration_impl.dart index 2e0ec450..6466bcc0 100644 --- a/lib/app/app_controller_desktop_thread_sessions_collaboration_impl.dart +++ b/lib/app/app_controller_desktop_thread_sessions_collaboration_impl.dart @@ -79,7 +79,6 @@ Future refreshMultiAgentMountsThreadSessionInternal( var nextConfig = await controller.multiAgentMountManagerInternal.reconcile( config: effectiveConfig, aiGatewayUrl: controller.aiGatewayUrl, - configuredCodexCliPath: controller.configuredCodexCliPath, ); if (nextConfig.autoSync != currentConfig.autoSync) { nextConfig = nextConfig.copyWith(autoSync: currentConfig.autoSync); @@ -354,12 +353,6 @@ List assistantModelChoicesForSessionThreadSessionInternal( normalizedSessionKey, ); if (target == AssistantExecutionTarget.singleAgent) { - final singleAgentUsesAiGatewayFallback = - !controller.hasAnyAvailableSingleAgentProvider && - controller.canUseAiGatewayConversation; - if (singleAgentUsesAiGatewayFallback) { - return controller.aiGatewayConversationModelChoices; - } final runtimeModel = controller.singleAgentRuntimeModelForSession( normalizedSessionKey, ); diff --git a/lib/features/modules/modules_page.dart b/lib/features/modules/modules_page.dart index dad8fe96..8e1dd4fb 100644 --- a/lib/features/modules/modules_page.dart +++ b/lib/features/modules/modules_page.dart @@ -663,7 +663,7 @@ class _SkillsPanel extends StatelessWidget { ? StatusInfo(appText('当前模式', 'Current mode'), StatusTone.accent) : StatusInfo(appText('可切换', 'Available'), StatusTone.success), chips: [ - for (final provider in controller.availableSingleAgentProviders) + for (final provider in controller.configuredSingleAgentProviders) provider.label, ], skills: singleAgentSkills.map((item) => item.name).toList(), diff --git a/lib/runtime/code_agent_node_orchestrator.dart b/lib/runtime/code_agent_node_orchestrator.dart index 61d44673..ae67e881 100644 --- a/lib/runtime/code_agent_node_orchestrator.dart +++ b/lib/runtime/code_agent_node_orchestrator.dart @@ -12,8 +12,6 @@ class CodeAgentNodeState { required this.bridgeEnabled, required this.bridgeState, required this.preferredProviderId, - this.resolvedCodexCliPath, - this.configuredCodexCliPath = '', }); final String selectedAgentId; @@ -23,8 +21,6 @@ class CodeAgentNodeState { final bool bridgeEnabled; final String bridgeState; final String preferredProviderId; - final String? resolvedCodexCliPath; - final String configuredCodexCliPath; } /// Resolved gateway dispatch envelope for the app-mediated node. @@ -58,8 +54,6 @@ class CodeAgentNodeOrchestrator { 'runtimeMode': state.runtimeMode.name, 'bridgeEnabled': state.bridgeEnabled, 'bridgeState': state.bridgeState, - 'resolvedCodexCliPath': state.resolvedCodexCliPath?.trim() ?? '', - 'configuredCodexCliPath': state.configuredCodexCliPath.trim(), }, nodeInfo: const { 'id': 'xworkmate-app', @@ -82,9 +76,6 @@ class CodeAgentNodeOrchestrator { ) : null; final normalizedAgentId = state.selectedAgentId.trim(); - final configuredPath = state.resolvedCodexCliPath?.trim().isNotEmpty == true - ? state.resolvedCodexCliPath!.trim() - : state.configuredCodexCliPath.trim(); final metadata = { 'node': { @@ -107,7 +98,6 @@ class CodeAgentNodeOrchestrator { CodeAgentRuntimeMode.externalCli => 'stdio-jsonrpc', CodeAgentRuntimeMode.builtIn => 'ffi-runtime', }, - if (configuredPath.isNotEmpty) 'binaryConfigured': true, }, if (provider != null) 'provider': { diff --git a/lib/runtime/go_multi_agent_mount_desktop_client.dart b/lib/runtime/go_multi_agent_mount_desktop_client.dart index 79027076..1753a77e 100644 --- a/lib/runtime/go_multi_agent_mount_desktop_client.dart +++ b/lib/runtime/go_multi_agent_mount_desktop_client.dart @@ -16,7 +16,6 @@ class GoMultiAgentMountDesktopClient implements MultiAgentMountResolver { Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', required String codexHome, required String opencodeHome, required ArisMountProbe arisProbe, @@ -32,7 +31,6 @@ class GoMultiAgentMountDesktopClient implements MultiAgentMountResolver { .toList(growable: false), }, 'aiGatewayUrl': aiGatewayUrl.trim(), - 'configuredCodexCliPath': configuredCodexCliPath.trim(), 'codexHome': codexHome.trim(), 'opencodeHome': opencodeHome.trim(), 'aris': arisProbe.toJson(), diff --git a/lib/runtime/multi_agent_mount_resolver.dart b/lib/runtime/multi_agent_mount_resolver.dart index 542e1f64..9df71395 100644 --- a/lib/runtime/multi_agent_mount_resolver.dart +++ b/lib/runtime/multi_agent_mount_resolver.dart @@ -40,7 +40,6 @@ abstract class MultiAgentMountResolver { Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', required String codexHome, required String opencodeHome, required ArisMountProbe arisProbe, diff --git a/lib/runtime/multi_agent_mounts.dart b/lib/runtime/multi_agent_mounts.dart index cc334cf4..7238c720 100644 --- a/lib/runtime/multi_agent_mounts.dart +++ b/lib/runtime/multi_agent_mounts.dart @@ -52,12 +52,10 @@ class MultiAgentMountManager { Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }) async { final resolved = await _resolver?.reconcile( config: config, aiGatewayUrl: aiGatewayUrl, - configuredCodexCliPath: configuredCodexCliPath, codexHome: _codexConfigBridge.codexHome, opencodeHome: _opencodeConfigBridge.opencodeHome, arisProbe: await _buildArisProbe(), @@ -68,7 +66,6 @@ class MultiAgentMountManager { return _reconcileLocally( config: config, aiGatewayUrl: aiGatewayUrl, - configuredCodexCliPath: configuredCodexCliPath, ); } @@ -94,7 +91,6 @@ class MultiAgentMountManager { Future _reconcileLocally({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }) async { final states = []; for (final adapter in _adapters) { @@ -103,7 +99,6 @@ class MultiAgentMountManager { await adapter.reconcile( config: config, aiGatewayUrl: aiGatewayUrl, - configuredCodexCliPath: configuredCodexCliPath, ), ); } catch (error) { @@ -115,9 +110,7 @@ class MultiAgentMountManager { supportsMcp: adapter.supportsMcp, supportsAiGatewayInjection: adapter.supportsAiGatewayInjection, ).copyWith( - available: await adapter.isInstalled( - configuredCodexCliPath: configuredCodexCliPath, - ), + available: await adapter.isInstalled(), discoveryState: 'error', syncState: 'error', detail: error.toString(), @@ -150,12 +143,11 @@ abstract class CliMountAdapter { bool get supportsMcp; bool get supportsAiGatewayInjection; - Future isInstalled({String configuredCodexCliPath = ''}); + Future isInstalled(); Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }); Future _runCommand(List command) async { @@ -188,15 +180,6 @@ abstract class CliMountAdapter { .length; } - Future _binaryExists(String command) async { - final check = await Process.run( - Platform.isWindows ? 'where' : 'which', - [command], - runInShell: true, - ); - return check.exitCode == 0 && '${check.stdout}'.trim().isNotEmpty; - } - int countMcpTomlSections(String content) { return RegExp( r'^\[mcp_servers\.[^\]]+\]', @@ -230,7 +213,7 @@ class ArisMountAdapter extends CliMountAdapter { bool get supportsAiGatewayInjection => false; @override - Future isInstalled({String configuredCodexCliPath = ''}) async { + Future isInstalled() async { try { await _bundleRepository.loadManifest(); return true; @@ -243,7 +226,6 @@ class ArisMountAdapter extends CliMountAdapter { Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }) async { try { final bundle = await _bundleRepository.ensureReady(); @@ -313,23 +295,14 @@ class CodexMountAdapter extends CliMountAdapter { bool get supportsAiGatewayInjection => true; @override - Future isInstalled({String configuredCodexCliPath = ''}) async { - final configuredPath = configuredCodexCliPath.trim(); - if (configuredPath.isNotEmpty && await File(configuredPath).exists()) { - return true; - } - return _binaryExists('codex'); - } + Future isInstalled() async => false; @override Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }) async { - final available = await isInstalled( - configuredCodexCliPath: configuredCodexCliPath, - ); + final available = await isInstalled(); final configFile = File('${_bridge.codexHome}/config.toml'); final content = await configFile.exists() ? await configFile.readAsString() @@ -391,14 +364,12 @@ class ClaudeMountAdapter extends CliMountAdapter { bool get supportsAiGatewayInjection => true; @override - Future isInstalled({String configuredCodexCliPath = ''}) => - _binaryExists('claude'); + Future isInstalled() async => false; @override Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }) async { final available = await isInstalled(); final discoveredMcpCount = available @@ -441,14 +412,12 @@ class GeminiMountAdapter extends CliMountAdapter { bool get supportsAiGatewayInjection => true; @override - Future isInstalled({String configuredCodexCliPath = ''}) => - _binaryExists('gemini'); + Future isInstalled() async => false; @override Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }) async { final available = await isInstalled(); final discoveredMcpCount = available @@ -495,14 +464,12 @@ class OpencodeMountAdapter extends CliMountAdapter { bool get supportsAiGatewayInjection => true; @override - Future isInstalled({String configuredCodexCliPath = ''}) => - _binaryExists('opencode'); + Future isInstalled() async => false; @override Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }) async { final available = await isInstalled(); final content = await _bridge.readConfig(); @@ -562,14 +529,12 @@ class OpenClawMountAdapter extends CliMountAdapter { bool get supportsAiGatewayInjection => true; @override - Future isInstalled({String configuredCodexCliPath = ''}) => - _binaryExists('openclaw'); + Future isInstalled() async => false; @override Future reconcile({ required MultiAgentConfig config, required String aiGatewayUrl, - String configuredCodexCliPath = '', }) async { final available = await isInstalled(); final configFile = File( diff --git a/lib/runtime/runtime_models_account.dart b/lib/runtime/runtime_models_account.dart index 295a6439..ad2dcbc7 100644 --- a/lib/runtime/runtime_models_account.dart +++ b/lib/runtime/runtime_models_account.dart @@ -425,14 +425,12 @@ class AcpBridgeServerAdvancedOverrides { required this.gatewayProfiles, required this.vault, required this.aiGateway, - required this.acpBridgeServerProfiles, required this.authorizedSkillDirectories, }); final List gatewayProfiles; final VaultConfig vault; final AiGatewayProfile aiGateway; - final List acpBridgeServerProfiles; final List authorizedSkillDirectories; factory AcpBridgeServerAdvancedOverrides.defaults() { @@ -440,7 +438,6 @@ class AcpBridgeServerAdvancedOverrides { gatewayProfiles: normalizeGatewayProfiles(), vault: VaultConfig.defaults(), aiGateway: AiGatewayProfile.defaults(), - acpBridgeServerProfiles: normalizeExternalAcpEndpoints(), authorizedSkillDirectories: normalizeAuthorizedSkillDirectories(), ); } @@ -449,7 +446,6 @@ class AcpBridgeServerAdvancedOverrides { List? gatewayProfiles, VaultConfig? vault, AiGatewayProfile? aiGateway, - List? acpBridgeServerProfiles, List? authorizedSkillDirectories, }) { return AcpBridgeServerAdvancedOverrides( @@ -458,9 +454,6 @@ class AcpBridgeServerAdvancedOverrides { : this.gatewayProfiles, vault: vault ?? this.vault, aiGateway: aiGateway ?? this.aiGateway, - acpBridgeServerProfiles: acpBridgeServerProfiles != null - ? normalizeExternalAcpEndpoints(profiles: acpBridgeServerProfiles) - : this.acpBridgeServerProfiles, authorizedSkillDirectories: authorizedSkillDirectories != null ? normalizeAuthorizedSkillDirectories( directories: authorizedSkillDirectories, @@ -476,9 +469,6 @@ class AcpBridgeServerAdvancedOverrides { .toList(growable: false), 'vault': vault.toJson(), 'aiGateway': aiGateway.toJson(), - 'acpBridgeServerProfiles': acpBridgeServerProfiles - .map((item) => item.toJson()) - .toList(growable: false), 'authorizedSkillDirectories': authorizedSkillDirectories .map((item) => item.toJson()) .toList(growable: false), @@ -502,16 +492,6 @@ class AcpBridgeServerAdvancedOverrides { aiGateway: AiGatewayProfile.fromJson( (json['aiGateway'] as Map?)?.cast() ?? const {}, ), - acpBridgeServerProfiles: normalizeExternalAcpEndpoints( - profiles: - ((json['acpBridgeServerProfiles'] as List?) ?? const []) - .whereType() - .map( - (item) => ExternalAcpEndpointProfile.fromJson( - item.cast(), - ), - ), - ), authorizedSkillDirectories: normalizeAuthorizedSkillDirectories( directories: ((json['authorizedSkillDirectories'] as List?) ?? const []) diff --git a/lib/runtime/runtime_models_settings_snapshot.dart b/lib/runtime/runtime_models_settings_snapshot.dart index aea010dc..59b996e1 100644 --- a/lib/runtime/runtime_models_settings_snapshot.dart +++ b/lib/runtime/runtime_models_settings_snapshot.dart @@ -24,11 +24,9 @@ class SettingsSnapshot { required this.remoteProjectRoot, required this.cliPath, required this.codeAgentRuntimeMode, - required this.codexCliPath, required this.defaultModel, required this.defaultProvider, required this.gatewayProfiles, - required this.providerSyncDefinitions, required this.authorizedSkillDirectories, required this.ollamaLocal, required this.ollamaCloud, @@ -59,11 +57,9 @@ class SettingsSnapshot { final String remoteProjectRoot; final String cliPath; final CodeAgentRuntimeMode codeAgentRuntimeMode; - final String codexCliPath; final String defaultModel; final String defaultProvider; final List gatewayProfiles; - final List providerSyncDefinitions; final List authorizedSkillDirectories; final OllamaLocalConfig ollamaLocal; final OllamaCloudConfig ollamaCloud; @@ -95,11 +91,9 @@ class SettingsSnapshot { remoteProjectRoot: '', cliPath: 'openclaw', codeAgentRuntimeMode: CodeAgentRuntimeMode.externalCli, - codexCliPath: '', defaultModel: '', defaultProvider: 'gateway', gatewayProfiles: normalizeGatewayProfiles(), - providerSyncDefinitions: normalizeExternalAcpEndpoints(), authorizedSkillDirectories: normalizeAuthorizedSkillDirectories(), ollamaLocal: OllamaLocalConfig.defaults(), ollamaCloud: OllamaCloudConfig.defaults(), @@ -132,11 +126,9 @@ class SettingsSnapshot { String? remoteProjectRoot, String? cliPath, CodeAgentRuntimeMode? codeAgentRuntimeMode, - String? codexCliPath, String? defaultModel, String? defaultProvider, List? gatewayProfiles, - List? providerSyncDefinitions, List? authorizedSkillDirectories, OllamaLocalConfig? ollamaLocal, OllamaCloudConfig? ollamaCloud, @@ -160,9 +152,6 @@ class SettingsSnapshot { final resolvedGatewayProfiles = gatewayProfiles != null ? normalizeGatewayProfiles(profiles: gatewayProfiles) : this.gatewayProfiles; - final resolvedProviderSyncDefinitions = providerSyncDefinitions != null - ? normalizeExternalAcpEndpoints(profiles: providerSyncDefinitions) - : this.providerSyncDefinitions; final resolvedAuthorizedSkillDirectories = authorizedSkillDirectories != null ? normalizeAuthorizedSkillDirectories( @@ -179,11 +168,9 @@ class SettingsSnapshot { remoteProjectRoot: remoteProjectRoot ?? this.remoteProjectRoot, cliPath: cliPath ?? this.cliPath, codeAgentRuntimeMode: codeAgentRuntimeMode ?? this.codeAgentRuntimeMode, - codexCliPath: codexCliPath ?? this.codexCliPath, defaultModel: defaultModel ?? this.defaultModel, defaultProvider: defaultProvider ?? this.defaultProvider, gatewayProfiles: resolvedGatewayProfiles, - providerSyncDefinitions: resolvedProviderSyncDefinitions, authorizedSkillDirectories: resolvedAuthorizedSkillDirectories, ollamaLocal: ollamaLocal ?? this.ollamaLocal, ollamaCloud: ollamaCloud ?? this.ollamaCloud, @@ -222,15 +209,11 @@ class SettingsSnapshot { 'remoteProjectRoot': remoteProjectRoot, 'cliPath': cliPath, 'codeAgentRuntimeMode': codeAgentRuntimeMode.name, - 'codexCliPath': codexCliPath, 'defaultModel': defaultModel, 'defaultProvider': defaultProvider, 'gatewayProfiles': gatewayProfiles .map((item) => item.toJson()) .toList(growable: false), - 'providerSyncDefinitions': providerSyncDefinitions - .map((item) => item.toJson()) - .toList(growable: false), 'authorizedSkillDirectories': authorizedSkillDirectories .map((item) => item.toJson()) .toList(growable: false), @@ -270,15 +253,6 @@ class SettingsSnapshot { GatewayConnectionProfile.fromJson(item.cast()), ), ); - final providerSyncDefinitions = normalizeExternalAcpEndpoints( - profiles: ((json['providerSyncDefinitions'] as List?) ?? const []) - .whereType() - .map( - (item) => ExternalAcpEndpointProfile.fromJson( - item.cast(), - ), - ), - ); final authorizedSkillDirectories = normalizeAuthorizedSkillDirectories( directories: ((json['authorizedSkillDirectories'] as List?) ?? const []) @@ -308,9 +282,6 @@ class SettingsSnapshot { codeAgentRuntimeMode: CodeAgentRuntimeModeCopy.fromJsonValue( json['codeAgentRuntimeMode'] as String?, ), - codexCliPath: - json['codexCliPath'] as String? ?? - SettingsSnapshot.defaults().codexCliPath, defaultModel: json['defaultModel'] as String? ?? SettingsSnapshot.defaults().defaultModel, @@ -318,7 +289,6 @@ class SettingsSnapshot { json['defaultProvider'] as String? ?? SettingsSnapshot.defaults().defaultProvider, gatewayProfiles: gatewayProfiles, - providerSyncDefinitions: providerSyncDefinitions, authorizedSkillDirectories: authorizedSkillDirectories, ollamaLocal: OllamaLocalConfig.fromJson( (json['ollamaLocal'] as Map?)?.cast() ?? const {}, @@ -419,91 +389,15 @@ class SettingsSnapshot { return copyWithGatewayProfileAt(index, profile); } - ExternalAcpEndpointProfile providerSyncDefinitionForProvider( - SingleAgentProvider provider, - ) { - return providerSyncDefinitionForProviderId(provider.providerId) ?? - ExternalAcpEndpointProfile.defaultsForProvider(provider); - } - - ExternalAcpEndpointProfile? providerSyncDefinitionForProviderId( - String providerId, - ) { - final normalized = normalizeSingleAgentProviderId(providerId); - if (normalized.isEmpty) { - return null; - } - for (final item in providerSyncDefinitions) { - if (item.providerKey == normalized) { - return item; - } - } - return null; - } - - SingleAgentProvider resolveSingleAgentProvider(SingleAgentProvider provider) { - if (provider.isUnspecified) { - return SingleAgentProvider.unspecified; - } - final profile = providerSyncDefinitionForProviderId(provider.providerId); - if (profile != null) { - return profile.toProvider(); - } - return provider; - } - - SingleAgentProvider singleAgentProviderForId(String providerId) { - final resolved = normalizeSingleAgentProviderId(providerId); - if (resolved.isEmpty || resolved == 'auto') { - return SingleAgentProvider.unspecified; - } - final normalizedSelection = SingleAgentProvider.fromJsonValue(resolved); - final profile = providerSyncDefinitionForProviderId( - normalizedSelection.providerId, - ); - if (profile != null) { - return profile.toProvider(); - } - return normalizedSelection; - } - SingleAgentProvider sanitizeSingleAgentProviderSelection( SingleAgentProvider provider, ) { - final resolved = resolveSingleAgentProvider(provider); - if (resolved.isUnspecified) { + if (provider.isUnspecified) { return SingleAgentProvider.unspecified; } - if (isBridgeOwnedSingleAgentProviderId(resolved.providerId)) { - return resolved; + if (isBridgeOwnedSingleAgentProviderId(provider.providerId)) { + return provider; } return SingleAgentProvider.unspecified; } - - SettingsSnapshot copyWithProviderSyncDefinitionForProvider( - SingleAgentProvider provider, - ExternalAcpEndpointProfile profile, - ) { - return copyWith( - providerSyncDefinitions: replaceExternalAcpEndpointForProvider( - providerSyncDefinitions, - provider, - profile, - ), - ); - } - - SettingsSnapshot captureAcpBridgeServerAdvancedOverrides() { - return copyWith( - acpBridgeServerModeConfig: acpBridgeServerModeConfig.copyWith( - advancedOverrides: AcpBridgeServerAdvancedOverrides( - gatewayProfiles: gatewayProfiles, - vault: vault, - aiGateway: aiGateway, - acpBridgeServerProfiles: providerSyncDefinitions, - authorizedSkillDirectories: authorizedSkillDirectories, - ), - ), - ); - } } diff --git a/test/app_controller_desktop_runtime_cleanup_test.dart b/test/app_controller_desktop_runtime_cleanup_test.dart index 74efb1df..0bed7363 100644 --- a/test/app_controller_desktop_runtime_cleanup_test.dart +++ b/test/app_controller_desktop_runtime_cleanup_test.dart @@ -10,7 +10,6 @@ import 'package:xworkmate/runtime/desktop_platform_service.dart'; import 'package:xworkmate/runtime/go_task_service_client.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; import 'package:xworkmate/runtime/secure_config_store.dart'; -import 'package:xworkmate/runtime/single_agent_capabilities.dart'; import 'package:xworkmate/runtime/skill_directory_access.dart'; void main() { @@ -49,7 +48,63 @@ void main() { reason: 'app-side runtime coordination should not own provider auth side-channels', ); + expect( + source.contains('configuredCodexCliPath'), + isFalse, + reason: + 'runtime coordination should not pass configured Codex CLI paths into runtime flows', + ); + expect( + source.contains('resolvedCodexCliPath'), + isFalse, + reason: + 'runtime coordination should not retain detected Codex CLI paths', + ); } + + final settingsSnapshot = File('lib/runtime/runtime_models_settings_snapshot.dart') + .readAsStringSync(); + expect( + settingsSnapshot.contains('providerSyncDefinitions'), + isFalse, + reason: + 'settings snapshots should not persist provider catalog mirror data', + ); + expect( + settingsSnapshot.contains('codexCliPath'), + isFalse, + reason: 'settings snapshots should not persist app-side Codex CLI paths', + ); + + final accountModels = File('lib/runtime/runtime_models_account.dart') + .readAsStringSync(); + expect( + accountModels.contains('acpBridgeServerProfiles'), + isFalse, + reason: + 'account advanced overrides should not mirror bridge provider catalogs', + ); + + final orchestrator = File('lib/runtime/code_agent_node_orchestrator.dart') + .readAsStringSync(); + expect( + orchestrator.contains('configuredCodexCliPath'), + isFalse, + reason: + 'node metadata should not expose configured Codex CLI paths anymore', + ); + expect( + orchestrator.contains('resolvedCodexCliPath'), + isFalse, + reason: + 'node metadata should not expose detected Codex CLI paths anymore', + ); + expect( + orchestrator.contains('binaryConfigured'), + isFalse, + reason: + 'node metadata should not derive binaryConfigured from local CLI detection', + ); }); test( @@ -123,20 +178,14 @@ void main() { controller.dispose(); await server.close(force: true); if (root.existsSync()) { - await root.delete(recursive: true); + try { + await root.delete(recursive: true); + } catch (_) {} } }); - final endpoint = 'http://${server.address.address}:${server.port}'; - final nextSettings = controller.settings.copyWith( - providerSyncDefinitions: [ - ExternalAcpEndpointProfile.defaultsForProvider( - SingleAgentProvider.codex, - ).copyWith(endpoint: endpoint), - ], - ); - controller.settingsController.snapshotInternal = nextSettings; - controller.lastObservedSettingsSnapshotInternal = nextSettings; + controller.settingsController.snapshotInternal = controller.settings; + controller.lastObservedSettingsSnapshotInternal = controller.settings; const sessionKey = 'draft:runtime-cleanup'; controller.initializeAssistantThreadContext( @@ -204,7 +253,9 @@ void main() { addTearDown(() async { controller.dispose(); if (root.existsSync()) { - await root.delete(recursive: true); + try { + await root.delete(recursive: true); + } catch (_) {} } }); @@ -243,14 +294,6 @@ void _seedBridgeProviders( List providers, ) { controller.bridgeAdvertisedProvidersInternal = providers; - controller.singleAgentCapabilitiesByProviderInternal = { - for (final provider in providers) - provider: SingleAgentCapabilities( - available: true, - supportedProviders: [provider], - endpoint: 'bridge', - ), - }; } class _FakeSkillDirectoryAccessService implements SkillDirectoryAccessService { diff --git a/test/app_controller_desktop_working_directory_dispatch_test.dart b/test/app_controller_desktop_working_directory_dispatch_test.dart index 0b774205..54d694a8 100644 --- a/test/app_controller_desktop_working_directory_dispatch_test.dart +++ b/test/app_controller_desktop_working_directory_dispatch_test.dart @@ -9,7 +9,6 @@ import 'package:xworkmate/app/app_controller_desktop_workspace_execution.dart'; import 'package:xworkmate/runtime/go_task_service_client.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; import 'package:xworkmate/runtime/secure_config_store.dart'; -import 'package:xworkmate/runtime/single_agent_capabilities.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -94,9 +93,9 @@ void main() { ); expect( client.resolveExternalAcpRoutingCallCount, - 0, + 2, reason: - 'single-agent turns should go straight to session.start/session.message without app-side routing preflight', + 'single-agent turns should preflight through bridge routing.resolve once per turn before dispatch', ); }, ); @@ -218,14 +217,6 @@ void _seedBridgeProviders( List providers, ) { controller.bridgeAdvertisedProvidersInternal = providers; - controller.singleAgentCapabilitiesByProviderInternal = { - for (final provider in providers) - provider: SingleAgentCapabilities( - available: true, - supportedProviders: [provider], - endpoint: 'bridge', - ), - }; } class _CapturingGoTaskServiceClient implements GoTaskServiceClient { diff --git a/test/assistant_execution_target_picker_widget_test.dart b/test/assistant_execution_target_picker_widget_test.dart index d37ca2db..27f0a783 100644 --- a/test/assistant_execution_target_picker_widget_test.dart +++ b/test/assistant_execution_target_picker_widget_test.dart @@ -11,7 +11,6 @@ import 'package:xworkmate/runtime/desktop_platform_service.dart'; import 'package:xworkmate/runtime/go_task_service_client.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; import 'package:xworkmate/runtime/secure_config_store.dart'; -import 'package:xworkmate/runtime/single_agent_capabilities.dart'; import 'package:xworkmate/runtime/skill_directory_access.dart'; import 'package:xworkmate/theme/app_theme.dart'; @@ -245,14 +244,6 @@ void _seedBridgeProviders( List providers, ) { controller.bridgeAdvertisedProvidersInternal = providers; - controller.singleAgentCapabilitiesByProviderInternal = { - for (final provider in providers) - provider: SingleAgentCapabilities( - available: true, - supportedProviders: [provider], - endpoint: 'bridge', - ), - }; } class _FakeSkillDirectoryAccessService implements SkillDirectoryAccessService { diff --git a/test/features/assistant/assistant_page_composer_golden_test.dart b/test/features/assistant/assistant_page_composer_golden_test.dart index 6d6c66c9..5744323b 100644 --- a/test/features/assistant/assistant_page_composer_golden_test.dart +++ b/test/features/assistant/assistant_page_composer_golden_test.dart @@ -10,7 +10,6 @@ import 'package:xworkmate/runtime/desktop_platform_service.dart'; import 'package:xworkmate/runtime/go_task_service_client.dart'; import 'package:xworkmate/runtime/runtime_models.dart'; import 'package:xworkmate/runtime/secure_config_store.dart'; -import 'package:xworkmate/runtime/single_agent_capabilities.dart'; import 'package:xworkmate/runtime/skill_directory_access.dart'; import 'package:xworkmate/theme/app_theme.dart'; @@ -118,14 +117,6 @@ void _seedBridgeProviders( List providers, ) { controller.bridgeAdvertisedProvidersInternal = providers; - controller.singleAgentCapabilitiesByProviderInternal = { - for (final provider in providers) - provider: SingleAgentCapabilities( - available: true, - supportedProviders: [provider], - endpoint: 'bridge', - ), - }; } class _GoldenSkillDirectoryAccessService diff --git a/test/runtime/settings_snapshot_provider_sync_definitions_test.dart b/test/runtime/settings_snapshot_provider_sync_definitions_test.dart index 3bb53c4e..3e981efa 100644 --- a/test/runtime/settings_snapshot_provider_sync_definitions_test.dart +++ b/test/runtime/settings_snapshot_provider_sync_definitions_test.dart @@ -3,38 +3,6 @@ import 'package:xworkmate/runtime/runtime_models.dart'; void main() { group('SettingsSnapshot schema v1', () { - test('defaults include provider sync presets', () { - final providerKeys = SettingsSnapshot.defaults().providerSyncDefinitions - .map((item) => item.providerKey) - .toList(growable: false); - - expect(providerKeys, ['codex', 'opencode', 'gemini']); - }); - - test('round trips providerSyncDefinitions and schemaVersion', () { - final snapshot = SettingsSnapshot.defaults().copyWith( - providerSyncDefinitions: [ - ExternalAcpEndpointProfile.defaultsForProvider( - SingleAgentProvider.codex, - ).copyWith(endpoint: 'https://codex.example.com'), - ExternalAcpEndpointProfile.defaultsForProvider( - SingleAgentProvider.opencode, - ), - ExternalAcpEndpointProfile.defaultsForProvider( - SingleAgentProvider.gemini, - ), - ], - ); - - final decoded = SettingsSnapshot.fromJson(snapshot.toJson()); - - expect(decoded.schemaVersion, settingsSnapshotSchemaVersion); - expect( - decoded.providerSyncDefinitions.first.endpoint, - 'https://codex.example.com', - ); - }); - test('missing schemaVersion is rejected', () { expect( () => SettingsSnapshot.fromJson({ @@ -45,7 +13,34 @@ void main() { ); }); - test('removed ui restore fields are not serialized', () { + test('legacy provider sync and CLI fields are ignored on read', () { + final decoded = SettingsSnapshot.fromJson({ + 'schemaVersion': settingsSnapshotSchemaVersion, + 'appLanguage': 'zh', + 'gatewayProfiles': >[], + 'providerSyncDefinitions': >[ + { + 'providerKey': 'codex', + 'label': 'Codex', + 'badge': 'C', + 'endpoint': 'https://codex.example.com', + 'authRef': 'secret://codex', + 'enabled': true, + }, + ], + 'codexCliPath': '/tmp/codex', + }); + + expect(decoded.schemaVersion, settingsSnapshotSchemaVersion); + expect( + decoded.sanitizeSingleAgentProviderSelection(SingleAgentProvider.codex), + SingleAgentProvider.codex, + ); + expect(decoded.toJson().containsKey('providerSyncDefinitions'), isFalse); + expect(decoded.toJson().containsKey('codexCliPath'), isFalse); + }); + + test('removed ui restore and local provider fields are not serialized', () { final json = SettingsSnapshot.defaults().toJson(); expect(json.containsKey('assistantLastSessionKey'), isFalse); @@ -54,12 +49,13 @@ void main() { expect(json.containsKey('assistantArchivedTaskKeys'), isFalse); expect(json.containsKey('savedGatewayTargets'), isFalse); expect(json.containsKey('externalAcpEndpoints'), isFalse); - expect(json.containsKey('providerSyncDefinitions'), isTrue); + expect(json.containsKey('providerSyncDefinitions'), isFalse); + expect(json.containsKey('codexCliPath'), isFalse); }); }); group('AcpBridgeServerModeConfig advanced overrides', () { - test('advanced override ACP profiles are normalized to full presets', () { + test('legacy ACP bridge server profiles are ignored and not reserialized', () { final config = AcpBridgeServerModeConfig.fromJson({ 'advancedOverrides': { 'acpBridgeServerProfiles': >[ @@ -67,19 +63,19 @@ void main() { 'providerKey': 'opencode', 'label': 'OpenCode', 'badge': 'O', - 'endpoint': '', - 'authRef': '', + 'endpoint': 'https://opencode.example.com', + 'authRef': 'secret://opencode', 'enabled': true, }, ], }, }); - final providerKeys = config.advancedOverrides.acpBridgeServerProfiles - .map((item) => item.providerKey) - .toList(growable: false); + final json = config.toJson(); + final advancedOverrides = (json['advancedOverrides'] as Map?)?.cast(); - expect(providerKeys, ['codex', 'opencode', 'gemini']); + expect(advancedOverrides, isNotNull); + expect(advancedOverrides!.containsKey('acpBridgeServerProfiles'), isFalse); }); }); }