diff --git a/lib/app/app.dart b/lib/app/app.dart index 169be312..153c5ea2 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -11,9 +11,9 @@ import 'app_shell.dart'; import 'ui_feature_manifest.dart'; class XWorkmateApp extends StatefulWidget { - const XWorkmateApp({super.key, this.featureManifest}); + const XWorkmateApp({super.key, required this.featureManifest}); - final UiFeatureManifest? featureManifest; + final UiFeatureManifest featureManifest; @override State createState() => _XWorkmateAppState(); @@ -31,9 +31,7 @@ class _XWorkmateAppState extends State { void initState() { super.initState(); _themeSurface = resolveAppThemeSurface(); - _controller = AppController( - uiFeatureManifest: widget.featureManifest ?? UiFeatureManifest.fallback(), - ); + _controller = AppController(uiFeatureManifest: widget.featureManifest); if (_supportsDesktopLifecycleChannel) { _appLifecycleChannel.setMethodCallHandler(_handleAppLifecycleCall); } diff --git a/lib/app/app_controller_desktop_core.dart b/lib/app/app_controller_desktop_core.dart index 57afdfac..e4e9e96c 100644 --- a/lib/app/app_controller_desktop_core.dart +++ b/lib/app/app_controller_desktop_core.dart @@ -119,6 +119,7 @@ class AppController extends ChangeNotifier { RuntimeCoordinator? runtimeCoordinator, DesktopPlatformService? desktopPlatformService, UiFeatureManifest? uiFeatureManifest, + List? initialBridgeProviderCatalog, SkillDirectoryAccessService? skillDirectoryAccessService, AccountRuntimeClient Function(String baseUrl)? accountClientFactory, Map? environmentOverride, @@ -129,7 +130,7 @@ class AppController extends ChangeNotifier { }) { storeInternal = store ?? SecureConfigStore(); uiFeatureManifestInternal = - uiFeatureManifest ?? UiFeatureManifest.fallback(); + uiFeatureManifest ?? loadRepoUiFeatureManifestSyncInternal(); hostUiFeaturePlatformInternal = Platform.isIOS || Platform.isAndroid ? UiFeaturePlatform.mobile : UiFeaturePlatform.desktop; @@ -230,6 +231,9 @@ class AppController extends ChangeNotifier { endpointResolver: resolveGatewayAcpEndpointInternal, ), ); + bridgeProviderCatalogInternal = normalizeBridgeOwnedSingleAgentProviderList( + initialBridgeProviderCatalog ?? const [], + ); attachChildListenersInternal(); unawaited(initializeInternal()); @@ -436,7 +440,6 @@ class AppController extends ChangeNotifier { bool isCodexBridgeEnabledInternal = false; bool isCodexBridgeBusyInternal = false; String? codexBridgeErrorInternal; - String? codexRuntimeWarningInternal; CodexCooperationState codexCooperationStateInternal = CodexCooperationState.notStarted; SettingsController get settingsController => settingsControllerInternal; @@ -521,7 +524,6 @@ class AppController extends ChangeNotifier { settingsControllerInternal.hasEffectiveAiGatewayApiKey; bool get isCodexBridgeBusy => isCodexBridgeBusyInternal; String? get codexBridgeError => codexBridgeErrorInternal; - String? get codexRuntimeWarning => codexRuntimeWarningInternal; CodeAgentRuntimeMode get configuredCodeAgentRuntimeMode => settings.codeAgentRuntimeMode; CodeAgentRuntimeMode get effectiveCodeAgentRuntimeMode => @@ -568,15 +570,10 @@ class AppController extends ChangeNotifier { List get bridgeProviderCatalog => normalizeSingleAgentProviderList(bridgeProviderCatalogInternal); - List get assistantProviderCatalog { - final catalog = normalizeBridgeOwnedSingleAgentProviderList( - bridgeProviderCatalogInternal, - ); - if (catalog.isNotEmpty) { - return catalog; - } - return kPresetExternalAcpProviders; - } + List get assistantProviderCatalog => + normalizeBridgeOwnedSingleAgentProviderList( + bridgeProviderCatalogInternal, + ); SingleAgentProvider? bridgeProviderForId(String providerId) { final normalizedProviderId = normalizeSingleAgentProviderId(providerId); @@ -623,6 +620,16 @@ class AppController extends ChangeNotifier { return resolveAssistantProvider(thread?.executionBinding.providerId); } + UiFeatureManifest loadRepoUiFeatureManifestSyncInternal() { + final file = File(UiFeatureManifest.assetPath); + if (!file.existsSync()) { + throw StateError( + 'UiFeatureManifest is required and "${UiFeatureManifest.assetPath}" is missing.', + ); + } + return UiFeatureManifest.fromYamlString(file.readAsStringSync()); + } + List visibleAssistantExecutionTargets( Iterable supportedTargets, ) => compactAssistantExecutionTargets(supportedTargets); diff --git a/lib/app/app_controller_desktop_runtime_coordination_impl.dart b/lib/app/app_controller_desktop_runtime_coordination_impl.dart index d9351f4d..acf9f692 100644 --- a/lib/app/app_controller_desktop_runtime_coordination_impl.dart +++ b/lib/app/app_controller_desktop_runtime_coordination_impl.dart @@ -50,13 +50,20 @@ Future refreshAcpCapabilitiesRuntimeInternal( bool forceRefresh = false, bool persistMountTargets = false, }) async { + GatewayAcpCapabilities? capabilities; try { - await controller.gatewayAcpClientInternal.loadCapabilities( + capabilities = await controller.gatewayAcpClientInternal.loadCapabilities( forceRefresh: forceRefresh, ); } catch (_) { // Keep mount refresh resilient when ACP is temporarily unavailable. } + if (capabilities != null) { + controller.bridgeProviderCatalogInternal = + normalizeBridgeOwnedSingleAgentProviderList( + capabilities.providerCatalog, + ); + } if (persistMountTargets && !controller.disposedInternal) { final currentConfig = controller.settings.multiAgent; final nextConfig = await controller.multiAgentMountManagerInternal @@ -79,7 +86,21 @@ Future refreshAcpCapabilitiesRuntimeInternal( Future refreshSingleAgentCapabilitiesRuntimeInternal( AppController controller, { bool forceRefresh = false, -}) async {} +}) async { + try { + final capabilities = await controller.gatewayAcpClientInternal + .loadCapabilities(forceRefresh: forceRefresh); + controller.bridgeProviderCatalogInternal = + normalizeBridgeOwnedSingleAgentProviderList( + capabilities.providerCatalog, + ); + } catch (_) { + controller.bridgeProviderCatalogInternal = const []; + } + if (!controller.disposedInternal) { + controller.notifyListeners(); + } +} List mergeAcpCapabilitiesIntoMountTargetsRuntimeInternal( diff --git a/lib/app/app_controller_desktop_runtime_helpers.dart b/lib/app/app_controller_desktop_runtime_helpers.dart index dca386a6..f9b2ce81 100644 --- a/lib/app/app_controller_desktop_runtime_helpers.dart +++ b/lib/app/app_controller_desktop_runtime_helpers.dart @@ -388,23 +388,7 @@ extension AppControllerDesktopRuntimeHelpers on AppController { SettingsSnapshot sanitizeCodeAgentSettingsInternal( SettingsSnapshot snapshot, - ) { - final normalizedRuntimeMode = - snapshot.codeAgentRuntimeMode == CodeAgentRuntimeMode.builtIn - ? CodeAgentRuntimeMode.externalCli - : snapshot.codeAgentRuntimeMode; - codexRuntimeWarningInternal = - snapshot.codeAgentRuntimeMode == CodeAgentRuntimeMode.builtIn - ? appText( - '内置 Codex 运行时当前仅保留为未来扩展位;已自动切换为 External Codex CLI。', - 'Built-in Codex runtime is reserved for a future release; XWorkmate switched back to External Codex CLI automatically.', - ) - : null; - if (normalizedRuntimeMode == snapshot.codeAgentRuntimeMode) { - return snapshot; - } - return snapshot.copyWith(codeAgentRuntimeMode: normalizedRuntimeMode); - } + ) => snapshot; Future refreshAcpCapabilitiesInternal({ bool forceRefresh = false, diff --git a/lib/app/ui_feature_manifest.dart b/lib/app/ui_feature_manifest.dart index af81892a..531304bf 100644 --- a/lib/app/ui_feature_manifest.dart +++ b/lib/app/ui_feature_manifest.dart @@ -1,2 +1 @@ export 'ui_feature_manifest_core.dart'; -export 'ui_feature_manifest_fallback.dart'; diff --git a/lib/app/ui_feature_manifest_core.dart b/lib/app/ui_feature_manifest_core.dart index 3b1f95dc..f7cd5ba6 100644 --- a/lib/app/ui_feature_manifest_core.dart +++ b/lib/app/ui_feature_manifest_core.dart @@ -6,7 +6,6 @@ import 'package:flutter/services.dart'; import 'package:yaml/yaml.dart'; import '../models/app_models.dart'; import '../runtime/runtime_models.dart'; -import 'ui_feature_manifest_fallback.dart'; enum UiFeaturePlatform { mobile, desktop, web } @@ -127,7 +126,6 @@ class UiFeatureManifest { static const String assetPath = 'config/feature_flags.yaml'; - static const String fallbackYaml = fallbackUiFeatureManifestYamlInternal; final Map> releasePolicy; final Map>> flagsByPlatformInternal; @@ -152,10 +150,6 @@ class UiFeatureManifest { ); } - factory UiFeatureManifest.fallback() { - return UiFeatureManifest.fromYamlString(fallbackYaml); - } - UiFeatureAccess forPlatform( UiFeaturePlatform platform, { UiFeatureBuildMode? buildMode, @@ -538,8 +532,10 @@ class UiFeatureManifestLoader { try { final raw = await bundle.loadString(assetPath); return UiFeatureManifest.fromYamlString(raw); - } catch (_) { - return UiFeatureManifest.fallback(); + } catch (error) { + throw StateError( + 'Failed to load required UI feature manifest "$assetPath": $error', + ); } } } diff --git a/lib/app/ui_feature_manifest_fallback.dart b/lib/app/ui_feature_manifest_fallback.dart deleted file mode 100644 index 04590f23..00000000 --- a/lib/app/ui_feature_manifest_fallback.dart +++ /dev/null @@ -1,585 +0,0 @@ -// ignore_for_file: unused_import, unnecessary_import - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:yaml/yaml.dart'; -import '../models/app_models.dart'; -import '../runtime/runtime_models.dart'; -import 'ui_feature_manifest_core.dart'; - -const String fallbackUiFeatureManifestYamlInternal = ''' -release_policy: - debug: [stable, beta, experimental] - profile: [stable, beta] - release: [stable] - -mobile: - navigation: - assistant: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile assistant destination - ui_surface: mobile_shell - tasks: - enabled: false - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile tasks destination - ui_surface: mobile_shell - workspace: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile workspace hub destination - ui_surface: mobile_shell - secrets: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile secrets destination - ui_surface: mobile_shell - settings: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile settings destination - ui_surface: mobile_shell - workspace: - skills: - enabled: false - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile workspace skills launcher - ui_surface: mobile_workspace_hub - nodes: - enabled: false - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile workspace nodes launcher - ui_surface: mobile_workspace_hub - agents: - enabled: false - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile workspace agents launcher - ui_surface: mobile_workspace_hub - mcp_server: - enabled: false - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile workspace MCP launcher - ui_surface: mobile_workspace_hub - claw_hub: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile workspace ClawHub launcher - ui_surface: mobile_workspace_hub - connectors: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile workspace connectors launcher - ui_surface: mobile_workspace_hub - ai_gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile workspace LLM API launcher - ui_surface: mobile_workspace_hub - account: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile workspace account launcher - ui_surface: mobile_workspace_hub - assistant: - direct_ai: - enabled: false - release_tier: experimental - build_modes: [] - description: Mobile does not expose direct AI assistant mode - ui_surface: assistant_page - local_gateway: - enabled: false - release_tier: experimental - build_modes: [] - description: Mobile does not expose a separate gateway assistant mode - ui_surface: assistant_page - relay_gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile relay gateway assistant mode - ui_surface: assistant_page - file_attachments: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile file attachment action in assistant composer - ui_surface: assistant_page - multi_agent: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile gateway toggle in assistant composer - ui_surface: assistant_page - local_runtime: - enabled: false - release_tier: experimental - build_modes: [] - description: Mobile does not expose desktop runtime controls - ui_surface: assistant_page - settings: - general: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile settings general tab - ui_surface: settings_page - workspace: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile settings workspace tab - ui_surface: settings_page - gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile settings gateway tab - ui_surface: settings_page - account_access: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile account access section - ui_surface: settings_page - vault_server: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile Vault server integration section - ui_surface: settings_page - gateway_self_hosted_base: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile self-hosted base connection controls - ui_surface: settings_page - gateway_advanced_custom_mode: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile advanced custom override mode - ui_surface: settings_page - gateway_setup_code: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile gateway setup code editor - ui_surface: settings_page - agents: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile settings gateway tab - ui_surface: settings_page - appearance: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile settings appearance tab - ui_surface: settings_page - diagnostics: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile settings diagnostics tab - ui_surface: settings_page - experimental: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile settings experimental tab - ui_surface: settings_page - about: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Mobile settings about tab - ui_surface: settings_page - experimental_canvas: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile experimental canvas host toggle - ui_surface: settings_page - experimental_bridge: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile experimental bridge toggle - ui_surface: settings_page - experimental_debug: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Mobile experimental debug runtime toggle - ui_surface: settings_page - -desktop: - navigation: - assistant: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop assistant destination - ui_surface: sidebar_navigation - tasks: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop tasks destination - ui_surface: sidebar_navigation - skills: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop skills destination - ui_surface: sidebar_navigation - nodes: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop nodes destination - ui_surface: sidebar_navigation - agents: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop agents destination - ui_surface: sidebar_navigation - mcp_server: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop MCP Hub destination - ui_surface: sidebar_navigation - claw_hub: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop ClawHub destination - ui_surface: sidebar_navigation - secrets: - enabled: false - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop secrets destination - ui_surface: sidebar_navigation - ai_gateway: - enabled: false - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop LLM API destination - ui_surface: sidebar_navigation - settings: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop settings destination - ui_surface: sidebar_navigation - account: - enabled: false - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop account destination - ui_surface: sidebar_navigation - workspace: - claw_hub: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop workspace ClawHub tab - ui_surface: modules_page - connectors: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop workspace connectors tab - ui_surface: modules_page - assistant: - direct_ai: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop direct AI assistant mode - ui_surface: assistant_page - local_gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop gateway assistant mode - ui_surface: assistant_page - relay_gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop relay gateway assistant mode - ui_surface: assistant_page - file_attachments: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop file attachment action in assistant composer - ui_surface: assistant_page - multi_agent: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop gateway toggle in assistant composer - ui_surface: assistant_page - local_runtime: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop local runtime and gateway orchestration entry - ui_surface: assistant_page - settings: - general: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop settings general tab - ui_surface: settings_page - workspace: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop settings workspace tab - ui_surface: settings_page - gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop settings gateway tab - ui_surface: settings_page - account_access: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop account access section - ui_surface: settings_page - vault_server: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop Vault server integration section - ui_surface: settings_page - gateway_self_hosted_base: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop self-hosted base connection controls - ui_surface: settings_page - gateway_advanced_custom_mode: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop advanced custom override mode - ui_surface: settings_page - gateway_setup_code: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop gateway setup code editor - ui_surface: settings_page - agents: - enabled: false - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop settings gateway tab - ui_surface: settings_page - appearance: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop settings appearance tab - ui_surface: settings_page - diagnostics: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop settings diagnostics tab - ui_surface: settings_page - experimental: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop settings experimental tab - ui_surface: settings_page - about: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Desktop settings about tab - ui_surface: settings_page - experimental_canvas: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop experimental canvas host toggle - ui_surface: settings_page - experimental_bridge: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop experimental bridge toggle - ui_surface: settings_page - experimental_debug: - enabled: true - release_tier: experimental - build_modes: [debug, profile, release] - description: Desktop experimental debug runtime toggle - ui_surface: settings_page - -web: - navigation: - assistant: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web assistant destination - ui_surface: web_shell - tasks: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web tasks destination - ui_surface: web_shell - skills: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web skills destination - ui_surface: web_shell - nodes: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web nodes destination - ui_surface: web_shell - secrets: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web secrets destination - ui_surface: web_shell - ai_gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web LLM API destination - ui_surface: web_shell - settings: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web settings destination - ui_surface: web_shell - assistant: - direct_ai: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web direct AI assistant mode - ui_surface: web_assistant_page - relay_gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web relay gateway assistant mode - ui_surface: web_assistant_page - file_attachments: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web file attachment action in assistant composer - ui_surface: web_assistant_page - multi_agent: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web gateway toggle in assistant composer - ui_surface: web_assistant_page - local_gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web gateway assistant mode - ui_surface: web_assistant_page - local_runtime: - enabled: false - release_tier: experimental - build_modes: [] - description: Web does not expose desktop runtime controls - ui_surface: web_assistant_page - settings: - general: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web settings general tab - ui_surface: web_settings_page - gateway: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web settings gateway tab - ui_surface: web_settings_page - account_access: - enabled: false - release_tier: experimental - build_modes: [] - description: Web does not expose account access section - ui_surface: web_settings_page - vault_server: - enabled: false - release_tier: experimental - build_modes: [] - description: Web does not expose vault server integration - ui_surface: web_settings_page - gateway_self_hosted_base: - enabled: false - release_tier: experimental - build_modes: [] - description: Web does not expose self-hosted base connection controls - ui_surface: web_settings_page - gateway_advanced_custom_mode: - enabled: false - release_tier: experimental - build_modes: [] - description: Web does not expose advanced custom override mode - ui_surface: web_settings_page - gateway_setup_code: - enabled: false - release_tier: experimental - build_modes: [] - description: Web does not expose gateway setup code editor - ui_surface: web_settings_page - appearance: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web settings appearance tab - ui_surface: web_settings_page - about: - enabled: true - release_tier: stable - build_modes: [debug, profile, release] - description: Web settings about tab - ui_surface: web_settings_page -'''; diff --git a/lib/runtime/code_agent_node_orchestrator.dart b/lib/runtime/code_agent_node_orchestrator.dart index ae67e881..c61d0c90 100644 --- a/lib/runtime/code_agent_node_orchestrator.dart +++ b/lib/runtime/code_agent_node_orchestrator.dart @@ -94,10 +94,7 @@ class CodeAgentNodeOrchestrator { 'state': state.bridgeState, 'gatewayConnected': state.gatewayConnected, 'runtimeMode': state.runtimeMode.name, - 'localTransport': switch (state.runtimeMode) { - CodeAgentRuntimeMode.externalCli => 'stdio-jsonrpc', - CodeAgentRuntimeMode.builtIn => 'ffi-runtime', - }, + 'localTransport': 'stdio-jsonrpc', }, if (provider != null) 'provider': { diff --git a/lib/runtime/codex_ffi_bindings.dart b/lib/runtime/codex_ffi_bindings.dart deleted file mode 100644 index 56e66fe2..00000000 --- a/lib/runtime/codex_ffi_bindings.dart +++ /dev/null @@ -1,339 +0,0 @@ -// FFI bindings for Codex CLI integration. -// -// These bindings provide direct access to the native Rust library. - -import 'dart:ffi'; -import 'dart:io'; - -import 'package:ffi/ffi.dart'; - -// ============================================================================ -// FFI Structures -// ============================================================================ - -/// FFI-compatible result type. -final class CodexResultFFI extends Struct { - @Bool() - external bool success; - - @Int32() - external int errorCode; - - external Pointer errorMessage; -} - -/// FFI-compatible message type. -final class CodexMessageFFI extends Struct { - external Pointer messageType; - external Pointer content; - external Pointer threadId; - external Pointer turnId; -} - -/// FFI-compatible event type. -final class CodexEventFFI extends Struct { - external Pointer eventType; - external Pointer threadId; - external Pointer turnId; - external Pointer data; - @Int64() - external int timestamp; -} - -/// FFI-compatible configuration. -final class CodexConfigFFI extends Struct { - external Pointer codexPath; - external Pointer workingDirectory; - @Int32() - external int sandboxMode; - @Int32() - external int approvalPolicy; - external Pointer model; - external Pointer apiKey; - external Pointer gatewayUrl; - @Bool() - external bool debug; -} - -/// Opaque thread handle. -final class ThreadHandleFFI extends Struct { - @Uint64() - external int id; -} - -// ============================================================================ -// Native Functions -// ============================================================================ - -typedef _CodexInitNative = Int32 Function(); -typedef _CodexInitDart = int Function(); - -typedef _CodexRuntimeCreateNative = - Pointer Function(Pointer config); -typedef _CodexRuntimeCreateDart = - Pointer Function(Pointer config); - -typedef _CodexRuntimeDestroyNative = - Void Function(Pointer runtime); -typedef _CodexRuntimeDestroyDart = void Function(Pointer runtime); - -typedef _CodexStartThreadNative = - ThreadHandleFFI Function(Pointer runtime, Pointer cwd); -typedef _CodexStartThreadDart = - ThreadHandleFFI Function(Pointer runtime, Pointer cwd); - -typedef _CodexSendMessageNative = - Int32 Function( - Pointer runtime, - ThreadHandleFFI thread, - Pointer message, - ); -typedef _CodexSendMessageDart = - int Function( - Pointer runtime, - ThreadHandleFFI thread, - Pointer message, - ); - -typedef _CodexPollEventsNative = - UintPtr Function( - Pointer runtime, - Pointer events, - UintPtr maxEvents, - ); -typedef _CodexPollEventsDart = - int Function( - Pointer runtime, - Pointer events, - int maxEvents, - ); - -typedef _CodexShutdownNative = Int32 Function(Pointer runtime); -typedef _CodexShutdownDart = int Function(Pointer runtime); - -typedef _CodexLastErrorNative = - Pointer Function(Pointer runtime); -typedef _CodexLastErrorDart = - Pointer Function(Pointer runtime); - -// Opaque runtime type -final class CodexRuntime extends Opaque {} - -// ============================================================================ -// Dart Wrapper Class -// ============================================================================ - -/// Dart wrapper for Codex FFI. -class CodexFFIBindings { - final DynamicLibrary _lib; - late final _CodexInitDart _init; - late final _CodexRuntimeCreateDart _runtimeCreate; - late final _CodexRuntimeDestroyDart _runtimeDestroy; - late final _CodexStartThreadDart _startThread; - late final _CodexSendMessageDart _sendMessage; - late final _CodexPollEventsDart _pollEvents; - late final _CodexShutdownDart _shutdown; - late final _CodexLastErrorDart _lastError; - - Pointer? _runtime; - - CodexFFIBindings() : _lib = _loadLibrary() { - _init = _lib.lookupFunction<_CodexInitNative, _CodexInitDart>('codex_init'); - _runtimeCreate = _lib - .lookupFunction<_CodexRuntimeCreateNative, _CodexRuntimeCreateDart>( - 'codex_runtime_create', - ); - _runtimeDestroy = _lib - .lookupFunction<_CodexRuntimeDestroyNative, _CodexRuntimeDestroyDart>( - 'codex_runtime_destroy', - ); - _startThread = _lib - .lookupFunction<_CodexStartThreadNative, _CodexStartThreadDart>( - 'codex_start_thread', - ); - _sendMessage = _lib - .lookupFunction<_CodexSendMessageNative, _CodexSendMessageDart>( - 'codex_send_message', - ); - _pollEvents = _lib - .lookupFunction<_CodexPollEventsNative, _CodexPollEventsDart>( - 'codex_poll_events', - ); - _shutdown = _lib.lookupFunction<_CodexShutdownNative, _CodexShutdownDart>( - 'codex_shutdown', - ); - _lastError = _lib - .lookupFunction<_CodexLastErrorNative, _CodexLastErrorDart>( - 'codex_last_error', - ); - } - - static DynamicLibrary _loadLibrary() { - if (Platform.isMacOS) { - return DynamicLibrary.open('libcodex_ffi.dylib'); - } else if (Platform.isLinux) { - return DynamicLibrary.open('libcodex_ffi.so'); - } else if (Platform.isWindows) { - return DynamicLibrary.open('codex_ffi.dll'); - } - throw UnsupportedError('Unsupported platform'); - } - - /// Initialize the library. - void initialize() { - final result = _init(); - if (result != 0) { - throw StateError('Failed to initialize Codex FFI'); - } - } - - /// Create a runtime with configuration. - void createRuntime(CodexConfig config) { - if (_runtime != null) { - throw StateError('Runtime already created'); - } - - final configPtr = _createConfigFFI(config); - try { - _runtime = _runtimeCreate(configPtr); - if (_runtime == nullptr) { - throw StateError('Failed to create runtime'); - } - } finally { - _freeConfigFFI(configPtr); - } - } - - /// Destroy the runtime. - void destroyRuntime() { - if (_runtime != null) { - _runtimeDestroy(_runtime!); - _runtime = nullptr; - } - } - - /// Start a new thread. - int startThread(String cwd) { - _ensureRuntime(); - final cwdPtr = cwd.toNativeUtf8(); - try { - final handle = _startThread(_runtime!, cwdPtr); - return handle.id; - } finally { - calloc.free(cwdPtr); - } - } - - /// Send a message to the thread. - int sendMessage(int threadId, String message) { - _ensureRuntime(); - final messagePtr = message.toNativeUtf8(); - final handlePtr = calloc(); - try { - handlePtr.ref.id = threadId; - return _sendMessage(_runtime!, handlePtr.ref, messagePtr); - } finally { - calloc.free(messagePtr); - calloc.free(handlePtr); - } - } - - /// Poll for events. - List> pollEvents(int maxEvents) { - _ensureRuntime(); - final eventsPtr = calloc(maxEvents); - try { - final count = _pollEvents(_runtime!, eventsPtr, maxEvents); - final events = >[]; - for (var i = 0; i < count; i++) { - final event = eventsPtr[i]; - events.add({ - 'eventType': event.eventType.toDartString(), - 'threadId': event.threadId.toDartString(), - 'turnId': event.turnId.toDartString(), - 'data': event.data.toDartString(), - 'timestamp': event.timestamp, - }); - } - return events; - } finally { - calloc.free(eventsPtr); - } - } - - /// Shutdown the runtime. - void shutdown() { - _ensureRuntime(); - _shutdown(_runtime!); - } - - /// Get last error message. - String? lastError() { - if (_runtime == null) return null; - final ptr = _lastError(_runtime!); - if (ptr == nullptr) return null; - return ptr.toDartString(); - } - - void _ensureRuntime() { - if (_runtime == null) { - throw StateError('Runtime not initialized'); - } - } - - Pointer _createConfigFFI(CodexConfig config) { - final ptr = calloc(); - ptr.ref.codexPath = config.codexPath?.toNativeUtf8() ?? nullptr; - ptr.ref.workingDirectory = - config.workingDirectory?.toNativeUtf8() ?? nullptr; - ptr.ref.sandboxMode = config.sandboxMode; - ptr.ref.approvalPolicy = config.approvalPolicy; - ptr.ref.model = config.model?.toNativeUtf8() ?? nullptr; - ptr.ref.apiKey = config.apiKey?.toNativeUtf8() ?? nullptr; - ptr.ref.gatewayUrl = config.gatewayUrl?.toNativeUtf8() ?? nullptr; - ptr.ref.debug = config.debug; - return ptr; - } - - void _freeConfigFFI(Pointer ptr) { - if (ptr.ref.codexPath != nullptr) { - calloc.free(ptr.ref.codexPath); - } - if (ptr.ref.workingDirectory != nullptr) { - calloc.free(ptr.ref.workingDirectory); - } - if (ptr.ref.model != nullptr) { - calloc.free(ptr.ref.model); - } - if (ptr.ref.apiKey != nullptr) { - calloc.free(ptr.ref.apiKey); - } - if (ptr.ref.gatewayUrl != nullptr) { - calloc.free(ptr.ref.gatewayUrl); - } - calloc.free(ptr); - } -} - -/// Configuration for Codex FFI. -class CodexConfig { - final String? codexPath; - final String? workingDirectory; - final int sandboxMode; - final int approvalPolicy; - final String? model; - final String? apiKey; - final String? gatewayUrl; - final bool debug; - - const CodexConfig({ - this.codexPath, - this.workingDirectory, - this.sandboxMode = 1, // workspace-write - this.approvalPolicy = 0, // suggest - this.model, - this.apiKey, - this.gatewayUrl, - this.debug = false, - }); -} diff --git a/lib/runtime/desktop_thread_artifact_sync.dart b/lib/runtime/desktop_thread_artifact_sync.dart deleted file mode 100644 index 8c2f60fa..00000000 --- a/lib/runtime/desktop_thread_artifact_sync.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'go_task_service_client.dart'; - -class DesktopThreadArtifactSyncResult { - const DesktopThreadArtifactSyncResult({ - required this.wroteArtifact, - required this.writtenFiles, - }); - - final bool wroteArtifact; - final List writtenFiles; -} - -Future syncInlineArtifactsToLocalWorkspace({ - required Directory root, - required List artifacts, -}) async { - await root.create(recursive: true); - final writtenFiles = []; - for (final artifact in artifacts) { - if (!artifact.hasInlineContent) { - continue; - } - final relativePath = sanitizeArtifactRelativePath(artifact.relativePath); - if (relativePath.isEmpty) { - continue; - } - final target = await nextArtifactTargetFile(root, relativePath); - await target.parent.create(recursive: true); - await target.writeAsBytes(decodeArtifactContent(artifact), flush: true); - writtenFiles.add(target.path); - } - return DesktopThreadArtifactSyncResult( - wroteArtifact: writtenFiles.isNotEmpty, - writtenFiles: List.unmodifiable(writtenFiles), - ); -} - -String sanitizeArtifactRelativePath(String raw) { - final trimmed = raw.trim().replaceAll('\\', '/'); - if (trimmed.isEmpty) { - return ''; - } - return trimmed - .split('/') - .where( - (segment) => segment.isNotEmpty && segment != '.' && segment != '..', - ) - .join('/'); -} - -List decodeArtifactContent(GoTaskServiceArtifact artifact) { - final encoding = artifact.encoding.trim().toLowerCase(); - if (encoding == 'base64') { - return base64Decode(artifact.content); - } - return utf8.encode(artifact.content); -} - -Future nextArtifactTargetFile(Directory root, String relativePath) async { - final segments = relativePath.split('/'); - final fileName = segments.removeLast(); - final parent = segments.isEmpty - ? root - : Directory('${root.path}/${segments.join('/')}'); - final dotIndex = fileName.lastIndexOf('.'); - final baseName = dotIndex <= 0 ? fileName : fileName.substring(0, dotIndex); - final extension = dotIndex <= 0 ? '' : fileName.substring(dotIndex); - var candidate = File('${parent.path}/$fileName'); - if (!await candidate.exists()) { - return candidate; - } - for (var version = 2; version < 1000; version += 1) { - candidate = File('${parent.path}/$baseName.v$version$extension'); - if (!await candidate.exists()) { - return candidate; - } - } - return File( - '${parent.path}/$baseName.${DateTime.now().millisecondsSinceEpoch}$extension', - ); -} diff --git a/lib/runtime/gateway_acp_client.dart b/lib/runtime/gateway_acp_client.dart index 6346c819..8ffa713f 100644 --- a/lib/runtime/gateway_acp_client.dart +++ b/lib/runtime/gateway_acp_client.dart @@ -107,7 +107,7 @@ class GatewayAcpClient { return _cachedCapabilities; } - final response = await _requestWithFallback( + final response = await _requestForResolvedEndpoint( _GatewayAcpRpcRequest( id: _nextRequestId('capabilities'), method: 'acp.capabilities', @@ -196,7 +196,7 @@ class GatewayAcpClient { ); var lastSequence = -1; try { - final response = await _requestWithFallback( + final response = await _requestForResolvedEndpoint( rpcRequest, onNotification: (notification) { final event = _multiAgentEventFromNotification(notification); @@ -256,7 +256,7 @@ class GatewayAcpClient { Uri? endpointOverride, String authorizationOverride = '', }) async { - await _requestWithFallback( + await _requestForResolvedEndpoint( _GatewayAcpRpcRequest( id: _nextRequestId('cancel'), method: 'session.cancel', @@ -274,7 +274,7 @@ class GatewayAcpClient { Uri? endpointOverride, String authorizationOverride = '', }) async { - await _requestWithFallback( + await _requestForResolvedEndpoint( _GatewayAcpRpcRequest( id: _nextRequestId('close'), method: 'session.close', @@ -293,7 +293,7 @@ class GatewayAcpClient { Uri? endpointOverride, String authorizationOverride = '', }) async { - return _requestWithFallback( + return _requestForResolvedEndpoint( _GatewayAcpRpcRequest( id: _nextRequestId(method), method: method, @@ -307,7 +307,7 @@ class GatewayAcpClient { Future dispose() async {} - Future> _requestWithFallback( + Future> _requestForResolvedEndpoint( _GatewayAcpRpcRequest request, { required void Function(Map) onNotification, Uri? endpointOverride, diff --git a/lib/runtime/gateway_runtime_core.dart b/lib/runtime/gateway_runtime_core.dart index 8129ec31..e65db39b 100644 --- a/lib/runtime/gateway_runtime_core.dart +++ b/lib/runtime/gateway_runtime_core.dart @@ -26,13 +26,10 @@ class GatewayRuntime extends ChangeNotifier with GatewayRuntimeHelpersInternal { required SecureConfigStore store, required DeviceIdentityStore identityStore, GatewayRuntimeSessionClient? sessionClient, - bool allowDirectSocketFallbackOnSessionClientFailure = false, String runtimeId = '', }) : storeInternal = store, identityStoreInternal = identityStore, sessionClientInternal = sessionClient, - allowDirectSocketFallbackOnSessionClientFailureInternal = - allowDirectSocketFallbackOnSessionClientFailure, runtimeIdInternal = runtimeId.trim().isNotEmpty ? runtimeId.trim() : randomIdInternal(); @@ -40,7 +37,6 @@ class GatewayRuntime extends ChangeNotifier with GatewayRuntimeHelpersInternal { final SecureConfigStore storeInternal; final DeviceIdentityStore identityStoreInternal; final GatewayRuntimeSessionClient? sessionClientInternal; - final bool allowDirectSocketFallbackOnSessionClientFailureInternal; final String runtimeIdInternal; final StreamController eventsInternal = StreamController.broadcast(); @@ -316,50 +312,40 @@ class GatewayRuntime extends ChangeNotifier with GatewayRuntimeHelpersInternal { notifyListeners(); return; } on GatewayRuntimeException catch (error) { - if (allowDirectSocketFallbackOnSessionClientFailureInternal && - _shouldFallbackToDirectRuntimeInternal(error)) { + if (error.detailCode == 'AUTH_DEVICE_TOKEN_MISMATCH' && + deviceToken.isNotEmpty && + sharedToken.isEmpty) { + await storeInternal.clearDeviceToken( + deviceId: identity.deviceId, + role: 'operator', + ); + } else if (usedStoredDeviceTokenOnly && + isPairingRequiredErrorInternal(error.code, error.detailCode)) { + await storeInternal.clearDeviceToken( + deviceId: identity.deviceId, + role: 'operator', + ); appendLogInternal( this, 'warn', - 'connect', - 'go-core runtime unavailable, falling back to direct websocket | code: ${error.code ?? 'unknown'}', + 'auth', + 'cleared stale device token after pairing-required response', ); - } else { - if (error.detailCode == 'AUTH_DEVICE_TOKEN_MISMATCH' && - deviceToken.isNotEmpty && - sharedToken.isEmpty) { - await storeInternal.clearDeviceToken( - deviceId: identity.deviceId, - role: 'operator', - ); - } else if (usedStoredDeviceTokenOnly && - isPairingRequiredErrorInternal(error.code, error.detailCode)) { - await storeInternal.clearDeviceToken( - deviceId: identity.deviceId, - role: 'operator', - ); - appendLogInternal( - this, - 'warn', - 'auth', - 'cleared stale device token after pairing-required response', - ); - } - snapshotInternal = snapshotInternal.copyWith( - status: RuntimeConnectionStatus.error, - statusText: 'Connection failed', - lastError: error.toString(), - lastErrorCode: error.code, - lastErrorDetailCode: error.detailCode, - connectAuthMode: connectAuthMode, - connectAuthFields: connectAuthFields, - connectAuthSources: connectAuthSources, - hasSharedAuth: sharedToken.isNotEmpty || password.isNotEmpty, - hasDeviceToken: deviceToken.isNotEmpty, - ); - notifyListeners(); - rethrow; } + snapshotInternal = snapshotInternal.copyWith( + status: RuntimeConnectionStatus.error, + statusText: 'Connection failed', + lastError: error.toString(), + lastErrorCode: error.code, + lastErrorDetailCode: error.detailCode, + connectAuthMode: connectAuthMode, + connectAuthFields: connectAuthFields, + connectAuthSources: connectAuthSources, + hasSharedAuth: sharedToken.isNotEmpty || password.isNotEmpty, + hasDeviceToken: deviceToken.isNotEmpty, + ); + notifyListeners(); + rethrow; } } @@ -556,19 +542,6 @@ class GatewayRuntime extends ChangeNotifier with GatewayRuntimeHelpersInternal { notifyListeners(); } - bool _shouldFallbackToDirectRuntimeInternal(GatewayRuntimeException error) { - switch (error.code) { - case 'GO_GATEWAY_RUNTIME_ENDPOINT_MISSING': - case 'GO_GATEWAY_RUNTIME_TRANSPORT_UNAVAILABLE': - case 'GO_GATEWAY_RUNTIME_WS_CONNECT_TIMEOUT': - case 'GO_GATEWAY_RUNTIME_WS_CLOSED': - case 'GO_GATEWAY_RUNTIME_WS_ERROR': - return true; - default: - return false; - } - } - Future> health() => _healthInternal(); Future> status() => _statusInternal(); diff --git a/lib/runtime/runtime_models_connection.dart b/lib/runtime/runtime_models_connection.dart index 8fefcb83..1518d05f 100644 --- a/lib/runtime/runtime_models_connection.dart +++ b/lib/runtime/runtime_models_connection.dart @@ -316,13 +316,6 @@ List normalizeSingleAgentProviderList( return normalized; } -const List kPresetExternalAcpProviders = - [ - SingleAgentProvider.codex, - SingleAgentProvider.opencode, - SingleAgentProvider.gemini, - ]; - const String kCanonicalGatewayProviderId = 'openclaw'; const String kCanonicalGatewayProviderLabel = 'OpenClaw'; diff --git a/lib/runtime/runtime_models_profiles.dart b/lib/runtime/runtime_models_profiles.dart index 21b22507..349a20ac 100644 --- a/lib/runtime/runtime_models_profiles.dart +++ b/lib/runtime/runtime_models_profiles.dart @@ -10,231 +10,6 @@ import 'runtime_models_runtime_payloads.dart'; import 'runtime_models_gateway_entities.dart'; import 'runtime_models_multi_agent.dart'; -class ExternalAcpEndpointProfile { - const ExternalAcpEndpointProfile({ - required this.providerKey, - required this.label, - required this.badge, - required this.endpoint, - required this.authRef, - required this.enabled, - }); - - final String providerKey; - final String label; - final String badge; - final String endpoint; - final String authRef; - final bool enabled; - - factory ExternalAcpEndpointProfile.defaultsForProvider( - SingleAgentProvider provider, - ) { - return ExternalAcpEndpointProfile( - providerKey: provider.providerId, - label: provider.label, - badge: provider.badge, - endpoint: '', - authRef: '', - enabled: true, - ); - } - - ExternalAcpEndpointProfile copyWith({ - String? providerKey, - String? label, - String? badge, - String? endpoint, - String? authRef, - bool? enabled, - }) { - return ExternalAcpEndpointProfile( - providerKey: normalizeSingleAgentProviderId( - providerKey ?? this.providerKey, - ), - label: (label ?? this.label).trim(), - badge: (badge ?? this.badge).trim(), - endpoint: (endpoint ?? this.endpoint).trim(), - authRef: (authRef ?? this.authRef).trim(), - enabled: enabled ?? this.enabled, - ); - } - - SingleAgentProvider? get builtinProvider { - final normalized = providerKey.trim().toLowerCase(); - for (final provider in kPresetExternalAcpProviders) { - if (provider.providerId == normalized) { - return provider; - } - } - return null; - } - - bool get isPreset => - kPresetExternalAcpProviders.any((item) => item.providerId == providerKey); - - SingleAgentProvider toProvider() { - final builtin = builtinProvider; - return SingleAgentProvider.fromJsonValue( - providerKey, - label: label, - badge: badge, - ).copyWith( - source: builtin?.source ?? SingleAgentProviderSource.externalExtension, - ); - } - - Map toJson() { - return { - 'providerKey': providerKey, - 'label': label, - 'badge': badge, - 'endpoint': endpoint, - 'authRef': authRef, - 'enabled': enabled, - }; - } - - factory ExternalAcpEndpointProfile.fromJson(Map json) { - final providerKey = normalizeSingleAgentProviderId( - json['providerKey']?.toString() ?? '', - ); - final builtin = SingleAgentProviderCopy.fromJsonValue(providerKey); - final fallbackLabel = builtin.isUnspecified ? providerKey : builtin.label; - final label = json['label']?.toString().trim().isNotEmpty == true - ? json['label'].toString().trim() - : fallbackLabel; - return ExternalAcpEndpointProfile( - providerKey: providerKey, - label: label, - badge: json['badge']?.toString().trim().isNotEmpty == true - ? json['badge'].toString().trim() - : singleAgentProviderFallbackBadgeInternal( - providerId: providerKey, - label: label, - ), - endpoint: json['endpoint']?.toString().trim() ?? '', - authRef: json['authRef']?.toString().trim() ?? '', - enabled: json['enabled'] as bool? ?? true, - ); - } -} - -List normalizeExternalAcpEndpoints({ - Iterable? profiles, -}) { - final incoming = - profiles?.toList(growable: false) ?? const []; - final byKey = {}; - - SingleAgentProvider? canonicalProviderForProfile( - ExternalAcpEndpointProfile profile, - ) { - final key = profile.providerKey.trim().toLowerCase(); - for (final provider in kPresetExternalAcpProviders) { - if (provider.providerId == key) { - return provider; - } - } - final label = profile.label.trim(); - final badge = profile.badge.trim(); - for (final provider in kPresetExternalAcpProviders) { - if (provider.label == label && provider.badge == badge) { - return provider; - } - } - return null; - } - - for (final item in incoming) { - final originalKey = item.providerKey.trim().toLowerCase(); - final canonicalProvider = canonicalProviderForProfile(item); - final key = canonicalProvider?.providerId ?? originalKey; - if (key.isEmpty) { - continue; - } - if (!isBridgeOwnedSingleAgentProviderId(originalKey) && - item.endpoint.trim().isEmpty) { - continue; - } - final normalizedItem = item.copyWith( - providerKey: key, - label: canonicalProvider?.label ?? item.label, - badge: canonicalProvider?.badge ?? item.badge, - ); - final existing = byKey[key]; - if (existing == null || - (existing.endpoint.trim().isEmpty && - normalizedItem.endpoint.trim().isNotEmpty)) { - byKey[key] = normalizedItem; - } - } - - final normalized = [ - for (final provider in kPresetExternalAcpProviders) - byKey.remove(provider.providerId) ?? - ExternalAcpEndpointProfile.defaultsForProvider(provider), - ...byKey.values, - ]; - return List.unmodifiable(normalized); -} - -List replaceExternalAcpEndpointForProvider( - List profiles, - SingleAgentProvider provider, - ExternalAcpEndpointProfile profile, -) { - final normalized = normalizeExternalAcpEndpoints(profiles: profiles); - final next = List.from(normalized); - final index = next.indexWhere( - (item) => item.providerKey.trim().toLowerCase() == provider.providerId, - ); - final resolved = profile.copyWith( - providerKey: provider.providerId, - label: profile.label.trim().isEmpty ? provider.label : profile.label, - badge: profile.badge.trim().isEmpty ? provider.badge : profile.badge, - ); - if (index == -1) { - next.add(resolved); - } else { - next[index] = resolved; - } - return normalizeExternalAcpEndpoints(profiles: next); -} - -ExternalAcpEndpointProfile buildCustomExternalAcpEndpointProfile( - Iterable profiles, { - required String label, - required String endpoint, -}) { - final normalizedProfiles = normalizeExternalAcpEndpoints(profiles: profiles); - var suffix = normalizedProfiles.length + 1; - - String providerKey() => 'custom-agent-$suffix'; - - final existingKeys = normalizedProfiles - .map((item) => item.providerKey) - .toSet(); - while (existingKeys.contains(providerKey())) { - suffix += 1; - } - - final normalizedLabel = label.trim().isEmpty - ? 'Custom ACP Endpoint $suffix' - : label.trim(); - return ExternalAcpEndpointProfile( - providerKey: providerKey(), - label: normalizedLabel, - badge: singleAgentProviderFallbackBadgeInternal( - providerId: providerKey(), - label: normalizedLabel, - ), - endpoint: endpoint.trim(), - authRef: '', - enabled: true, - ); -} - String normalizeAuthorizedSkillDirectoryPath(String path) { var trimmed = path.trim(); if (trimmed.isEmpty) { @@ -377,7 +152,7 @@ extension AssistantPermissionLevelCopy on AssistantPermissionLevel { } } -enum CodeAgentRuntimeMode { builtIn, externalCli } +enum CodeAgentRuntimeMode { externalCli } extension CodeAgentRuntimeModeCopy on CodeAgentRuntimeMode { String get label => switch (this) { @@ -385,7 +160,6 @@ extension CodeAgentRuntimeModeCopy on CodeAgentRuntimeMode { '外部 Codex CLI', 'External Codex CLI', ), - CodeAgentRuntimeMode.builtIn => appText('内置 Codex', 'Built-in Codex'), }; static CodeAgentRuntimeMode fromJsonValue(String? value) { diff --git a/lib/runtime/single_agent_capabilities.dart b/lib/runtime/single_agent_capabilities.dart deleted file mode 100644 index a1f13dfd..00000000 --- a/lib/runtime/single_agent_capabilities.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'runtime_models.dart'; - -class SingleAgentCapabilities { - const SingleAgentCapabilities({ - required this.available, - required this.supportedProviders, - required this.endpoint, - this.errorMessage, - }); - - const SingleAgentCapabilities.unavailable({ - required this.endpoint, - this.errorMessage, - }) : available = false, - supportedProviders = const []; - - final bool available; - final List supportedProviders; - final String endpoint; - final String? errorMessage; - - bool get supportsCodex => supportsProvider(SingleAgentProvider.codex); - - bool supportsProvider(SingleAgentProvider provider) => - supportedProviders.contains(provider); -} diff --git a/test/features/assistant/assistant_lower_pane_test.dart b/test/features/assistant/assistant_lower_pane_test.dart index 3cca7824..82f011ba 100644 --- a/test/features/assistant/assistant_lower_pane_test.dart +++ b/test/features/assistant/assistant_lower_pane_test.dart @@ -4,13 +4,20 @@ import 'package:xworkmate/app/app_controller.dart'; import 'package:xworkmate/features/assistant/assistant_page_composer_clipboard.dart'; import 'package:xworkmate/features/assistant/assistant_page_composer_skill_models.dart'; import 'package:xworkmate/features/assistant/assistant_page_main.dart'; +import 'package:xworkmate/runtime/runtime_models.dart'; import 'package:xworkmate/theme/app_theme.dart'; import 'package:xworkmate/widgets/surface_card.dart'; void main() { group('AssistantLowerPaneInternal', () { testWidgets('shows agent and gateway task dialog modes', (tester) async { - final controller = AppController(); + final controller = AppController( + initialBridgeProviderCatalog: const [ + SingleAgentProvider.codex, + SingleAgentProvider.opencode, + SingleAgentProvider.gemini, + ], + ); addTearDown(controller.dispose); await controller.sessionsController.switchSession('session-1'); @@ -66,7 +73,13 @@ void main() { testWidgets('shows assistant providers and allows switching provider', ( tester, ) async { - final controller = AppController(); + final controller = AppController( + initialBridgeProviderCatalog: const [ + SingleAgentProvider.codex, + SingleAgentProvider.opencode, + SingleAgentProvider.gemini, + ], + ); addTearDown(controller.dispose); await controller.sessionsController.switchSession('session-1'); diff --git a/test/runtime/ui_feature_manifest_desktop_surface_test.dart b/test/runtime/ui_feature_manifest_desktop_surface_test.dart index 13481ebb..60e4e1f0 100644 --- a/test/runtime/ui_feature_manifest_desktop_surface_test.dart +++ b/test/runtime/ui_feature_manifest_desktop_surface_test.dart @@ -22,20 +22,5 @@ void main() { }, ); }); - - test('fallback manifest only exposes assistant and settings on desktop', () { - final desktop = UiFeatureManifest.fallback().forPlatform( - UiFeaturePlatform.desktop, - buildMode: UiFeatureBuildMode.debug, - ); - - expect( - desktop.allowedDestinations, - { - WorkspaceDestination.assistant, - WorkspaceDestination.settings, - }, - ); - }); }); }