diff --git a/lib/app/app_controller_desktop_runtime_coordination_impl.dart b/lib/app/app_controller_desktop_runtime_coordination_impl.dart index 7cc00f55..b4f51755 100644 --- a/lib/app/app_controller_desktop_runtime_coordination_impl.dart +++ b/lib/app/app_controller_desktop_runtime_coordination_impl.dart @@ -100,7 +100,14 @@ Future refreshSingleAgentCapabilitiesRuntimeInternal( ), ); final next = {}; - for (final provider in controller.configuredSingleAgentProviders) { + final candidateProviders = + normalizeSingleAgentProviderList([ + ...controller.configuredSingleAgentProviders, + ...capabilities.providers.map( + controller.settings.resolveSingleAgentProvider, + ), + ]); + for (final provider in candidateProviders) { if (!capabilities.providers.contains(provider)) { next[provider] = const SingleAgentCapabilities.unavailable(endpoint: ''); continue; diff --git a/lib/app/app_controller_desktop_thread_sessions.dart b/lib/app/app_controller_desktop_thread_sessions.dart index 0f3ca2ff..8d88f33d 100644 --- a/lib/app/app_controller_desktop_thread_sessions.dart +++ b/lib/app/app_controller_desktop_thread_sessions.dart @@ -319,7 +319,9 @@ extension AppControllerDesktopThreadSessions on AppController { singleAgentShouldShowModelControlForSession(currentSessionKey); List get singleAgentProviderOptions => - configuredSingleAgentProviders; + availableSingleAgentProviders.isNotEmpty + ? availableSingleAgentProviders + : configuredSingleAgentProviders; String singleAgentProviderLabelForSession(String sessionKey) { return singleAgentProviderForSession(sessionKey).label; diff --git a/test/runtime/acp_bridge_provider_hub_suite.dart b/test/runtime/acp_bridge_provider_hub_suite.dart index 6526ae8b..0e6d6293 100644 --- a/test/runtime/acp_bridge_provider_hub_suite.dart +++ b/test/runtime/acp_bridge_provider_hub_suite.dart @@ -119,6 +119,58 @@ void main() { ); }); + test( + 'self-hosted bridge capabilities add dynamic builtin providers to the single-agent picker', + () async { + SharedPreferences.setMockInitialValues({}); + final store = createIsolatedTestStore(enableSecureStorage: false); + final controller = AppController( + store: store, + goTaskServiceClient: FakeGoTaskServiceClientInternal( + capabilities: ExternalCodeAgentAcpCapabilities( + singleAgent: true, + multiAgent: false, + providers: { + SingleAgentProvider.codex, + SingleAgentProvider.opencode, + }, + raw: {}, + ), + ), + ); + addTearDown(controller.dispose); + await _waitFor(() => !controller.initializing); + + await controller.saveSettings( + controller.settings.copyWith( + acpBridgeServerModeConfig: controller + .settings + .acpBridgeServerModeConfig + .copyWith( + mode: AcpBridgeServerMode.selfHosted, + selfHosted: controller + .settings + .acpBridgeServerModeConfig + .selfHosted + .copyWith(serverUrl: 'https://xworkmate-bridge.svc.plus'), + ), + ), + refreshAfterSave: false, + ); + + await controller.refreshSingleAgentCapabilitiesInternal( + forceRefresh: true, + ); + + expect( + controller.singleAgentProviderOptions + .map((item) => item.providerId) + .toList(growable: false), + const ['codex', 'opencode'], + ); + }, + ); + test( 'local sync-only custom provider does not appear unless bridge advertises it', () async {