diff --git a/lib/app/app_controller_desktop_core.dart b/lib/app/app_controller_desktop_core.dart index b1231133..d587c06a 100644 --- a/lib/app/app_controller_desktop_core.dart +++ b/lib/app/app_controller_desktop_core.dart @@ -307,9 +307,6 @@ class AppController extends ChangeNotifier { >{}; final Map aiGatewayStreamingTextBySessionInternal = {}; - final Map - syncedGoAgentProvidersInternal = - {}; final DesktopThreadArtifactService threadArtifactServiceInternal = DesktopThreadArtifactService(); List singleAgentSharedImportedSkillsInternal = diff --git a/lib/app/app_controller_desktop_external_acp_routing.dart b/lib/app/app_controller_desktop_external_acp_routing.dart index 643f477d..401db133 100644 --- a/lib/app/app_controller_desktop_external_acp_routing.dart +++ b/lib/app/app_controller_desktop_external_acp_routing.dart @@ -37,47 +37,6 @@ import 'app_controller_desktop_core.dart'; import 'app_controller_desktop_thread_sessions.dart'; extension AppControllerDesktopExternalAcpRouting on AppController { - Future> - buildExternalAcpSyncedProvidersInternal() async { - final providers = []; - for (final profile in settings.externalAcpEndpoints) { - final builtinProvider = profile.builtinProvider; - final effectiveProfile = builtinProvider == null - ? profile - : settings.externalAcpEndpointForProvider(builtinProvider); - final providerId = effectiveProfile.providerKey.trim(); - final endpoint = effectiveProfile.endpoint.trim(); - if (providerId.isEmpty || endpoint.isEmpty) { - continue; - } - final authorizationHeader = effectiveProfile.authRef.trim().isEmpty - ? '' - : await settingsControllerInternal.resolveSecretValueInternal( - refName: effectiveProfile.authRef.trim(), - ); - providers.add( - ExternalCodeAgentAcpSyncedProvider( - providerId: providerId, - label: effectiveProfile.label, - endpoint: endpoint, - authorizationHeader: authorizationHeader, - enabled: effectiveProfile.enabled, - ), - ); - } - return providers; - } - - Future syncExternalAcpProvidersInternal() async { - final providers = await buildExternalAcpSyncedProvidersInternal(); - syncedGoAgentProvidersInternal - ..clear() - ..addEntries( - providers.map((item) => MapEntry(item.providerId.trim(), item)), - ); - await goTaskServiceClientInternal.syncExternalProviders(providers); - } - ExternalCodeAgentAcpRoutingConfig buildExternalAcpRoutingForSessionInternal( String sessionKey, { String? explicitExecutionTarget, diff --git a/lib/app/app_controller_desktop_runtime_coordination_impl.dart b/lib/app/app_controller_desktop_runtime_coordination_impl.dart index ce2c50a9..85be703b 100644 --- a/lib/app/app_controller_desktop_runtime_coordination_impl.dart +++ b/lib/app/app_controller_desktop_runtime_coordination_impl.dart @@ -103,7 +103,6 @@ Future refreshSingleAgentCapabilitiesRuntimeInternal( AppController controller, { bool forceRefresh = false, }) async { - await controller.syncExternalAcpProvidersInternal(); final capabilities = await controller.goTaskServiceClientInternal .loadExternalAcpCapabilities( target: AssistantExecutionTarget.singleAgent, 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 44ccc4c7..2702ebd9 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 @@ -58,7 +58,6 @@ Future sendSingleAgentMessageDesktopGoTaskFlowInternal( sessionKey, ); final selection = controller.singleAgentProviderForSession(sessionKey); - await controller.syncExternalAcpProvidersInternal(); final capabilities = await controller.goTaskServiceClientInternal .loadExternalAcpCapabilities( target: AssistantExecutionTarget.singleAgent, diff --git a/lib/runtime/external_code_agent_acp_desktop_transport.dart b/lib/runtime/external_code_agent_acp_desktop_transport.dart index 43e2f84a..eb5b4af8 100644 --- a/lib/runtime/external_code_agent_acp_desktop_transport.dart +++ b/lib/runtime/external_code_agent_acp_desktop_transport.dart @@ -140,6 +140,9 @@ class ExternalCodeAgentAcpDesktopTransport Future dispose() => _bridge.dispose(); Future _syncProviders() async { + if (_syncedProviders.isEmpty) { + return; + } await _bridge.request( method: 'xworkmate.providers.sync', params: { diff --git a/test/runtime/external_code_agent_acp_desktop_transport_test.dart b/test/runtime/external_code_agent_acp_desktop_transport_test.dart new file mode 100644 index 00000000..14e41910 --- /dev/null +++ b/test/runtime/external_code_agent_acp_desktop_transport_test.dart @@ -0,0 +1,80 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:xworkmate/runtime/external_code_agent_acp_desktop_transport.dart'; +import 'package:xworkmate/runtime/go_acp_stdio_bridge.dart'; +import 'package:xworkmate/runtime/go_task_service_client.dart'; +import 'package:xworkmate/runtime/runtime_models.dart'; + +class _FakeGoAcpStdioBridge extends GoAcpStdioBridge { + _FakeGoAcpStdioBridge(); + + final List methods = []; + final StreamController> _notifications = + StreamController>.broadcast(); + + @override + Stream> get notifications => _notifications.stream; + + @override + Future> request({ + required String method, + required Map params, + Duration timeout = const Duration(seconds: 120), + }) async { + methods.add(method); + if (method == 'acp.capabilities') { + return { + 'result': { + 'singleAgent': true, + 'multiAgent': true, + 'providers': ['codex', 'opencode', 'gemini'], + }, + }; + } + return {'result': {}}; + } + + @override + Future dispose() async { + await _notifications.close(); + } +} + +void main() { + group('ExternalCodeAgentAcpDesktopTransport', () { + test('reads bridge capabilities without pushing an empty provider sync', () async { + final bridge = _FakeGoAcpStdioBridge(); + final transport = ExternalCodeAgentAcpDesktopTransport(bridge: bridge); + + final capabilities = await transport.loadExternalAcpCapabilities( + target: AssistantExecutionTarget.singleAgent, + ); + + expect(bridge.methods, ['acp.capabilities']); + expect( + capabilities.providers.map((item) => item.providerId).toList()..sort(), + ['codex', 'gemini', 'opencode'], + ); + }); + + test('only syncs when app has explicit provider overrides to send', () async { + final bridge = _FakeGoAcpStdioBridge(); + final transport = ExternalCodeAgentAcpDesktopTransport(bridge: bridge); + + await transport.syncExternalProviders( + const [ + ExternalCodeAgentAcpSyncedProvider( + providerId: 'codex', + label: 'Codex', + endpoint: 'https://acp-server.svc.plus/codex/acp/rpc', + authorizationHeader: '', + enabled: true, + ), + ], + ); + + expect(bridge.methods, ['xworkmate.providers.sync']); + }); + }); +}