Remove local CLI and provider mirror decisions
This commit is contained in:
parent
0505024e6d
commit
64e14beb70
@ -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<SingleAgentProvider, SingleAgentCapabilities>
|
||||
singleAgentCapabilitiesByProviderInternal =
|
||||
const <SingleAgentProvider, SingleAgentCapabilities>{};
|
||||
List<SingleAgentProvider> bridgeAdvertisedProvidersInternal =
|
||||
const <SingleAgentProvider>[];
|
||||
final Map<String, List<GatewayChatMessage>> 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<SingleAgentProvider> get availableSingleAgentProviders =>
|
||||
configuredSingleAgentProviders
|
||||
.where(canUseSingleAgentProviderInternal)
|
||||
.toList(growable: false);
|
||||
|
||||
List<AssistantExecutionTarget> visibleAssistantExecutionTargets(
|
||||
Iterable<AssistantExecutionTarget> supportedTargets,
|
||||
) {
|
||||
final supported = supportedTargets.toSet();
|
||||
final visible = <AssistantExecutionTarget>[];
|
||||
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<String> get aiGatewayConversationModelChoices {
|
||||
|
||||
@ -75,7 +75,6 @@ Future<void> 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<void> refreshSingleAgentCapabilitiesRuntimeInternal(
|
||||
);
|
||||
controller.bridgeAdvertisedProvidersInternal =
|
||||
normalizeSingleAgentProviderList(capabilities.providerCatalog);
|
||||
final next = <SingleAgentProvider, SingleAgentCapabilities>{};
|
||||
for (final provider in controller.bridgeAdvertisedProvidersInternal) {
|
||||
next[provider] = SingleAgentCapabilities(
|
||||
available: true,
|
||||
supportedProviders: <SingleAgentProvider>[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<void> ensureCodexGatewayRegistrationRuntimeInternal(
|
||||
'providerId': 'codex',
|
||||
'runtimeMode': controller.effectiveCodeAgentRuntimeMode.name,
|
||||
'gatewayMode': bridgeGatewayModeRuntimeInternal(controller).name,
|
||||
'binaryConfigured':
|
||||
(controller.resolvedCodexCliPath ??
|
||||
controller.configuredCodexCliPath)
|
||||
.trim()
|
||||
.isNotEmpty,
|
||||
'capabilities': const <String>[
|
||||
'chat',
|
||||
'code-edit',
|
||||
|
||||
@ -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<void> 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<ManagedMountTargetState> mergeAcpCapabilitiesIntoMountTargetsInternal(
|
||||
List<ManagedMountTargetState> 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;
|
||||
|
||||
@ -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((_) {}));
|
||||
|
||||
@ -57,9 +57,6 @@ Future<void> 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<void> 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<void> 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(
|
||||
|
||||
@ -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<SingleAgentProvider> get singleAgentProviderOptions =>
|
||||
availableSingleAgentProviders.isNotEmpty
|
||||
? availableSingleAgentProviders
|
||||
: configuredSingleAgentProviders;
|
||||
configuredSingleAgentProviders;
|
||||
|
||||
String singleAgentProviderLabelForSession(String sessionKey) {
|
||||
return singleAgentProviderForSession(sessionKey).label;
|
||||
|
||||
@ -79,7 +79,6 @@ Future<void> 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<String> assistantModelChoicesForSessionThreadSessionInternal(
|
||||
normalizedSessionKey,
|
||||
);
|
||||
if (target == AssistantExecutionTarget.singleAgent) {
|
||||
final singleAgentUsesAiGatewayFallback =
|
||||
!controller.hasAnyAvailableSingleAgentProvider &&
|
||||
controller.canUseAiGatewayConversation;
|
||||
if (singleAgentUsesAiGatewayFallback) {
|
||||
return controller.aiGatewayConversationModelChoices;
|
||||
}
|
||||
final runtimeModel = controller.singleAgentRuntimeModelForSession(
|
||||
normalizedSessionKey,
|
||||
);
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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 <String, dynamic>{
|
||||
'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 = <String, dynamic>{
|
||||
'node': <String, dynamic>{
|
||||
@ -107,7 +98,6 @@ class CodeAgentNodeOrchestrator {
|
||||
CodeAgentRuntimeMode.externalCli => 'stdio-jsonrpc',
|
||||
CodeAgentRuntimeMode.builtIn => 'ffi-runtime',
|
||||
},
|
||||
if (configuredPath.isNotEmpty) 'binaryConfigured': true,
|
||||
},
|
||||
if (provider != null)
|
||||
'provider': <String, dynamic>{
|
||||
|
||||
@ -16,7 +16,6 @@ class GoMultiAgentMountDesktopClient implements MultiAgentMountResolver {
|
||||
Future<MultiAgentConfig?> 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(),
|
||||
|
||||
@ -40,7 +40,6 @@ abstract class MultiAgentMountResolver {
|
||||
Future<MultiAgentConfig?> reconcile({
|
||||
required MultiAgentConfig config,
|
||||
required String aiGatewayUrl,
|
||||
String configuredCodexCliPath = '',
|
||||
required String codexHome,
|
||||
required String opencodeHome,
|
||||
required ArisMountProbe arisProbe,
|
||||
|
||||
@ -52,12 +52,10 @@ class MultiAgentMountManager {
|
||||
Future<MultiAgentConfig> 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<MultiAgentConfig> _reconcileLocally({
|
||||
required MultiAgentConfig config,
|
||||
required String aiGatewayUrl,
|
||||
String configuredCodexCliPath = '',
|
||||
}) async {
|
||||
final states = <ManagedMountTargetState>[];
|
||||
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<bool> isInstalled({String configuredCodexCliPath = ''});
|
||||
Future<bool> isInstalled();
|
||||
|
||||
Future<ManagedMountTargetState> reconcile({
|
||||
required MultiAgentConfig config,
|
||||
required String aiGatewayUrl,
|
||||
String configuredCodexCliPath = '',
|
||||
});
|
||||
|
||||
Future<String> _runCommand(List<String> command) async {
|
||||
@ -188,15 +180,6 @@ abstract class CliMountAdapter {
|
||||
.length;
|
||||
}
|
||||
|
||||
Future<bool> _binaryExists(String command) async {
|
||||
final check = await Process.run(
|
||||
Platform.isWindows ? 'where' : 'which',
|
||||
<String>[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<bool> isInstalled({String configuredCodexCliPath = ''}) async {
|
||||
Future<bool> isInstalled() async {
|
||||
try {
|
||||
await _bundleRepository.loadManifest();
|
||||
return true;
|
||||
@ -243,7 +226,6 @@ class ArisMountAdapter extends CliMountAdapter {
|
||||
Future<ManagedMountTargetState> 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<bool> isInstalled({String configuredCodexCliPath = ''}) async {
|
||||
final configuredPath = configuredCodexCliPath.trim();
|
||||
if (configuredPath.isNotEmpty && await File(configuredPath).exists()) {
|
||||
return true;
|
||||
}
|
||||
return _binaryExists('codex');
|
||||
}
|
||||
Future<bool> isInstalled() async => false;
|
||||
|
||||
@override
|
||||
Future<ManagedMountTargetState> 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<bool> isInstalled({String configuredCodexCliPath = ''}) =>
|
||||
_binaryExists('claude');
|
||||
Future<bool> isInstalled() async => false;
|
||||
|
||||
@override
|
||||
Future<ManagedMountTargetState> 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<bool> isInstalled({String configuredCodexCliPath = ''}) =>
|
||||
_binaryExists('gemini');
|
||||
Future<bool> isInstalled() async => false;
|
||||
|
||||
@override
|
||||
Future<ManagedMountTargetState> 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<bool> isInstalled({String configuredCodexCliPath = ''}) =>
|
||||
_binaryExists('opencode');
|
||||
Future<bool> isInstalled() async => false;
|
||||
|
||||
@override
|
||||
Future<ManagedMountTargetState> 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<bool> isInstalled({String configuredCodexCliPath = ''}) =>
|
||||
_binaryExists('openclaw');
|
||||
Future<bool> isInstalled() async => false;
|
||||
|
||||
@override
|
||||
Future<ManagedMountTargetState> reconcile({
|
||||
required MultiAgentConfig config,
|
||||
required String aiGatewayUrl,
|
||||
String configuredCodexCliPath = '',
|
||||
}) async {
|
||||
final available = await isInstalled();
|
||||
final configFile = File(
|
||||
|
||||
@ -425,14 +425,12 @@ class AcpBridgeServerAdvancedOverrides {
|
||||
required this.gatewayProfiles,
|
||||
required this.vault,
|
||||
required this.aiGateway,
|
||||
required this.acpBridgeServerProfiles,
|
||||
required this.authorizedSkillDirectories,
|
||||
});
|
||||
|
||||
final List<GatewayConnectionProfile> gatewayProfiles;
|
||||
final VaultConfig vault;
|
||||
final AiGatewayProfile aiGateway;
|
||||
final List<ExternalAcpEndpointProfile> acpBridgeServerProfiles;
|
||||
final List<AuthorizedSkillDirectory> 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<GatewayConnectionProfile>? gatewayProfiles,
|
||||
VaultConfig? vault,
|
||||
AiGatewayProfile? aiGateway,
|
||||
List<ExternalAcpEndpointProfile>? acpBridgeServerProfiles,
|
||||
List<AuthorizedSkillDirectory>? 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<String, dynamic>() ?? const {},
|
||||
),
|
||||
acpBridgeServerProfiles: normalizeExternalAcpEndpoints(
|
||||
profiles:
|
||||
((json['acpBridgeServerProfiles'] as List?) ?? const <Object>[])
|
||||
.whereType<Map>()
|
||||
.map(
|
||||
(item) => ExternalAcpEndpointProfile.fromJson(
|
||||
item.cast<String, dynamic>(),
|
||||
),
|
||||
),
|
||||
),
|
||||
authorizedSkillDirectories: normalizeAuthorizedSkillDirectories(
|
||||
directories:
|
||||
((json['authorizedSkillDirectories'] as List?) ?? const <Object>[])
|
||||
|
||||
@ -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<GatewayConnectionProfile> gatewayProfiles;
|
||||
final List<ExternalAcpEndpointProfile> providerSyncDefinitions;
|
||||
final List<AuthorizedSkillDirectory> 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<GatewayConnectionProfile>? gatewayProfiles,
|
||||
List<ExternalAcpEndpointProfile>? providerSyncDefinitions,
|
||||
List<AuthorizedSkillDirectory>? 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<String, dynamic>()),
|
||||
),
|
||||
);
|
||||
final providerSyncDefinitions = normalizeExternalAcpEndpoints(
|
||||
profiles: ((json['providerSyncDefinitions'] as List?) ?? const <Object>[])
|
||||
.whereType<Map>()
|
||||
.map(
|
||||
(item) => ExternalAcpEndpointProfile.fromJson(
|
||||
item.cast<String, dynamic>(),
|
||||
),
|
||||
),
|
||||
);
|
||||
final authorizedSkillDirectories = normalizeAuthorizedSkillDirectories(
|
||||
directories:
|
||||
((json['authorizedSkillDirectories'] as List?) ?? const <Object>[])
|
||||
@ -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<String, dynamic>() ?? 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>[
|
||||
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<SingleAgentProvider> providers,
|
||||
) {
|
||||
controller.bridgeAdvertisedProvidersInternal = providers;
|
||||
controller.singleAgentCapabilitiesByProviderInternal = {
|
||||
for (final provider in providers)
|
||||
provider: SingleAgentCapabilities(
|
||||
available: true,
|
||||
supportedProviders: <SingleAgentProvider>[provider],
|
||||
endpoint: 'bridge',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
class _FakeSkillDirectoryAccessService implements SkillDirectoryAccessService {
|
||||
|
||||
@ -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<SingleAgentProvider> providers,
|
||||
) {
|
||||
controller.bridgeAdvertisedProvidersInternal = providers;
|
||||
controller.singleAgentCapabilitiesByProviderInternal = {
|
||||
for (final provider in providers)
|
||||
provider: SingleAgentCapabilities(
|
||||
available: true,
|
||||
supportedProviders: <SingleAgentProvider>[provider],
|
||||
endpoint: 'bridge',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
class _CapturingGoTaskServiceClient implements GoTaskServiceClient {
|
||||
|
||||
@ -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<SingleAgentProvider> providers,
|
||||
) {
|
||||
controller.bridgeAdvertisedProvidersInternal = providers;
|
||||
controller.singleAgentCapabilitiesByProviderInternal = {
|
||||
for (final provider in providers)
|
||||
provider: SingleAgentCapabilities(
|
||||
available: true,
|
||||
supportedProviders: <SingleAgentProvider>[provider],
|
||||
endpoint: 'bridge',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
class _FakeSkillDirectoryAccessService implements SkillDirectoryAccessService {
|
||||
|
||||
@ -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<SingleAgentProvider> providers,
|
||||
) {
|
||||
controller.bridgeAdvertisedProvidersInternal = providers;
|
||||
controller.singleAgentCapabilitiesByProviderInternal = {
|
||||
for (final provider in providers)
|
||||
provider: SingleAgentCapabilities(
|
||||
available: true,
|
||||
supportedProviders: <SingleAgentProvider>[provider],
|
||||
endpoint: 'bridge',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
class _GoldenSkillDirectoryAccessService
|
||||
|
||||
@ -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, <String>['codex', 'opencode', 'gemini']);
|
||||
});
|
||||
|
||||
test('round trips providerSyncDefinitions and schemaVersion', () {
|
||||
final snapshot = SettingsSnapshot.defaults().copyWith(
|
||||
providerSyncDefinitions: <ExternalAcpEndpointProfile>[
|
||||
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(<String, dynamic>{
|
||||
@ -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(<String, dynamic>{
|
||||
'schemaVersion': settingsSnapshotSchemaVersion,
|
||||
'appLanguage': 'zh',
|
||||
'gatewayProfiles': <Map<String, dynamic>>[],
|
||||
'providerSyncDefinitions': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'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(<String, dynamic>{
|
||||
'advancedOverrides': <String, dynamic>{
|
||||
'acpBridgeServerProfiles': <Map<String, dynamic>>[
|
||||
@ -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<String, dynamic>();
|
||||
|
||||
expect(providerKeys, <String>['codex', 'opencode', 'gemini']);
|
||||
expect(advancedOverrides, isNotNull);
|
||||
expect(advancedOverrides!.containsKey('acpBridgeServerProfiles'), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user