Merge branch 'codex/cleanup-single-agent-garbage'

This commit is contained in:
Haitao Pan 2026-04-12 18:40:21 +08:00
commit 879451ba98
36 changed files with 134 additions and 1639 deletions

View File

@ -43,7 +43,6 @@ import 'task_thread_repositories.dart';
import 'app_controller_desktop_navigation.dart';
import 'app_controller_desktop_gateway.dart';
import 'app_controller_desktop_settings.dart';
import 'app_controller_desktop_single_agent.dart';
import 'app_controller_desktop_thread_sessions.dart';
import 'app_controller_desktop_thread_actions.dart';
import 'app_controller_desktop_workspace_execution.dart';
@ -319,8 +318,6 @@ class AppController extends ChangeNotifier {
<String, HttpClient>{};
final Set<String> aiGatewayPendingSessionKeysInternal = <String>{};
final Set<String> aiGatewayAbortedSessionKeysInternal = <String>{};
final Set<String> singleAgentExternalCliPendingSessionKeysInternal =
<String>{};
final Map<String, Future<void>> assistantThreadTurnQueuesInternal =
<String, Future<void>>{};
bool multiAgentRunPendingInternal = false;
@ -483,9 +480,7 @@ class AppController extends ChangeNotifier {
bool get hasPendingSettingsApply => pendingSettingsApplyInternal;
String get settingsDraftStatusMessage => settingsDraftStatusMessageInternal;
List<GatewayAgentSummary> get agents => agentsControllerInternal.agents;
List<GatewaySessionSummary> get sessions => isSingleAgentMode
? assistantSessionSummariesInternal()
: sessionsControllerInternal.sessions;
List<GatewaySessionSummary> get sessions => sessionsControllerInternal.sessions;
List<GatewaySessionSummary> get assistantSessions =>
assistantSessionsInternal();
List<GatewayInstanceSummary> get instances =>
@ -523,8 +518,6 @@ class AppController extends ChangeNotifier {
settingsControllerInternal.effectiveAiGatewayBaseUrl.trim();
bool get hasStoredAiGatewayApiKey =>
settingsControllerInternal.hasEffectiveAiGatewayApiKey;
bool get isSingleAgentMode =>
currentAssistantExecutionTarget == AssistantExecutionTarget.singleAgent;
bool get isCodexBridgeBusy => isCodexBridgeBusyInternal;
String? get codexBridgeError => codexBridgeErrorInternal;
String? get codexRuntimeWarning => codexRuntimeWarningInternal;
@ -535,8 +528,6 @@ class AppController extends ChangeNotifier {
CodexCooperationState get codexCooperationState =>
codexCooperationStateInternal;
bool get isMultiAgentRunPending => multiAgentRunPendingInternal;
bool get showsSingleAgentRuntimeDebugMessagesInternal =>
settings.experimentalDebug;
bool desktopPlatformBusyInternal = false;
static const String draftAiGatewayApiKeyKeyInternal = 'ai_gateway_api_key';
@ -552,11 +543,9 @@ class AppController extends ChangeNotifier {
resolvedAiGatewayModel.isNotEmpty;
int get activeGatewayProfileIndexInternal {
final target = currentAssistantExecutionTarget;
if (target == AssistantExecutionTarget.singleAgent) {
return kGatewayRemoteProfileIndex;
}
return gatewayProfileIndexForExecutionTargetInternal(target);
return gatewayProfileIndexForExecutionTargetInternal(
currentAssistantExecutionTarget,
);
}
bool hasStoredGatewayTokenForProfile(int profileIndex) =>
@ -593,27 +582,7 @@ class AppController extends ChangeNotifier {
List<AssistantExecutionTarget> visibleAssistantExecutionTargets(
Iterable<AssistantExecutionTarget> supportedTargets,
) {
final supported = supportedTargets.toSet();
final visible = <AssistantExecutionTarget>[];
if (supported.contains(AssistantExecutionTarget.singleAgent) &&
bridgeProviderCatalog.isNotEmpty) {
visible.add(AssistantExecutionTarget.singleAgent);
}
if (supported.contains(AssistantExecutionTarget.gateway)) {
visible.add(AssistantExecutionTarget.gateway);
}
if (!supportedTargets.contains(AssistantExecutionTarget.singleAgent) ||
visible.contains(AssistantExecutionTarget.singleAgent)) {
return visible;
}
return <AssistantExecutionTarget>[
AssistantExecutionTarget.singleAgent,
...visible.where(
(target) => target != AssistantExecutionTarget.singleAgent,
),
];
}
) => const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
List<String> get aiGatewayConversationModelChoices {
final availableModels =
@ -654,9 +623,6 @@ class AppController extends ChangeNotifier {
String resolvedAssistantModelForTargetInternal(
AssistantExecutionTarget target,
) {
if (target == AssistantExecutionTarget.singleAgent) {
return '';
}
final resolved = resolvedDefaultModel.trim();
if (resolved.isNotEmpty) {
return resolved;

View File

@ -45,13 +45,7 @@ extension AppControllerDesktopExternalAcpRouting on AppController {
sessionKey,
);
final thread = assistantThreadRecordsInternal[normalizedSessionKey];
final sessionTarget = assistantExecutionTargetForSession(
normalizedSessionKey,
);
final preferredGatewayTarget = switch (sessionTarget) {
AssistantExecutionTarget.gateway => kCanonicalGatewayProviderId,
AssistantExecutionTarget.singleAgent => kCanonicalGatewayProviderId,
};
const preferredGatewayTarget = kCanonicalGatewayProviderId;
final availableSkills =
assistantImportedSkillsForSession(normalizedSessionKey)
.map((item) {
@ -70,11 +64,7 @@ extension AppControllerDesktopExternalAcpRouting on AppController {
.where((item) => item.trim().isNotEmpty)
.toList(growable: false);
final resolvedExplicitProviderId =
sessionTarget == AssistantExecutionTarget.singleAgent &&
(thread?.hasExplicitProviderSelection ?? false)
? singleAgentProviderForSession(normalizedSessionKey).providerId
: '';
const resolvedExplicitProviderId = '';
final resolvedExplicitModel = thread?.hasExplicitModelSelection ?? false
? assistantModelForSession(normalizedSessionKey)
: '';
@ -121,7 +111,6 @@ extension AppControllerDesktopExternalAcpRouting on AppController {
String _routingExecutionTargetValueInternal(AssistantExecutionTarget target) {
return switch (target) {
AssistantExecutionTarget.singleAgent => 'single-agent',
AssistantExecutionTarget.gateway => 'gateway',
};
}

View File

@ -31,13 +31,11 @@ import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.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 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
import 'app_controller_desktop_gateway.dart';
import 'app_controller_desktop_settings.dart';
import 'app_controller_desktop_single_agent.dart';
import 'app_controller_desktop_thread_sessions.dart';
import 'app_controller_desktop_thread_actions.dart';
import 'app_controller_desktop_workspace_execution.dart';
@ -53,19 +51,9 @@ Future<void> refreshAcpCapabilitiesRuntimeInternal(
bool persistMountTargets = false,
}) async {
try {
final target = controller.assistantExecutionTargetForSession(
controller.sessionsControllerInternal.currentSessionKey,
await controller.gatewayAcpClientInternal.loadCapabilities(
forceRefresh: forceRefresh,
);
if (target == AssistantExecutionTarget.singleAgent) {
await controller.goTaskServiceClientInternal.loadExternalAcpCapabilities(
target: AssistantExecutionTarget.singleAgent,
forceRefresh: forceRefresh,
);
} else {
await controller.gatewayAcpClientInternal.loadCapabilities(
forceRefresh: forceRefresh,
);
}
} catch (_) {
// Keep mount refresh resilient when ACP is temporarily unavailable.
}
@ -91,19 +79,7 @@ Future<void> refreshAcpCapabilitiesRuntimeInternal(
Future<void> refreshSingleAgentCapabilitiesRuntimeInternal(
AppController controller, {
bool forceRefresh = false,
}) async {
final capabilities = await controller.goTaskServiceClientInternal
.loadExternalAcpCapabilities(
target: AssistantExecutionTarget.singleAgent,
forceRefresh: forceRefresh,
);
controller.bridgeProviderCatalogInternal = normalizeSingleAgentProviderList(
capabilities.providerCatalog,
);
if (!controller.disposedInternal) {
controller.notifyListeners();
}
}
}) async {}
List<ManagedMountTargetState>
mergeAcpCapabilitiesIntoMountTargetsRuntimeInternal(
@ -192,42 +168,6 @@ String? resolveLocalAssistantWorkingDirectoryForSessionRuntimeInternal(
return candidate;
}
String? resolveSingleAgentWorkingDirectoryForSessionRuntimeInternal(
AppController controller,
String sessionKey, {
SingleAgentProvider? provider,
}) {
final requiresLocalPath =
provider == null ||
singleAgentProviderRequiresLocalPathRuntimeInternal(controller, provider);
final record =
controller.assistantThreadRecordsInternal[controller
.normalizedAssistantSessionKeyInternal(sessionKey)];
if (!requiresLocalPath && record?.workspaceKind == WorkspaceKind.remoteFs) {
return assistantWorkingDirectoryForSessionRuntimeInternal(
controller,
sessionKey,
);
}
return resolveLocalAssistantWorkingDirectoryForSessionRuntimeInternal(
controller,
sessionKey,
requireLocalExistence: requiresLocalPath,
);
}
bool singleAgentProviderRequiresLocalPathRuntimeInternal(
AppController _,
SingleAgentProvider provider,
) {
if (provider == SingleAgentProvider.codex ||
provider == SingleAgentProvider.opencode ||
provider == SingleAgentProvider.gemini) {
return false;
}
return true;
}
CodeAgentNodeState buildCodeAgentNodeStateRuntimeInternal(
AppController controller,
) {
@ -247,7 +187,6 @@ GatewayMode bridgeGatewayModeRuntimeInternal(AppController controller) {
return GatewayMode.offline;
}
return switch (controller.currentAssistantExecutionTarget) {
AssistantExecutionTarget.singleAgent => GatewayMode.offline,
AssistantExecutionTarget.gateway => GatewayMode.remote,
};
}

View File

@ -226,36 +226,9 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
}) {
final raw = error.toString().trim();
final lowered = raw.toLowerCase();
if ((lowered.contains('acp_endpoint_missing') ||
lowered.contains('missing acp endpoint')) &&
target == AssistantExecutionTarget.singleAgent) {
return appText(
'当前线程缺少可用的 Bridge Server暂时无法继续。',
'This thread does not have an available bridge server yet.',
);
}
if (lowered.contains('gateway not connected') ||
lowered.contains('code: offline') ||
lowered.contains('offlin') && lowered.contains('gateway')) {
if (target == AssistantExecutionTarget.singleAgent) {
final selection = singleAgentProviderForSession(
sessionsControllerInternal.currentSessionKey,
);
final provider = currentSingleAgentResolvedProvider ?? selection;
final providerLabel = provider.isUnspecified
? appText('Bridge Provider', 'Bridge Provider')
: provider.label;
final address = _extractGatewayAddressFromErrorInternal(raw);
return address.isEmpty
? appText(
'当前线程的 Bridge Provider$providerLabel)未连接。请先恢复该 Provider 对应连接后再重试。',
'The Bridge Provider for this thread ($providerLabel) is offline. Restore that provider connection, then try again.',
)
: appText(
'当前线程的 Bridge Provider$providerLabel)未连接:$address。请先恢复该 Provider 对应连接后再重试。',
'The Bridge Provider for this thread ($providerLabel) is offline: $address. Restore that provider connection, then try again.',
);
}
final profile = gatewayProfileForAssistantExecutionTargetInternal(target);
final address = gatewayAddressLabelInternal(profile);
return address == appText('未连接目标', 'No target')
@ -271,13 +244,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
return raw;
}
String _extractGatewayAddressFromErrorInternal(String raw) {
final match = RegExp(
r'((?:\d{1,3}\.){3}\d{1,3}:\d+|localhost:\d+|[a-zA-Z0-9.-]+:\d+)',
).firstMatch(raw);
return match?.group(1)?.trim() ?? '';
}
String formatAiGatewayHttpErrorInternal(int statusCode, String detail) {
final base = switch (statusCode) {
400 => appText(
@ -477,19 +443,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
requireLocalExistence: requireLocalExistence,
);
String? resolveSingleAgentWorkingDirectoryForSessionInternal(
String sessionKey, {
SingleAgentProvider? provider,
}) => resolveSingleAgentWorkingDirectoryForSessionRuntimeInternal(
this,
sessionKey,
provider: provider,
);
bool singleAgentProviderRequiresLocalPathInternal(
SingleAgentProvider provider,
) => singleAgentProviderRequiresLocalPathRuntimeInternal(this, provider);
void registerCodexExternalProviderInternal() {
runtimeCoordinatorInternal.registerExternalCodeAgent(
ExternalCodeAgentProvider(
@ -611,10 +564,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
if (disposedInternal) {
return;
}
if (assistantExecutionTargetForSession(currentSessionKey) ==
AssistantExecutionTarget.singleAgent) {
await refreshSingleAgentSkillsForSession(currentSessionKey);
}
}
notifyIfActiveInternal();
}
@ -800,35 +749,16 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
AssistantExecutionTarget assistantExecutionTargetForModeInternal(
RuntimeConnectionMode mode,
) {
return switch (mode) {
RuntimeConnectionMode.unconfigured =>
AssistantExecutionTarget.singleAgent,
RuntimeConnectionMode.local => AssistantExecutionTarget.gateway,
RuntimeConnectionMode.remote => AssistantExecutionTarget.gateway,
};
return AssistantExecutionTarget.gateway;
}
GatewayConnectionProfile gatewayProfileForAssistantExecutionTargetInternal(
AssistantExecutionTarget target,
) {
return switch (target) {
AssistantExecutionTarget.gateway => settings.primaryGatewayProfile,
AssistantExecutionTarget.singleAgent => throw StateError(
'Single Agent target has no gateway profile.',
),
};
}
) => settings.primaryGatewayProfile;
int gatewayProfileIndexForExecutionTargetInternal(
AssistantExecutionTarget target,
) {
return switch (target) {
AssistantExecutionTarget.gateway => kGatewayRemoteProfileIndex,
AssistantExecutionTarget.singleAgent => throw StateError(
'Single Agent target has no gateway profile index.',
),
};
}
) => kGatewayRemoteProfileIndex;
}
String _sanitizeArtifactRelativePathInternal(String raw) {

View File

@ -296,7 +296,6 @@ extension AppControllerDesktopSettings on AppController {
aiGatewayStreamingClientsInternal.clear();
aiGatewayPendingSessionKeysInternal.clear();
aiGatewayAbortedSessionKeysInternal.clear();
singleAgentExternalCliPendingSessionKeysInternal.clear();
assistantThreadTurnQueuesInternal.clear();
multiAgentRunPendingInternal = false;
initializeAssistantThreadContext(
@ -305,7 +304,6 @@ extension AppControllerDesktopSettings on AppController {
currentSettings.assistantExecutionTarget,
),
messageViewMode: AssistantMessageViewMode.rendered,
singleAgentProvider: SingleAgentProvider.unspecified,
);
await setCurrentAssistantSessionKeyInternal(
'main',

View File

@ -283,13 +283,12 @@ extension AppControllerDesktopSettingsRuntime on AppController {
String tokenOverride = '',
String passwordOverride = '',
}) async {
if (executionTarget == AssistantExecutionTarget.singleAgent ||
profile.mode == RuntimeConnectionMode.unconfigured) {
if (profile.mode == RuntimeConnectionMode.unconfigured) {
return (
state: 'inactive',
message: appText(
'当前模式使用单机智能体,不建立 OpenClaw Gateway 会话',
'The current mode uses Single Agent and does not open an OpenClaw Gateway session.',
'当前未配置可用 Gateway 连接',
'No active gateway connection is configured.',
),
endpoint: '',
);
@ -408,8 +407,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
}
await refreshAcpCapabilitiesInternal(forceRefresh: true);
await refreshSingleAgentCapabilitiesInternal(forceRefresh: true);
await runtimeCoordinatorInternal.configureCodexForGateway(
gatewayUrl: gatewayUrl,
apiKey: apiKey,
@ -527,7 +524,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
settings.launchAtLogin,
);
registerCodexExternalProviderInternal();
await refreshSingleAgentCapabilitiesInternal();
await refreshAcpCapabilitiesInternal(persistMountTargets: true);
if (disposedInternal) {
return;
@ -550,9 +546,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
await ensureActiveAssistantThreadInternal();
await ensureDesktopTaskThreadBindingInternal(currentSessionKey);
unawaited(startupRefreshSharedSingleAgentLocalSkillsCacheInternal());
if (isSingleAgentMode) {
await refreshSingleAgentSkillsForSession(currentSessionKey);
}
runtimeEventsSubscriptionInternal = runtimeCoordinatorInternal
.gateway
.events
@ -561,7 +554,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
startupTarget,
);
final shouldAutoConnect =
startupTarget != AssistantExecutionTarget.singleAgent &&
startupProfile != null &&
startupProfile.useSetupCode &&
startupProfile.setupCode.trim().isNotEmpty;
@ -789,7 +781,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
if (previous.codeAgentRuntimeMode != current.codeAgentRuntimeMode) {
registerCodexExternalProviderInternal();
}
unawaited(refreshSingleAgentCapabilitiesInternal().catchError((_) {}));
if (previous.linuxDesktop.toJson().toString() !=
current.linuxDesktop.toJson().toString() ||
previous.launchAtLogin != current.launchAtLogin) {
@ -806,20 +797,12 @@ extension AppControllerDesktopSettingsRuntime on AppController {
if (disposedInternal) {
return;
}
if (assistantExecutionTargetForSession(currentSessionKey) ==
AssistantExecutionTarget.singleAgent) {
await refreshSingleAgentSkillsForSession(currentSessionKey);
}
}
if (previous.workspacePath != current.workspacePath) {
await ensureDesktopTaskThreadBindingInternal(currentSessionKey);
if (disposedInternal) {
return;
}
if (assistantExecutionTargetForSession(currentSessionKey) ==
AssistantExecutionTarget.singleAgent) {
await refreshSingleAgentSkillsForSession(currentSessionKey);
}
}
if (refreshAfterSave) {
recomputeTasksInternal();
@ -855,9 +838,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
sessionKey: sessionKey,
persistDefaultSelection: false,
);
if (target == AssistantExecutionTarget.singleAgent) {
await refreshSingleAgentSkillsForSession(sessionKey);
}
recomputeTasksInternal();
notifyIfActiveInternal();
}

View File

@ -1,25 +1,3 @@
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_single_agent_go_task_flow.dart';
import 'app_controller_desktop_single_agent_status_messages.dart';
import '../runtime/runtime_models.dart';
extension AppControllerDesktopSingleAgent on AppController {
Future<void> sendSingleAgentMessageInternal(
String message, {
required String thinking,
required List<GatewayChatAttachmentPayload> attachments,
required List<CollaborationAttachment> localAttachments,
}) {
return sendSingleAgentMessageDesktopGoTaskFlowInternal(
this,
message,
thinking: thinking,
attachments: attachments,
localAttachments: localAttachments,
);
}
GatewayChatMessage assistantErrorMessageInternal(String text) {
return assistantErrorMessageSingleAgentDesktopInternal(this, text);
}
}
extension AppControllerDesktopSingleAgent on AppController {}

View File

@ -1,329 +0,0 @@
// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'package:flutter/material.dart';
import '../i18n/app_language.dart';
import '../models/app_models.dart';
import '../runtime/desktop_thread_artifact_sync.dart';
import '../runtime/go_task_service_client.dart';
import '../runtime/runtime_models.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_external_acp_routing.dart';
import 'app_controller_desktop_runtime_helpers.dart';
import 'app_controller_desktop_single_agent_status_messages.dart';
import 'app_controller_desktop_thread_sessions.dart';
import 'app_controller_desktop_thread_storage.dart';
import 'app_controller_desktop_skill_permissions.dart';
Future<void> sendSingleAgentMessageDesktopGoTaskFlowInternal(
AppController controller,
String message, {
required String thinking,
required List<GatewayChatAttachmentPayload> attachments,
required List<CollaborationAttachment> localAttachments,
}) async {
final sessionKey = controller.normalizedAssistantSessionKeyInternal(
controller.sessionsControllerInternal.currentSessionKey,
);
final trimmed = message.trim();
if (trimmed.isEmpty && attachments.isEmpty) {
return;
}
await controller.enqueueThreadTurnInternal<void>(sessionKey, () async {
final sessionTarget = controller.assistantExecutionTargetForSession(
sessionKey,
);
final userText = trimmed.isEmpty ? 'See attached.' : trimmed;
controller.appendAssistantThreadMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'user',
text: userText,
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: null,
stopReason: null,
pending: false,
error: false,
),
);
controller.aiGatewayPendingSessionKeysInternal.add(sessionKey);
controller.recomputeTasksInternal();
controller.notifyIfActiveInternal();
try {
final routing = controller.buildExternalAcpRoutingForSessionInternal(
sessionKey,
);
final selection = controller.singleAgentProviderForSession(sessionKey);
final preflightWorkingDirectory = controller
.resolveSingleAgentWorkingDirectoryForSessionInternal(sessionKey);
if (preflightWorkingDirectory == null ||
preflightWorkingDirectory.trim().isEmpty) {
final error = StateError(
appText(
'当前线程缺少可运行的工作路径,无法启动单机智能体。',
'This thread does not have a runnable workspace path, so Single Agent cannot start.',
),
);
controller.appendAssistantThreadMessageInternal(
sessionKey,
assistantErrorMessageSingleAgentDesktopInternal(
controller,
error.message,
),
);
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 resolvedProviderId = routingResolution.resolvedProviderId.trim();
final resolvedProvider = resolvedProviderId.isEmpty
? null
: controller.bridgeProviderForId(resolvedProviderId) ??
SingleAgentProviderCopy.fromJsonValue(resolvedProviderId);
final effectiveProvider = resolvedProvider ?? selection;
controller.upsertTaskThreadInternal(
sessionKey,
latestResolvedProviderId: resolvedProviderId,
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
final unavailableReason = routingResolution.unavailable
? singleAgentUnavailableLabelDesktopInternal(
controller,
sessionKey,
routingResolution.unavailableMessage,
)
: resolvedProviderId.isEmpty && effectiveProvider.isUnspecified
? singleAgentUnavailableLabelDesktopInternal(
controller,
sessionKey,
appText(
'Bridge 当前没有广告可用 Provider。',
'The bridge is not advertising any available providers.',
),
)
: null;
if (unavailableReason != null) {
controller.upsertTaskThreadInternal(
sessionKey,
lifecycleStatus: 'ready',
lastRunAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
lastResultCode: 'error',
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
controller.appendAssistantThreadMessageInternal(
sessionKey,
assistantErrorMessageSingleAgentDesktopInternal(
controller,
unavailableReason,
),
);
return;
}
if (!effectiveProvider.isUnspecified) {
appendSingleAgentRuntimeStatusDesktopInternal(
controller,
sessionKey,
effectiveProvider,
);
}
final workingDirectory = controller
.resolveSingleAgentWorkingDirectoryForSessionInternal(
sessionKey,
provider: effectiveProvider.isUnspecified
? null
: effectiveProvider,
);
final resolvedWorkingDirectory =
workingDirectory == null || workingDirectory.trim().isEmpty
? preflightWorkingDirectory
: workingDirectory;
final selectedSkills = controller
.assistantSelectedSkillsForSession(sessionKey)
.map((item) => item.label.trim().isNotEmpty ? item.label : item.key)
.where((item) => item.trim().isNotEmpty)
.toList(growable: false);
final result = await controller.goTaskServiceClientInternal.executeTask(
GoTaskServiceRequest(
sessionId: sessionKey,
threadId: sessionKey,
target: AssistantExecutionTarget.singleAgent,
prompt: message,
workingDirectory: resolvedWorkingDirectory,
model: controller.assistantModelForSession(sessionKey),
thinking: thinking,
selectedSkills: selectedSkills,
inlineAttachments: attachments,
localAttachments: localAttachments,
agentId: '',
metadata: const <String, dynamic>{},
routing: routing,
routingHint: 'single-agent',
provider: effectiveProvider,
remoteWorkingDirectoryHint:
controller
.requireTaskThreadForSessionInternal(sessionKey)
.lastRemoteWorkingDirectory ??
'',
),
onUpdate: (update) {
if (update.isDelta) {
controller.appendAiGatewayStreamingTextInternal(
sessionKey,
update.text,
);
controller.notifyIfActiveInternal();
}
},
);
await _applySingleAgentGoTaskResultDesktopInternal(
controller,
sessionKey: sessionKey,
sessionTarget: sessionTarget,
message: message,
thinking: thinking,
attachments: attachments,
result: result,
);
} catch (error) {
controller.clearAiGatewayStreamingTextInternal(sessionKey);
controller.upsertTaskThreadInternal(
sessionKey,
lifecycleStatus: 'ready',
lastRunAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
lastResultCode: 'error',
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
controller.appendAssistantThreadMessageInternal(
sessionKey,
assistantErrorMessageSingleAgentDesktopInternal(
controller,
controller.gatewayExecutionErrorLabelInternal(
error,
target: sessionTarget,
),
),
);
} finally {
controller.clearAiGatewayStreamingTextInternal(sessionKey);
controller.aiGatewayPendingSessionKeysInternal.remove(sessionKey);
controller.recomputeTasksInternal();
controller.notifyIfActiveInternal();
}
});
}
Future<void> _applySingleAgentGoTaskResultDesktopInternal(
AppController controller, {
required String sessionKey,
required AssistantExecutionTarget sessionTarget,
required String message,
required String thinking,
required List<GatewayChatAttachmentPayload> attachments,
required GoTaskServiceResult result,
}) async {
final resolvedRuntimeModel = result.resolvedModel.trim();
final resolvedGatewayEntryState = goTaskServiceGatewayEntryState(
requestedTarget: sessionTarget,
result: result,
);
controller.upsertTaskThreadInternal(
sessionKey,
gatewayEntryState: resolvedGatewayEntryState,
latestResolvedRuntimeModel: resolvedRuntimeModel,
latestResolvedProviderId: result.resolvedProviderId,
lifecycleStatus: 'ready',
lastRunAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
lastResultCode: result.success ? 'success' : 'error',
lastRemoteWorkingDirectory: result.remoteWorkingDirectory.trim().isEmpty
? null
: result.remoteWorkingDirectory.trim(),
lastRemoteWorkspaceRefKind: result.remoteWorkspaceRefKind,
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
await _persistSingleAgentArtifactsDesktopInternal(
controller,
sessionKey,
result,
);
controller.clearAiGatewayStreamingTextInternal(sessionKey);
if (!result.success) {
controller.appendAssistantThreadMessageInternal(
sessionKey,
assistantErrorMessageSingleAgentDesktopInternal(
controller,
appText(
'GoTaskService 执行失败:${result.errorMessage}',
'GoTaskService execution failed: ${result.errorMessage}',
),
),
);
return;
}
if (result.message.trim().isEmpty) {
controller.appendAssistantThreadMessageInternal(
sessionKey,
assistantErrorMessageSingleAgentDesktopInternal(
controller,
appText(
'GoTaskService 没有返回可显示的输出。',
'GoTaskService returned no displayable output.',
),
),
);
return;
}
controller.appendAssistantThreadMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'assistant',
text: result.message,
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: null,
stopReason: null,
pending: false,
error: false,
),
);
}
Future<void> _persistSingleAgentArtifactsDesktopInternal(
AppController controller,
String sessionKey,
GoTaskServiceResult result,
) => controller.persistGoTaskArtifactsForSessionInternal(sessionKey, result);

View File

@ -1,116 +0,0 @@
// ignore_for_file: unused_import, unnecessary_import
import 'package:flutter/material.dart';
import '../i18n/app_language.dart';
import '../runtime/runtime_models.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_runtime_helpers.dart';
import 'app_controller_desktop_thread_sessions.dart';
import 'app_controller_desktop_thread_storage.dart';
GatewayChatMessage assistantErrorMessageSingleAgentDesktopInternal(
AppController controller,
String text,
) {
return GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'assistant',
text: text,
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: null,
stopReason: null,
pending: false,
error: true,
);
}
String? singleAgentRuntimeDebugToolNameDesktopInternal(
AppController controller,
String label,
) {
if (!controller.showsSingleAgentRuntimeDebugMessagesInternal) {
return null;
}
final trimmed = label.trim();
if (trimmed.isEmpty) {
return null;
}
return trimmed;
}
void appendSingleAgentRuntimeStatusDesktopInternal(
AppController controller,
String sessionKey,
SingleAgentProvider provider,
) {
if (!controller.showsSingleAgentRuntimeDebugMessagesInternal) {
return;
}
controller.appendAssistantThreadMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'assistant',
text: appText(
'单机智能体已切换到 ${provider.label} 执行当前任务。',
'Single Agent is using ${provider.label} for this task.',
),
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: provider.label,
stopReason: null,
pending: false,
error: false,
),
);
}
String singleAgentUnavailableLabelDesktopInternal(
AppController controller,
String sessionKey,
String? reason,
) {
final normalizedSessionKey = controller.normalizedAssistantSessionKeyInternal(
sessionKey,
);
final detail = reason?.trim() ?? '';
final selection =
controller.currentSingleAgentResolvedProvider ??
controller.singleAgentProviderForSession(normalizedSessionKey);
if (controller.singleAgentShouldSuggestAcpSwitchForSession(
normalizedSessionKey,
)) {
return detail.isEmpty
? appText(
'当前线程固定为 ${selection.label},但它在这台设备上不可用。检测到其他 Bridge Provider 时不会自动改线,请手动切到可用 Provider。',
'This thread is pinned to ${selection.label}, but it is unavailable on this device. XWorkmate will not reroute to another bridge provider automatically. Switch to an available provider manually.',
)
: appText(
'当前线程固定为 ${selection.label}$detail 检测到其他 Bridge Provider 时不会自动改线,请手动切到可用 Provider。',
'This thread is pinned to ${selection.label}: $detail XWorkmate will not reroute to another bridge provider automatically. Switch to an available provider manually.',
);
}
if (controller.singleAgentNeedsBridgeProviderForSession(
normalizedSessionKey,
)) {
return detail.isEmpty
? appText(
'Bridge 当前没有可用 Provider。',
'The bridge does not currently advertise any available providers.',
)
: appText(
'$detail Bridge 当前没有可用 Provider。',
'$detail The bridge does not currently advertise any available providers.',
);
}
return detail.isEmpty
? appText(
'当前线程的 Bridge Provider 尚未就绪。',
'The bridge provider for this thread is not ready yet.',
)
: appText(
'当前线程的 Bridge Provider 尚未就绪:$detail',
'The bridge provider for this thread is not ready yet: $detail',
);
}

View File

@ -96,11 +96,6 @@ extension AppControllerDesktopSkillPermissions on AppController {
if (disposedInternal) {
return;
}
if (assistantExecutionTargetForSession(currentSessionKey) ==
AssistantExecutionTarget.singleAgent) {
await refreshSingleAgentSkillsForSession(currentSessionKey);
return;
}
notifyIfActiveInternal();
}
@ -255,10 +250,8 @@ extension AppControllerDesktopSkillPermissions on AppController {
final nextExecutionTarget =
executionTarget ??
switch (existing?.executionBinding.executionMode) {
ThreadExecutionMode.localAgent =>
AssistantExecutionTarget.singleAgent,
ThreadExecutionMode.gateway => AssistantExecutionTarget.gateway,
null => AssistantExecutionTarget.singleAgent,
null => AssistantExecutionTarget.gateway,
};
final nextImportedSkills =
importedSkills ??
@ -286,47 +279,30 @@ extension AppControllerDesktopSkillPermissions on AppController {
final nextWorkspaceBinding =
workspaceBinding ??
existing?.workspaceBinding ??
(nextExecutionTarget == AssistantExecutionTarget.singleAgent
? buildDesktopWorkspaceBindingInternal(
normalizedSessionKey,
executionTarget: nextExecutionTarget,
ownerScope: nextOwnerScope,
existingBinding: null,
)
: null);
if (nextWorkspaceBinding == null || !nextWorkspaceBinding.isComplete) {
buildDesktopWorkspaceBindingInternal(
normalizedSessionKey,
executionTarget: nextExecutionTarget,
ownerScope: nextOwnerScope,
existingBinding: null,
);
if (!nextWorkspaceBinding.isComplete) {
throw StateError(
'TaskThread $normalizedSessionKey is missing a complete workspaceBinding.',
);
}
final nextProvider =
nextExecutionTarget == AssistantExecutionTarget.singleAgent
? (singleAgentProvider ??
SingleAgentProviderCopy.fromJsonValue(
executionBinding?.providerId ??
existing?.executionBinding.providerId,
))
: SingleAgentProvider.unspecified;
final nextProviderSource =
nextExecutionTarget == AssistantExecutionTarget.singleAgent
? (singleAgentProviderSource ??
existing?.executionBinding.providerSource)
: ThreadSelectionSource.inherited;
final nextProvider = SingleAgentProvider.unspecified;
const nextProviderSource = ThreadSelectionSource.inherited;
final nextExecutionBinding =
(executionBinding ??
existing?.executionBinding ??
ExecutionBinding(
executionMode: ThreadExecutionMode.localAgent,
executionMode: ThreadExecutionMode.gateway,
executorId: nextProvider.providerId,
providerId: nextProvider.providerId,
endpointId: '',
))
.copyWith(
executionMode: switch (nextExecutionTarget) {
AssistantExecutionTarget.singleAgent =>
ThreadExecutionMode.localAgent,
AssistantExecutionTarget.gateway => ThreadExecutionMode.gateway,
},
executionMode: ThreadExecutionMode.gateway,
executorId: nextProvider.providerId,
providerId: nextProvider.providerId,
executionModeSource:

View File

@ -38,7 +38,6 @@ import 'app_controller_desktop_navigation.dart';
import 'app_controller_desktop_gateway.dart';
import 'app_controller_desktop_settings.dart';
import 'app_controller_desktop_external_acp_routing.dart';
import 'app_controller_desktop_single_agent.dart';
import 'app_controller_desktop_thread_binding.dart';
import 'app_controller_desktop_thread_sessions.dart';
import 'app_controller_desktop_workspace_execution.dart';
@ -49,6 +48,20 @@ import 'app_controller_desktop_runtime_helpers.dart';
// ignore_for_file: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member
extension AppControllerDesktopThreadActions on AppController {
GatewayChatMessage assistantErrorMessageInternal(String text) {
return GatewayChatMessage(
id: nextLocalMessageIdInternal(),
role: 'assistant',
text: text,
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: null,
stopReason: null,
pending: false,
error: true,
);
}
bool assistantSessionHasPendingRun(String sessionKey) {
final normalized = normalizedAssistantSessionKeyInternal(sessionKey);
return aiGatewayPendingSessionKeysInternal.contains(normalized) ||
@ -59,23 +72,8 @@ extension AppControllerDesktopThreadActions on AppController {
));
}
Future<void> sendSingleAgentMessageInternal(
String message, {
required String thinking,
required List<GatewayChatAttachmentPayload> attachments,
required List<CollaborationAttachment> localAttachments,
}) => AppControllerDesktopSingleAgent(this).sendSingleAgentMessageInternal(
message,
thinking: thinking,
attachments: attachments,
localAttachments: localAttachments,
);
Future<void> connectSavedGateway() async {
final target = currentAssistantExecutionTarget;
if (target == AssistantExecutionTarget.singleAgent) {
return;
}
await AppControllerDesktopGateway(this).connectProfileInternal(
gatewayProfileForAssistantExecutionTargetInternal(target),
profileIndex: gatewayProfileIndexForExecutionTargetInternal(target),
@ -154,20 +152,17 @@ extension AppControllerDesktopThreadActions on AppController {
Future<void> selectAgent(String? agentId) async {
agentsControllerInternal.selectAgent(agentId);
if (currentAssistantExecutionTarget !=
AssistantExecutionTarget.singleAgent) {
final target = currentAssistantExecutionTarget;
final nextProfile = gatewayProfileForAssistantExecutionTargetInternal(
target,
).copyWith(selectedAgentId: agentsControllerInternal.selectedAgentId);
await AppControllerDesktopSettings(this).saveSettings(
settings.copyWithGatewayProfileAt(
gatewayProfileIndexForExecutionTargetInternal(target),
nextProfile,
),
refreshAfterSave: false,
);
}
final target = currentAssistantExecutionTarget;
final nextProfile = gatewayProfileForAssistantExecutionTargetInternal(
target,
).copyWith(selectedAgentId: agentsControllerInternal.selectedAgentId);
await AppControllerDesktopSettings(this).saveSettings(
settings.copyWithGatewayProfileAt(
gatewayProfileIndexForExecutionTargetInternal(target),
nextProfile,
),
refreshAfterSave: false,
);
sessionsControllerInternal.configure(
mainSessionKey: runtimeInternal.snapshot.mainSessionKey ?? 'main',
selectedAgentId: agentsControllerInternal.selectedAgentId,
@ -205,9 +200,7 @@ extension AppControllerDesktopThreadActions on AppController {
final nextTarget = assistantExecutionTargetForSession(nextSessionKey);
final nextViewMode = assistantMessageViewModeForSession(nextSessionKey);
if (!isSingleAgentMode) {
preserveGatewayHistoryForSessionInternal(previousSessionKey);
}
preserveGatewayHistoryForSessionInternal(previousSessionKey);
await setCurrentAssistantSessionKeyInternal(nextSessionKey);
upsertTaskThreadInternal(
@ -226,9 +219,6 @@ extension AppControllerDesktopThreadActions on AppController {
persistDefaultSelection: false,
preserveGatewayHistoryForSelectedThread: false,
);
if (nextTarget == AssistantExecutionTarget.singleAgent) {
await refreshSingleAgentSkillsForSession(nextSessionKey);
}
recomputeTasksInternal();
}
@ -267,17 +257,6 @@ extension AppControllerDesktopThreadActions on AppController {
recomputeTasksInternal();
throw error;
}
if (currentTarget == AssistantExecutionTarget.singleAgent) {
await sendSingleAgentMessageInternal(
message,
thinking: thinking,
attachments: attachments,
localAttachments: localAttachments,
);
await flushAssistantThreadPersistenceInternal();
recomputeTasksInternal();
return;
}
await enqueueThreadTurnInternal<void>(
normalizedAssistantSessionKeyInternal(currentSessionKey),
() async {
@ -456,32 +435,6 @@ extension AppControllerDesktopThreadActions on AppController {
notifyIfActiveInternal();
return;
}
if (isSingleAgentMode) {
final sessionKey = normalizedAssistantSessionKeyInternal(
sessionsControllerInternal.currentSessionKey,
);
if (aiGatewayPendingSessionKeysInternal.contains(sessionKey)) {
await goTaskServiceClientInternal.cancelTask(
route: GoTaskServiceRoute.externalAcpSingle,
target: AssistantExecutionTarget.singleAgent,
sessionId: sessionKey,
threadId: sessionKey,
);
aiGatewayPendingSessionKeysInternal.remove(sessionKey);
clearAiGatewayStreamingTextInternal(sessionKey);
upsertTaskThreadInternal(
sessionKey,
lifecycleStatus: 'ready',
lastRunAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
lastResultCode: 'aborted',
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
recomputeTasksInternal();
notifyIfActiveInternal();
return;
}
return;
}
final sessionKey = normalizedAssistantSessionKeyInternal(
sessionsControllerInternal.currentSessionKey,
);

View File

@ -36,7 +36,6 @@ import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
import 'app_controller_desktop_gateway.dart';
import 'app_controller_desktop_settings.dart';
import 'app_controller_desktop_single_agent.dart';
import 'app_controller_desktop_thread_sessions.dart';
import 'app_controller_desktop_thread_actions.dart';
import 'app_controller_desktop_workspace_execution.dart';
@ -48,12 +47,10 @@ import 'app_controller_desktop_runtime_helpers.dart';
class DesktopThreadBindingSnapshotInternal {
const DesktopThreadBindingSnapshotInternal({
required this.executionTarget,
required this.selectedSingleAgentProvider,
required this.record,
});
final AssistantExecutionTarget executionTarget;
final SingleAgentProvider selectedSingleAgentProvider;
final TaskThread? record;
}
@ -70,12 +67,8 @@ resolveDesktopThreadBindingSnapshotInternal({
: assistantExecutionTargetFromExecutionMode(
latestRecord.executionBinding.executionMode,
));
final selectedProvider = SingleAgentProviderCopy.fromJsonValue(
latestRecord?.executionBinding.providerId ?? '',
);
return DesktopThreadBindingSnapshotInternal(
executionTarget: resolvedExecutionTarget,
selectedSingleAgentProvider: selectedProvider,
record: latestRecord,
);
}
@ -196,33 +189,6 @@ extension AppControllerDesktopThreadBinding on AppController {
required ThreadOwnerScope ownerScope,
WorkspaceBinding? existingBinding,
}) {
if (executionTarget == AssistantExecutionTarget.singleAgent) {
if (existingBinding != null &&
existingBinding.workspaceKind == WorkspaceKind.localFs) {
final existingPath = existingBinding.workspacePath.trim();
if (existingPath.isNotEmpty &&
ensureLocalWorkspaceDirectoryInternal(existingPath)) {
// A task thread owns one stable local workingDirectory for its
// lifetime. Do not silently rebind it after the initial allocation.
return existingBinding.copyWith(
displayPath: existingBinding.workspacePath,
);
}
}
final localPath = localThreadWorkspacePathInternal(sessionKey);
if (localPath.isEmpty) {
throw StateError(
'Local executable thread $sessionKey requires a writable local workspace.',
);
}
return WorkspaceBinding(
workspaceId: normalizedAssistantSessionKeyInternal(sessionKey),
workspaceKind: WorkspaceKind.localFs,
workspacePath: localPath,
displayPath: localPath,
writable: true,
);
}
final remotePath = remoteThreadWorkspacePathInternal(
sessionKey,
ownerScope,
@ -251,34 +217,21 @@ extension AppControllerDesktopThreadBinding on AppController {
ExecutionBinding buildDesktopExecutionBindingInternal({
required AssistantExecutionTarget executionTarget,
required SingleAgentProvider singleAgentProvider,
ExecutionBinding? existingBinding,
}) {
final selectedProviderId =
executionTarget == AssistantExecutionTarget.singleAgent
? settings
.sanitizeSingleAgentProviderSelection(singleAgentProvider)
.providerId
: kCanonicalGatewayProviderId;
const selectedProviderId = kCanonicalGatewayProviderId;
return (existingBinding ??
ExecutionBinding(
executionMode: ThreadExecutionMode.localAgent,
executionMode: ThreadExecutionMode.gateway,
executorId: selectedProviderId,
providerId: selectedProviderId,
endpointId: '',
))
.copyWith(
executionMode: switch (executionTarget) {
AssistantExecutionTarget.singleAgent =>
ThreadExecutionMode.localAgent,
AssistantExecutionTarget.gateway => ThreadExecutionMode.gateway,
},
executionMode: ThreadExecutionMode.gateway,
executorId: selectedProviderId,
providerId: selectedProviderId,
providerSource:
executionTarget == AssistantExecutionTarget.singleAgent
? existingBinding?.providerSource
: ThreadSelectionSource.inherited,
providerSource: ThreadSelectionSource.inherited,
);
}
@ -310,7 +263,6 @@ extension AppControllerDesktopThreadBinding on AppController {
workspaceBinding: workspaceBinding,
executionBinding: buildDesktopExecutionBindingInternal(
executionTarget: snapshot.executionTarget,
singleAgentProvider: snapshot.selectedSingleAgentProvider,
existingBinding: snapshot.record?.executionBinding,
),
lifecycleState:

View File

@ -37,7 +37,6 @@ import 'app_controller_desktop_navigation.dart';
import 'app_controller_desktop_gateway.dart';
import 'app_controller_desktop_settings.dart';
import 'app_controller_desktop_single_agent.dart';
import 'app_controller_desktop_single_agent_status_messages.dart';
import 'app_controller_desktop_thread_binding.dart';
import 'app_controller_desktop_thread_actions.dart';
import 'app_controller_desktop_workspace_execution.dart';
@ -109,13 +108,6 @@ extension AppControllerDesktopThreadSessions on AppController {
}
int assistantSkillCountForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
final target = assistantExecutionTargetForSession(normalizedSessionKey);
if (target == AssistantExecutionTarget.singleAgent) {
return assistantImportedSkillsForSession(normalizedSessionKey).length;
}
return skills.length;
}
@ -153,18 +145,6 @@ extension AppControllerDesktopThreadSessions on AppController {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
final target = assistantExecutionTargetForSession(normalizedSessionKey);
final latestResolvedModel =
taskThreadForSessionInternal(
normalizedSessionKey,
)?.latestResolvedRuntimeModel.trim() ??
'';
if (target == AssistantExecutionTarget.singleAgent) {
if (latestResolvedModel.isNotEmpty) {
return latestResolvedModel;
}
return singleAgentRuntimeModelForSession(normalizedSessionKey);
}
final recordModel =
assistantThreadRecordsInternal[normalizedSessionKey]?.assistantModelId
.trim() ??
@ -176,7 +156,9 @@ extension AppControllerDesktopThreadSessions on AppController {
(availableChoices.isEmpty || availableChoices.contains(recordModel))) {
return recordModel;
}
return resolvedAssistantModelForTargetInternal(target);
return resolvedAssistantModelForTargetInternal(
AssistantExecutionTarget.gateway,
);
}
String assistantWorkspacePathForSession(String sessionKey) {
@ -194,7 +176,7 @@ extension AppControllerDesktopThreadSessions on AppController {
normalizedAssistantSessionKeyInternal(sessionKey),
);
if (record == null) {
return WorkspaceRefKind.localPath;
return WorkspaceRefKind.remotePath;
}
return record.workspaceBinding.workspaceKind == WorkspaceKind.localFs
? WorkspaceRefKind.localPath
@ -237,177 +219,8 @@ extension AppControllerDesktopThreadSessions on AppController {
);
}
SingleAgentProvider singleAgentProviderForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
final selectedProvider = SingleAgentProviderCopy.fromJsonValue(
taskThreadForSessionInternal(
normalizedSessionKey,
)?.executionBinding.providerId ??
'',
);
if (!selectedProvider.isUnspecified) {
return selectedProvider;
}
final options = singleAgentProviderOptions;
return options.isEmpty ? SingleAgentProvider.unspecified : options.first;
}
SingleAgentProvider get currentSingleAgentProvider =>
singleAgentProviderForSession(currentSessionKey);
SingleAgentProvider? singleAgentResolvedProviderForSession(
String sessionKey,
) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
final record = taskThreadForSessionInternal(normalizedSessionKey);
final resolvedProviderId = record?.latestResolvedProviderId.trim() ?? '';
if (resolvedProviderId.isNotEmpty) {
return bridgeProviderForId(resolvedProviderId) ??
SingleAgentProviderCopy.fromJsonValue(resolvedProviderId);
}
return null;
}
SingleAgentProvider? get currentSingleAgentResolvedProvider =>
singleAgentResolvedProviderForSession(currentSessionKey);
SingleAgentProvider? singleAgentCatalogProviderForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
final selection = singleAgentProviderForSession(normalizedSessionKey);
if (selection.isUnspecified) {
return null;
}
return bridgeProviderForId(selection.providerId);
}
SingleAgentProvider? get currentSingleAgentCatalogProvider =>
singleAgentCatalogProviderForSession(currentSessionKey);
bool singleAgentNeedsBridgeProviderForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
if (assistantExecutionTargetForSession(normalizedSessionKey) !=
AssistantExecutionTarget.singleAgent) {
return false;
}
if (resolveExternalAcpEndpointForTargetInternal(
AssistantExecutionTarget.singleAgent,
) ==
null) {
return false;
}
return bridgeProviderCatalog.isEmpty;
}
bool get currentSingleAgentNeedsBridgeProvider =>
singleAgentNeedsBridgeProviderForSession(currentSessionKey);
bool singleAgentShouldSuggestAcpSwitchForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
if (assistantExecutionTargetForSession(normalizedSessionKey) !=
AssistantExecutionTarget.singleAgent) {
return false;
}
if (resolveExternalAcpEndpointForTargetInternal(
AssistantExecutionTarget.singleAgent,
) ==
null) {
return false;
}
final selection = singleAgentProviderForSession(normalizedSessionKey);
if (selection.isUnspecified) {
return false;
}
final selectedProvider = bridgeProviderForId(selection.providerId);
return selectedProvider == null && bridgeProviderCatalog.isNotEmpty;
}
bool get currentSingleAgentShouldSuggestAcpSwitch =>
singleAgentShouldSuggestAcpSwitchForSession(currentSessionKey);
String singleAgentRuntimeModelForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
return taskThreadForSessionInternal(
normalizedSessionKey,
)?.latestResolvedRuntimeModel.trim() ??
'';
}
String get currentSingleAgentRuntimeModel =>
singleAgentRuntimeModelForSession(currentSessionKey);
String singleAgentModelDisplayLabelForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
final runtimeModel = singleAgentRuntimeModelForSession(
normalizedSessionKey,
);
if (runtimeModel.isNotEmpty) {
return runtimeModel;
}
final model = assistantModelForSession(normalizedSessionKey);
if (model.isNotEmpty) {
return model;
}
final provider =
singleAgentResolvedProviderForSession(normalizedSessionKey) ??
singleAgentCatalogProviderForSession(normalizedSessionKey) ??
singleAgentProviderForSession(normalizedSessionKey);
return appText(
'请先配置 ${provider.label} 模型',
'Configure ${provider.label} model',
);
}
String get currentSingleAgentModelDisplayLabel =>
singleAgentModelDisplayLabelForSession(currentSessionKey);
bool singleAgentShouldShowModelControlForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
if (assistantExecutionTargetForSession(normalizedSessionKey) !=
AssistantExecutionTarget.singleAgent) {
return true;
}
return singleAgentRuntimeModelForSession(normalizedSessionKey).isNotEmpty;
}
bool get currentSingleAgentShouldShowModelControl =>
singleAgentShouldShowModelControlForSession(currentSessionKey);
List<SingleAgentProvider> get singleAgentProviderOptions =>
bridgeProviderCatalog;
String get assistantConversationOwnerLabel {
if (!isSingleAgentMode) {
return activeAgentName;
}
final resolvedProvider = currentSingleAgentResolvedProvider;
if (resolvedProvider != null) {
return resolvedProvider.label;
}
final catalogProvider = currentSingleAgentCatalogProvider;
if (catalogProvider != null) {
return catalogProvider.label;
}
final provider = currentSingleAgentProvider;
if (!provider.isUnspecified) {
return provider.label;
}
return appText('单机智能体', 'Single Agent');
return activeAgentName;
}
AssistantThreadConnectionState get currentAssistantConnectionState =>
@ -420,50 +233,6 @@ extension AppControllerDesktopThreadSessions on AppController {
sessionKey,
);
final target = assistantExecutionTargetForSession(normalizedSessionKey);
if (target == AssistantExecutionTarget.singleAgent) {
final primaryLabel = appText('Bridge', 'Bridge');
final provider = singleAgentProviderForSession(normalizedSessionKey);
final resolvedProvider = singleAgentResolvedProviderForSession(
normalizedSessionKey,
);
final catalogProvider = singleAgentCatalogProviderForSession(
normalizedSessionKey,
);
final model = assistantModelForSession(normalizedSessionKey);
final providerReady = catalogProvider != null;
final displayProvider = resolvedProvider ?? catalogProvider ?? provider;
final detail = providerReady
? joinConnectionPartsInternal(<String>[displayProvider.label, model])
: singleAgentShouldSuggestAcpSwitchForSession(normalizedSessionKey)
? appText(
'${provider.label} 当前不可用,请改成 Bridge 当前可用的 Provider。',
'${provider.label} is unavailable. Switch to a provider currently advertised by the bridge.',
)
: singleAgentNeedsBridgeProviderForSession(normalizedSessionKey)
? appText(
'Bridge 当前没有可用 Provider。',
'The bridge does not currently advertise any available providers.',
)
: appText(
'当前线程的 Bridge Provider 尚未就绪。',
'The bridge provider for this thread is not ready yet.',
);
return AssistantThreadConnectionState(
executionTarget: target,
status: providerReady
? RuntimeConnectionStatus.connected
: RuntimeConnectionStatus.offline,
primaryLabel: primaryLabel,
detailLabel: detail.isEmpty
? appText('未配置单机智能体', 'Single Agent is not configured')
: detail,
ready: providerReady,
pairingRequired: false,
gatewayTokenMissing: false,
lastError: null,
);
}
return resolveGatewayThreadConnectionStateInternal(
target: target,
connection: connection,
@ -534,14 +303,8 @@ extension AppControllerDesktopThreadSessions on AppController {
final sessionKey = normalizedAssistantSessionKeyInternal(
sessionsControllerInternal.currentSessionKey,
);
final items = List<GatewayChatMessage>.from(
isSingleAgentMode
? const <GatewayChatMessage>[]
: chatControllerInternal.messages,
);
final threadItems = isSingleAgentMode
? assistantThreadMessagesInternal[sessionKey]
: null;
final items = List<GatewayChatMessage>.from(chatControllerInternal.messages);
final threadItems = assistantThreadMessagesInternal[sessionKey];
if (threadItems != null && threadItems.isNotEmpty) {
items.addAll(threadItems);
}
@ -549,9 +312,7 @@ extension AppControllerDesktopThreadSessions on AppController {
if (localItems != null && localItems.isNotEmpty) {
items.addAll(localItems);
}
final streaming = isSingleAgentMode
? (aiGatewayStreamingTextBySessionInternal[sessionKey]?.trim() ?? '')
: (chatControllerInternal.streamingAssistantText?.trim() ?? '');
final streaming = chatControllerInternal.streamingAssistantText?.trim() ?? '';
if (streaming.isNotEmpty) {
items.add(
GatewayChatMessage(
@ -604,12 +365,7 @@ extension AppControllerDesktopThreadSessions on AppController {
WorkspaceRefKind defaultWorkspaceRefKindForTargetInternal(
AssistantExecutionTarget target,
) {
return switch (target) {
AssistantExecutionTarget.singleAgent => WorkspaceRefKind.localPath,
AssistantExecutionTarget.gateway => WorkspaceRefKind.remotePath,
};
}
) => WorkspaceRefKind.remotePath;
List<GatewaySessionSummary> assistantSessionsInternal() {
final byKey = <String, GatewaySessionSummary>{};
@ -663,7 +419,7 @@ AssistantExecutionTarget resolveAssistantExecutionTargetFromRecordsForTest(
}) {
final record = primaryRecord ?? fallbackRecord;
return record == null
? AssistantExecutionTarget.singleAgent
? AssistantExecutionTarget.gateway
: assistantExecutionTargetFromExecutionMode(
record.executionBinding.executionMode,
);

View File

@ -346,21 +346,6 @@ List<String> assistantModelChoicesForSessionThreadSessionInternal(
AppController controller,
String sessionKey,
) {
final normalizedSessionKey = normalizeAssistantSessionKeyThreadInternal(
sessionKey,
);
final target = controller.assistantExecutionTargetForSession(
normalizedSessionKey,
);
if (target == AssistantExecutionTarget.singleAgent) {
final runtimeModel = controller.singleAgentRuntimeModelForSession(
normalizedSessionKey,
);
if (runtimeModel.isNotEmpty) {
return <String>[runtimeModel];
}
return const <String>[];
}
final runtimeModels = connectedGatewayModelChoicesThreadSessionInternal(
controller,
);
@ -402,9 +387,6 @@ String resolvedDefaultModelThreadSessionInternal(AppController controller) {
bool canQuickConnectGatewayThreadSessionInternal(AppController controller) {
final target = controller.currentAssistantExecutionTarget;
if (target == AssistantExecutionTarget.singleAgent) {
return false;
}
final profile = controller.gatewayProfileForAssistantExecutionTargetInternal(
target,
);
@ -418,12 +400,7 @@ bool canQuickConnectGatewayThreadSessionInternal(AppController controller) {
if (profile.mode == RuntimeConnectionMode.local) {
return true;
}
final defaults = switch (target) {
AssistantExecutionTarget.singleAgent => GatewayConnectionProfile.emptySlot(
index: kGatewayRemoteProfileIndex,
),
AssistantExecutionTarget.gateway => GatewayConnectionProfile.defaults(),
};
final defaults = GatewayConnectionProfile.defaults();
return controller.hasStoredGatewayCredential ||
host != defaults.host ||
profile.port != defaults.port ||

View File

@ -62,10 +62,7 @@ extension AppControllerDesktopThreadStorage on AppController {
}
Future<void> ensureActiveAssistantThreadInternal() async {
if (!isSingleAgentMode ||
!isAssistantTaskArchived(
sessionsControllerInternal.currentSessionKey,
)) {
if (!isAssistantTaskArchived(sessionsControllerInternal.currentSessionKey)) {
return;
}
final fallback = assistantSessionSummariesInternal().firstWhere(
@ -219,14 +216,9 @@ extension AppControllerDesktopThreadStorage on AppController {
AssistantExecutionTarget sanitizeExecutionTargetInternal(
AssistantExecutionTarget? target,
) {
final sanitized = featuresFor(
hostUiFeaturePlatformInternal,
).sanitizeExecutionTarget(target);
return sanitized == AssistantExecutionTarget.singleAgent
? AssistantExecutionTarget.singleAgent
: AssistantExecutionTarget.gateway;
}
) => featuresFor(
hostUiFeaturePlatformInternal,
).sanitizeExecutionTarget(target);
AssistantExecutionTarget sanitizePersistedExecutionTargetInternal(
AssistantExecutionTarget? target,
@ -691,16 +683,11 @@ extension AppControllerDesktopThreadStorage on AppController {
record.executionBinding.executionMode,
),
);
final recordProvider =
recordExecutionTarget == AssistantExecutionTarget.singleAgent
? SingleAgentProviderCopy.fromJsonValue(
record.executionBinding.providerId,
)
: const SingleAgentProvider(
providerId: kCanonicalGatewayProviderId,
label: kCanonicalGatewayProviderLabel,
badge: 'OC',
);
const recordProvider = SingleAgentProvider(
providerId: kCanonicalGatewayProviderId,
label: kCanonicalGatewayProviderLabel,
badge: 'OC',
);
final workspaceBinding = record.workspaceBinding.copyWith(
workspaceId: sessionKey,
displayPath: record.workspaceKind == WorkspaceKind.localFs
@ -732,10 +719,7 @@ extension AppControllerDesktopThreadStorage on AppController {
),
executorId: recordProvider.providerId,
providerId: recordProvider.providerId,
providerSource:
recordExecutionTarget == AssistantExecutionTarget.singleAgent
? record.executionBinding.providerSource
: ThreadSelectionSource.inherited,
providerSource: ThreadSelectionSource.inherited,
),
lifecycleState: record.lifecycleState.copyWith(status: 'ready'),
);

View File

@ -36,7 +36,6 @@ import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
import 'app_controller_desktop_gateway.dart';
import 'app_controller_desktop_settings.dart';
import 'app_controller_desktop_single_agent.dart';
import 'app_controller_desktop_thread_binding.dart';
import 'app_controller_desktop_thread_sessions.dart';
import 'app_controller_desktop_thread_actions.dart';
@ -65,7 +64,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
sessionsControllerInternal.currentSessionKey,
executionTarget: resolvedTarget,
messageViewMode: currentAssistantMessageViewMode,
singleAgentProvider: currentSingleAgentProvider,
);
}
StateError? bindingError;
@ -96,11 +94,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
sessionKey: sessionsControllerInternal.currentSessionKey,
persistDefaultSelection: true,
);
if (resolvedTarget == AssistantExecutionTarget.singleAgent) {
await refreshSingleAgentSkillsForSession(
sessionsControllerInternal.currentSessionKey,
);
}
if (bindingError != null) {
debugPrint('setAssistantExecutionTarget binding fallback: $bindingError');
}
@ -108,43 +101,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
notifyIfActiveInternal();
}
Future<void> setSingleAgentProvider(SingleAgentProvider provider) async {
final sessionKey = normalizedAssistantSessionKeyInternal(currentSessionKey);
final sanitizedProvider = settings.sanitizeSingleAgentProviderSelection(
provider,
);
if (singleAgentProviderForSession(sessionKey) == sanitizedProvider) {
return;
}
if (!assistantThreadRecordsInternal.containsKey(sessionKey)) {
initializeAssistantThreadContext(
sessionKey,
executionTarget: assistantExecutionTargetForSession(sessionKey),
messageViewMode: assistantMessageViewModeForSession(sessionKey),
singleAgentProvider: currentSingleAgentProvider,
);
}
upsertTaskThreadInternal(
sessionKey,
singleAgentProvider: sanitizedProvider,
singleAgentProviderSource: ThreadSelectionSource.explicit,
latestResolvedRuntimeModel: '',
latestResolvedProviderId: '',
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
recomputeTasksInternal();
notifyIfActiveInternal();
if (assistantExecutionTargetForSession(sessionKey) ==
AssistantExecutionTarget.singleAgent) {
await refreshSingleAgentSkillsForSession(sessionKey);
}
unawaited(
refreshMultiAgentMounts(
sync: settings.multiAgent.autoSync,
).catchError((_) {}),
);
}
Future<void> setAssistantMessageViewMode(
AssistantMessageViewMode mode,
) async {
@ -159,7 +115,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
sessionKey,
executionTarget: assistantExecutionTargetForSession(sessionKey),
messageViewMode: assistantMessageViewModeForSession(sessionKey),
singleAgentProvider: singleAgentProviderForSession(sessionKey),
);
}
upsertTaskThreadInternal(
@ -194,14 +149,12 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
if (resolvedTarget != AssistantExecutionTarget.singleAgent) {
upsertTaskThreadInternal(
normalizedSessionKey,
latestResolvedRuntimeModel: '',
latestResolvedProviderId: '',
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
}
upsertTaskThreadInternal(
normalizedSessionKey,
latestResolvedRuntimeModel: '',
latestResolvedProviderId: '',
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
if (!matchesSessionKey(
normalizedSessionKey,
sessionsControllerInternal.currentSessionKey,
@ -216,26 +169,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
);
}
if (resolvedTarget == AssistantExecutionTarget.singleAgent) {
if (preserveGatewayHistoryForSelectedThread &&
runtimeInternal.isConnected) {
preserveGatewayHistoryForSessionInternal(normalizedSessionKey);
}
await ensureActiveAssistantThreadInternal();
if (runtimeInternal.isConnected) {
try {
await AppControllerDesktopGateway(this).disconnectGateway();
} catch (_) {
// Preserve the selected thread-bound target even when the active
// gateway session does not close cleanly on the first attempt.
}
} else {
chatControllerInternal.clear();
}
await setCurrentAssistantSessionKeyInternal(normalizedSessionKey);
return;
}
final targetProfile = gatewayProfileForAssistantExecutionTargetInternal(
resolvedTarget,
);
@ -300,9 +233,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
messageViewMode: assistantMessageViewModeForSession(
normalizedSessionKey,
),
singleAgentProvider: singleAgentProviderForSession(
normalizedSessionKey,
),
);
}
upsertTaskThreadInternal(
@ -328,7 +258,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
String title = '',
AssistantExecutionTarget? executionTarget,
AssistantMessageViewMode? messageViewMode,
SingleAgentProvider? singleAgentProvider,
}) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
@ -361,17 +290,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
messageViewMode:
messageViewMode ??
assistantMessageViewModeForSession(currentSessionKey),
singleAgentProvider:
singleAgentProvider ??
settings.sanitizeSingleAgentProviderSelection(
SingleAgentProviderCopy.fromJsonValue(
assistantThreadRecordsInternal[normalizedSessionKey]
?.executionBinding
.providerId ??
'',
),
),
singleAgentProviderSource: ThreadSelectionSource.inherited,
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
// Re-read the current thread target when the async binding sync runs so a
@ -382,30 +300,6 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
notifyIfActiveInternal();
}
Future<void> refreshSingleAgentSkillsForSession(String sessionKey) async {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
if (assistantExecutionTargetForSession(normalizedSessionKey) !=
AssistantExecutionTarget.singleAgent) {
return;
}
final localSkills = await singleAgentLocalSkillsForSessionInternal(
normalizedSessionKey,
);
await replaceSingleAgentThreadSkillsInternal(
normalizedSessionKey,
localSkills,
);
}
Future<void> refreshSingleAgentLocalSkillsForSession(
String sessionKey,
) async {
await refreshSharedSingleAgentLocalSkillsCacheInternal(forceRescan: true);
await refreshSingleAgentSkillsForSession(sessionKey);
}
Future<void> toggleAssistantSkillForSession(
String sessionKey,
String skillKey,

View File

@ -94,7 +94,6 @@ class _AppShellState extends State<AppShell> {
title: appText('新对话', 'New conversation'),
executionTarget: target,
messageViewMode: controller.currentAssistantMessageViewMode,
singleAgentProvider: SingleAgentProvider.unspecified,
);
controller.navigateTo(WorkspaceDestination.assistant);
await controller.switchSession(sessionKey);

View File

@ -520,38 +520,16 @@ class UiFeatureAccess {
}
List<AssistantExecutionTarget> get availableExecutionTargets {
final targets = <AssistantExecutionTarget>[];
if (supportsDirectAi) {
targets.add(AssistantExecutionTarget.singleAgent);
}
if (supportsRelayGateway) {
targets.add(AssistantExecutionTarget.gateway);
return const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
}
return targets;
return const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
}
AssistantExecutionTarget sanitizeExecutionTarget(
AssistantExecutionTarget? target,
) {
final available = availableExecutionTargets;
if (target != null && available.contains(target)) {
return target;
}
final preferredOrder = platform == UiFeaturePlatform.web
? const <AssistantExecutionTarget>[
AssistantExecutionTarget.singleAgent,
AssistantExecutionTarget.gateway,
]
: const <AssistantExecutionTarget>[
AssistantExecutionTarget.singleAgent,
AssistantExecutionTarget.gateway,
];
for (final candidate in preferredOrder) {
if (available.contains(candidate)) {
return candidate;
}
}
return AssistantExecutionTarget.singleAgent;
return AssistantExecutionTarget.gateway;
}
}

View File

@ -82,10 +82,7 @@ class AssistantTaskRailStateInternal extends State<AssistantTaskRailInternal> {
final groupedTasks = groupTasksForRailInternal(
tasks,
widget.controller.visibleAssistantExecutionTargets(
const <AssistantExecutionTarget>[
AssistantExecutionTarget.singleAgent,
AssistantExecutionTarget.gateway,
],
const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway],
),
);
final runningCount = tasks
@ -501,55 +498,14 @@ class AssistantEmptyStateInternal extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final connectionState = controller.currentAssistantConnectionState;
final singleAgent = connectionState.isSingleAgent;
final connected = connectionState.connected;
final singleAgentNeedsBridgeProvider =
controller.currentSingleAgentNeedsBridgeProvider;
final singleAgentSuggestsAcpSwitch =
controller.currentSingleAgentShouldSuggestAcpSwitch;
final providerLabel =
(controller.currentSingleAgentResolvedProvider ??
controller.currentSingleAgentProvider)
.label;
final reconnectAvailable = controller.canQuickConnectGateway;
final title = singleAgent
? connected
? appText('开始智能体任务', 'Start an agent task')
: singleAgentNeedsBridgeProvider
? appText(
'等待 Bridge Provider',
'Waiting for a bridge provider',
)
: appText(
'等待 Bridge 就绪',
'Waiting for bridge readiness',
)
: connected
final title = connected
? appText('开始对话或运行任务', 'Start a chat or run a task')
: connectionState.status == RuntimeConnectionStatus.error
? appText('Bridge 连接失败', 'Bridge connection failed')
: appText('先连接 Bridge', 'Connect xworkmate-bridge first');
final description = singleAgent
? connected
? appText(
'当前线程会通过 Bridge 当前广告的 Provider 处理任务,不会建立 OpenClaw Gateway 会话。',
'This thread runs through the provider currently advertised by the bridge and does not open an OpenClaw Gateway session.',
)
: singleAgentSuggestsAcpSwitch
? appText(
'当前线程固定为 $providerLabel,但它在这台设备上不可用。请改成 Bridge 当前可用的 Provider。',
'This thread is pinned to $providerLabel, but it is unavailable on this device. Switch to a provider currently advertised by the bridge.',
)
: singleAgentNeedsBridgeProvider
? appText(
'Bridge 当前没有广告可用 Provider。恢复后可直接开始任务当前流程不依赖本地集成配置。',
'The bridge is not advertising any available providers right now. Once it recovers, this thread can start directly without extra local integration setup.',
)
: appText(
'当前线程的 Bridge Provider 尚未就绪。请等待 Bridge 恢复,或切换到当前可用 Provider。',
'The bridge provider for this thread is not ready yet. Wait for the bridge to recover, or switch to a currently available provider.',
)
: connected
final description = connected
? appText(
'输入需求后即可开始执行,结果会回到当前会话并同步到任务页。',
'Type a request to start execution. Results return to this session and the Tasks page.',
@ -604,8 +560,6 @@ class AssistantEmptyStateInternal extends StatelessWidget {
children: [
FilledButton.icon(
onPressed: connected
? onFocusComposer
: singleAgent
? onFocusComposer
: reconnectAvailable
? () async {
@ -615,8 +569,6 @@ class AssistantEmptyStateInternal extends StatelessWidget {
icon: Icon(
connected
? Icons.edit_rounded
: singleAgent
? Icons.smart_toy_outlined
: reconnectAvailable
? Icons.refresh_rounded
: Icons.link_rounded,
@ -624,8 +576,6 @@ class AssistantEmptyStateInternal extends StatelessWidget {
label: Text(
connected
? appText('开始输入', 'Start typing')
: singleAgent
? appText('查看线程工具栏', 'Open toolbar')
: reconnectAvailable
? appText('重新连接 Bridge', 'Reconnect bridge')
: appText(

View File

@ -118,7 +118,6 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
late final TextEditingController skillPickerSearchControllerInternal;
late final FocusNode skillPickerSearchFocusNodeInternal;
bool handlingPasteShortcutInternal = false;
bool refreshingSingleAgentSkillsInternal = false;
String skillPickerQueryInternal = '';
@override
@ -147,37 +146,8 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
reportContentHeightInternal();
}
Future<void> refreshSingleAgentSkillsInternal() async {
if (refreshingSingleAgentSkillsInternal) {
return;
}
setState(() {
refreshingSingleAgentSkillsInternal = true;
});
try {
await widget.controller.refreshSingleAgentLocalSkillsForSession(
widget.controller.currentSessionKey,
);
} finally {
if (mounted) {
setState(() {
refreshingSingleAgentSkillsInternal = false;
});
}
}
}
List<ComposerSkillOptionInternal> activeSkillOptionsInternal() {
if (widget.controller.isSingleAgentMode) {
return widget.controller
.assistantImportedSkillsForSession(
widget.controller.currentSessionKey,
)
.map(skillOptionFromThreadSkillInternal)
.toList(growable: false);
}
return widget.availableSkills;
}
List<ComposerSkillOptionInternal> activeSkillOptionsInternal() =>
widget.availableSkills;
List<ComposerSkillOptionInternal> filteredSkillOptionsInternal() {
final normalizedQuery = skillPickerQueryInternal.trim().toLowerCase();
@ -220,9 +190,6 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
}
skillPickerSearchFocusNodeInternal.requestFocus();
});
if (widget.controller.isSingleAgentMode) {
unawaited(refreshSingleAgentSkillsInternal());
}
}
@override
@ -350,7 +317,6 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
resolveUiFeaturePlatformFromContext(context),
);
final connectionState = controller.currentAssistantConnectionState;
final singleAgent = connectionState.isSingleAgent;
final connected = connectionState.connected;
final reconnectAvailable = controller.canQuickConnectGateway;
final connecting = connectionState.connecting;
@ -374,12 +340,7 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
final selectedSkills = widget.availableSkills
.where((skill) => widget.selectedSkillKeys.contains(skill.key))
.toList(growable: false);
final displayedSingleAgentProvider =
controller.currentSingleAgentResolvedProvider ??
controller.currentSingleAgentProvider;
final submitLabel = connected
? appText('提交', 'Submit')
: singleAgent
? appText('提交', 'Submit')
: connecting
? appText('连接中…', 'Connecting…')
@ -432,12 +393,10 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
tooltip: appText('任务对话模式', 'Task Dialog Mode'),
onSelected: (value) {
final resolvedTarget =
value == AssistantExecutionTarget.singleAgent
? AssistantExecutionTarget.singleAgent
: resolveGatewayExecutionTargetFromVisibleTargets(
visibleExecutionTargets,
currentTarget: executionTarget,
);
resolveGatewayExecutionTargetFromVisibleTargets(
visibleExecutionTargets,
currentTarget: executionTarget,
);
controller.setAssistantExecutionTarget(resolvedTarget);
},
itemBuilder: (context) => compactExecutionTargets
@ -473,50 +432,7 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
),
const SizedBox(width: 4),
],
if (singleAgent) ...[
PopupMenuButton<SingleAgentProvider>(
key: const Key('assistant-single-agent-provider-button'),
tooltip: appText('Bridge Provider', 'Bridge Provider'),
onSelected: (value) {
unawaited(controller.setSingleAgentProvider(value));
},
itemBuilder: (context) => controller
.singleAgentProviderOptions
.map(
(value) => PopupMenuItem<SingleAgentProvider>(
value: value,
key: Key(
'assistant-single-agent-provider-menu-item-${value.providerId}',
),
child: Row(
children: [
SingleAgentProviderBadgeInternal(provider: value),
const SizedBox(width: 10),
Expanded(child: Text(value.label)),
if (value ==
controller.currentSingleAgentProvider)
const Icon(Icons.check_rounded, size: 18),
],
),
),
)
.toList(),
child: ComposerToolbarChipInternal(
leading: SingleAgentProviderBadgeInternal(
provider: displayedSingleAgentProvider,
),
tooltip: singleAgentProviderTooltipInternal(
displayedSingleAgentProvider,
),
showChevron: true,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
),
),
const SizedBox(width: 4),
] else ...[
if (!connecting) ...[
PopupMenuButton<String>(
key: const Key('assistant-gateway-provider-button'),
tooltip: appText('Gateway Provider', 'Gateway Provider'),
@ -795,8 +711,6 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
? null
: connected
? widget.onSend
: singleAgent
? widget.onSend
: reconnectAvailable
? () async {
await widget.onReconnectGateway();
@ -817,8 +731,6 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
children: [
Icon(
connected
? Icons.arrow_upward_rounded
: singleAgent
? Icons.arrow_upward_rounded
: reconnectAvailable
? Icons.refresh_rounded

View File

@ -94,7 +94,7 @@ Widget buildSkillPickerOverlayForInternal(
searchFocusNode: state.skillPickerSearchFocusNodeInternal,
selectedSkillKeys: state.widget.selectedSkillKeys,
filteredSkills: state.filteredSkillOptionsInternal(),
isLoading: state.refreshingSingleAgentSkillsInternal,
isLoading: false,
hasQuery: state.skillPickerQueryInternal.trim().isNotEmpty,
onQueryChanged: state.setSkillPickerQueryInternal,
onToggleSkill: (skillKey) => state.widget.onToggleSkill(skillKey),

View File

@ -195,7 +195,6 @@ class ComposerToolbarChipStateInternal
extension AssistantExecutionTargetIconInternal on AssistantExecutionTarget {
IconData get icon => switch (this) {
AssistantExecutionTarget.singleAgent => Icons.hub_outlined,
AssistantExecutionTarget.gateway => Icons.cloud_outlined,
};
}

View File

@ -87,11 +87,7 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
return;
}
final shouldUseGatewayAgent =
executionTarget != AssistantExecutionTarget.singleAgent;
final autoAgent = shouldUseGatewayAgent
? pickAutoAgentInternal(controller, rawPrompt)
: null;
final autoAgent = pickAutoAgentInternal(controller, rawPrompt);
if (autoAgent != null) {
await controller.selectAgent(autoAgent.id);
}
@ -111,7 +107,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
attachmentNames: attachmentNames,
selectedSkillLabels: selectedSkillLabels,
executionTarget: executionTarget,
singleAgentProvider: controller.currentSingleAgentProvider,
permissionLevel: settings.assistantPermissionLevel,
);
@ -127,7 +122,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
preview: rawPrompt,
status:
controller.hasAssistantPendingRun ||
executionTarget == AssistantExecutionTarget.singleAgent ||
connectionState.connected
? 'running'
: 'queued',
@ -256,12 +250,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
List<ComposerSkillOptionInternal> availableSkillOptionsInternal(
AppController controller,
) {
if (controller.isSingleAgentMode) {
return controller
.assistantImportedSkillsForSession(controller.currentSessionKey)
.map(skillOptionFromThreadSkillInternal)
.toList(growable: false);
}
final options = <ComposerSkillOptionInternal>[];
final seenKeys = <String>{};
@ -306,7 +294,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
required List<String> attachmentNames,
required List<String> selectedSkillLabels,
required AssistantExecutionTarget executionTarget,
required SingleAgentProvider singleAgentProvider,
required AssistantPermissionLevel permissionLevel,
}) {
final attachmentBlock = attachmentNames.isEmpty
@ -318,7 +305,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
final executionContext =
'Execution context:\n'
'- target: ${executionTarget.promptValue}\n'
'${executionTarget == AssistantExecutionTarget.singleAgent ? '- provider: ${singleAgentProvider.providerId}\n' : ''}'
'- permission: ${permissionLevel.promptValue}\n\n';
return switch (mode) {
@ -422,10 +408,7 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
final inheritedTarget = pickDraftThreadExecutionTargetInternal(
currentTarget: widget.controller.currentAssistantExecutionTarget,
visibleTargets: widget.controller.visibleAssistantExecutionTargets(
const <AssistantExecutionTarget>[
AssistantExecutionTarget.singleAgent,
AssistantExecutionTarget.gateway,
],
const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway],
),
localWorkspaceAvailable: widget.controller.settings.workspacePath
.trim()
@ -456,7 +439,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
title: appText('新对话', 'New conversation'),
executionTarget: inheritedTarget,
messageViewMode: inheritedViewMode,
singleAgentProvider: widget.controller.currentSingleAgentProvider,
);
await switchSessionWithRetryInternal(sessionKey);
}
@ -534,7 +516,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
executionTarget: resolvedVisibleExecutionTargetInternal(
widget.controller,
supportedTargets: const <AssistantExecutionTarget>[
AssistantExecutionTarget.singleAgent,
AssistantExecutionTarget.gateway,
],
),
@ -812,9 +793,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
String buildDraftSessionKeyInternal(AppController controller) {
final stamp = DateTime.now().millisecondsSinceEpoch;
if (controller.isSingleAgentMode) {
return 'draft:$stamp';
}
final selectedAgentId = controller.selectedAgentId.trim();
if (selectedAgentId.isEmpty) {
return 'draft:$stamp';

View File

@ -179,14 +179,9 @@ extension AssistantPageStateClosureInternal on AssistantPageStateInternal {
inputController: inputControllerInternal,
focusNode: composerFocusNodeInternal,
thinkingLabel: thinkingLabelInternal,
showModelControl:
!controller.isSingleAgentMode
? true
: controller.currentSingleAgentShouldShowModelControl,
showModelControl: true,
modelLabel:
controller.isSingleAgentMode
? controller.currentSingleAgentModelDisplayLabel
: controller.resolvedAssistantModel.isEmpty
controller.resolvedAssistantModel.isEmpty
? appText('未选择模型', 'No model selected')
: controller.resolvedAssistantModel,
modelOptions: controller.assistantModelChoices,

View File

@ -43,13 +43,6 @@ String executionTargetTooltipInternal(AssistantExecutionTarget target) =>
'Task dialog mode: ${target.compactLabel}',
);
String singleAgentProviderTooltipInternal(
SingleAgentProvider provider,
) => appText(
'Bridge Provider: ${provider.label.trim().isEmpty ? appText('未配置', 'Unconfigured') : provider.label}',
'Bridge Provider: ${provider.label.trim().isEmpty ? appText('未配置', 'Unconfigured') : provider.label}',
);
String gatewayProviderTooltipInternal() => appText(
'Gateway Provider: 🦞 $kCanonicalGatewayProviderLabel',
'Gateway Provider: 🦞 $kCanonicalGatewayProviderLabel',

View File

@ -645,33 +645,8 @@ class _SkillsPanel extends StatelessWidget {
List<GatewaySkillSummary> items,
AssistantExecutionTarget currentMode,
) {
final singleAgentSkills = items
.where((item) => _isSingleAgentSkill(item))
.toList(growable: false);
final gatewaySkills = items
.where((item) => !_isSingleAgentSkill(item))
.toList(growable: false);
final gatewaySkills = items.toList(growable: false);
return <_SkillModeCardData>[
_SkillModeCardData(
title: appText('单机智能体', 'Single agent'),
subtitle: appText(
'直接挂载本地 / 已授权目录中的技能包,适合个人工作区快速调用。',
'Mount local or authorized skill packs directly for fast personal workspace use.',
),
icon: Icons.auto_awesome_rounded,
status: currentMode == AssistantExecutionTarget.singleAgent
? StatusInfo(appText('当前模式', 'Current mode'), StatusTone.accent)
: StatusInfo(appText('可切换', 'Available'), StatusTone.success),
chips: [
for (final provider in controller.bridgeProviderCatalog)
provider.label,
],
skills: singleAgentSkills.map((item) => item.name).toList(),
emptyLabel: appText(
'切换到 Agent 模式后,将显示当前可用的本地技能包。',
'Switch to agent mode to inspect the currently available local skill packs.',
),
),
_SkillModeCardData(
title: appText('Gateway', 'Gateway'),
subtitle: appText(
@ -696,11 +671,6 @@ class _SkillsPanel extends StatelessWidget {
),
];
}
bool _isSingleAgentSkill(GatewaySkillSummary item) {
const gatewaySources = <String>{'gateway', 'workspace', 'acp'};
return !gatewaySources.contains(item.source.trim().toLowerCase());
}
}
class _SkillModeCardData {

View File

@ -66,7 +66,7 @@ class ExternalCodeAgentAcpDesktopTransport
'workingDirectory': workingDirectory.trim(),
'routing': routing.toJson(),
},
endpointOverride: _endpointResolver(AssistantExecutionTarget.singleAgent),
endpointOverride: _endpointResolver(AssistantExecutionTarget.gateway),
);
return ExternalCodeAgentAcpRoutingResolution(
raw: _castMap(response['result']),

View File

@ -239,17 +239,11 @@ class GoTaskServiceRequest {
}
String get acpMode {
return switch (target) {
AssistantExecutionTarget.singleAgent => 'single-agent',
AssistantExecutionTarget.gateway => _gatewaySessionMode,
};
return _gatewaySessionMode;
}
String get routingExecutionTarget {
return switch (target) {
AssistantExecutionTarget.singleAgent => 'single-agent',
AssistantExecutionTarget.gateway => 'gateway',
};
return 'gateway';
}
bool get hasInlineAttachments => inlineAttachments.isNotEmpty;
@ -314,10 +308,8 @@ class GoTaskServiceRequest {
final gatewayTarget = normalizedTarget;
final preferredGatewayTarget = switch (gatewayTarget) {
AssistantExecutionTarget.gateway => kCanonicalGatewayProviderId,
_ => kCanonicalGatewayProviderId,
};
final explicitExecutionTarget = switch (gatewayTarget) {
AssistantExecutionTarget.singleAgent => 'single-agent',
AssistantExecutionTarget.gateway => 'gateway',
};
final explicitProviderId = provider.isUnspecified
@ -562,9 +554,8 @@ String? goTaskServiceGatewayEntryState({
throw StateError(
'Bridge protocol mismatch: unsupported resolvedEndpointTarget "$resolvedEndpointTarget".',
);
case 'single-agent':
case 'multi-agent':
return AssistantExecutionTarget.singleAgent.promptValue;
return AssistantExecutionTarget.gateway.promptValue;
case 'local':
case 'remote':
throw StateError(

View File

@ -42,59 +42,35 @@ bool isLegacyAutoAssistantExecutionTargetValue(String? value) {
return value?.trim().toLowerCase() == 'auto';
}
enum AssistantExecutionTarget { singleAgent, gateway }
enum AssistantExecutionTarget { gateway }
extension AssistantExecutionTargetCopy on AssistantExecutionTarget {
String get label => switch (this) {
AssistantExecutionTarget.singleAgent => appText('Agent', 'Agent'),
AssistantExecutionTarget.gateway => appText('Gateway', 'Gateway'),
};
String get promptValue => switch (this) {
AssistantExecutionTarget.singleAgent => 'single-agent',
AssistantExecutionTarget.gateway => 'gateway',
};
bool get isGateway => this == AssistantExecutionTarget.gateway;
String get compactLabel => switch (this) {
AssistantExecutionTarget.singleAgent => appText('智能体', 'Agent'),
AssistantExecutionTarget.gateway => appText('Gateway', 'Gateway'),
};
static AssistantExecutionTarget fromJsonValue(String? value) {
final normalized = value?.trim() ?? '';
switch (normalized) {
case 'singleAgent':
case 'single-agent':
case 'agent':
return AssistantExecutionTarget.singleAgent;
case 'gateway':
return AssistantExecutionTarget.gateway;
default:
return AssistantExecutionTarget.singleAgent;
}
return AssistantExecutionTarget.gateway;
}
}
List<AssistantExecutionTarget> compactAssistantExecutionTargets(
Iterable<AssistantExecutionTarget> targets,
) {
final ordered = <AssistantExecutionTarget>[];
var addedGateway = false;
for (final target in targets) {
if (target == AssistantExecutionTarget.singleAgent) {
if (!ordered.contains(AssistantExecutionTarget.singleAgent)) {
ordered.add(AssistantExecutionTarget.singleAgent);
}
continue;
}
if (!addedGateway) {
ordered.add(AssistantExecutionTarget.gateway);
addedGateway = true;
}
if (targets.contains(AssistantExecutionTarget.gateway)) {
return const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
}
return List<AssistantExecutionTarget>.unmodifiable(ordered);
return const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
}
AssistantExecutionTarget collapseAssistantExecutionTargetForDisplay(

View File

@ -323,8 +323,7 @@ class AssistantThreadConnectionState {
final bool gatewayTokenMissing;
final String? lastError;
bool get isSingleAgent =>
executionTarget == AssistantExecutionTarget.singleAgent;
bool get isSingleAgent => false;
bool get connected => ready;

View File

@ -513,22 +513,11 @@ bool isLegacyAutoThreadExecutionModeValue(String? value) {
return value?.trim().toLowerCase() == 'auto';
}
enum ThreadExecutionMode { localAgent, gateway }
enum ThreadExecutionMode { gateway }
extension ThreadExecutionModeCopy on ThreadExecutionMode {
static ThreadExecutionMode fromJsonValue(String? value) {
final normalized = value?.trim();
switch (normalized) {
case 'singleAgent':
case 'single-agent':
case 'localAgent':
case 'agent':
return ThreadExecutionMode.localAgent;
case 'gateway':
return ThreadExecutionMode.gateway;
default:
return ThreadExecutionMode.localAgent;
}
return ThreadExecutionMode.gateway;
}
}
@ -722,19 +711,13 @@ class ExecutionBinding {
ThreadExecutionMode threadExecutionModeFromAssistantExecutionTarget(
AssistantExecutionTarget target,
) {
return switch (target) {
AssistantExecutionTarget.singleAgent => ThreadExecutionMode.localAgent,
AssistantExecutionTarget.gateway => ThreadExecutionMode.gateway,
};
return ThreadExecutionMode.gateway;
}
AssistantExecutionTarget assistantExecutionTargetFromExecutionMode(
ThreadExecutionMode mode,
) {
return switch (mode) {
ThreadExecutionMode.localAgent => AssistantExecutionTarget.singleAgent,
ThreadExecutionMode.gateway => AssistantExecutionTarget.gateway,
};
return AssistantExecutionTarget.gateway;
}
WorkspaceRefKind workspaceRefKindFromWorkspaceKind(WorkspaceKind kind) {
@ -1018,7 +1001,7 @@ class TaskThread {
executionBinding =
executionBinding ??
ExecutionBinding(
executionMode: ThreadExecutionMode.localAgent,
executionMode: ThreadExecutionMode.gateway,
executorId: SingleAgentProvider.unspecified.providerId,
providerId: SingleAgentProvider.unspecified.providerId,
endpointId: '',

View File

@ -111,7 +111,7 @@ class SettingsSnapshot {
accountLocalMode: true,
acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults(),
linuxDesktop: LinuxDesktopConfig.defaults(),
assistantExecutionTarget: AssistantExecutionTarget.singleAgent,
assistantExecutionTarget: AssistantExecutionTarget.gateway,
assistantPermissionLevel: AssistantPermissionLevel.defaultAccess,
);
}
@ -359,12 +359,7 @@ class SettingsSnapshot {
GatewayConnectionProfile? gatewayProfileForExecutionTarget(
AssistantExecutionTarget target,
) {
return switch (target) {
AssistantExecutionTarget.singleAgent => null,
AssistantExecutionTarget.gateway => primaryGatewayProfile,
};
}
) => primaryGatewayProfile;
SettingsSnapshot copyWithGatewayProfileAt(
int index,
@ -379,14 +374,7 @@ class SettingsSnapshot {
AssistantExecutionTarget target,
GatewayConnectionProfile profile,
) {
final index = switch (target) {
AssistantExecutionTarget.gateway => kGatewayRemoteProfileIndex,
AssistantExecutionTarget.singleAgent => null,
};
if (index == null) {
return this;
}
return copyWithGatewayProfileAt(index, profile);
return copyWithGatewayProfileAt(kGatewayRemoteProfileIndex, profile);
}
SingleAgentProvider sanitizeSingleAgentProviderSelection(

View File

@ -105,18 +105,12 @@ class AppUiState {
String toJsonString() => jsonEncode(toJson());
bool isGatewayTargetSaved(AssistantExecutionTarget target) {
final targetKey = switch (target) {
AssistantExecutionTarget.gateway => 'gateway',
_ => '',
};
const targetKey = 'gateway';
return targetKey.isNotEmpty && savedGatewayTargets.contains(targetKey);
}
AppUiState markGatewayTargetSaved(AssistantExecutionTarget target) {
final targetKey = switch (target) {
AssistantExecutionTarget.gateway => 'gateway',
_ => '',
};
const targetKey = 'gateway';
if (targetKey.isEmpty || savedGatewayTargets.contains(targetKey)) {
return this;
}

View File

@ -89,53 +89,20 @@ class SkillsFocusPreviewInternal extends StatelessWidget {
@override
Widget build(BuildContext context) {
final typedController = castAssistantFocusControllerInternal(controller);
final items = typedController.isSingleAgentMode
? typedController
.assistantImportedSkillsForSession(
typedController.currentSessionKey,
)
.take(4)
.map(
(skill) => GatewaySkillSummary(
name: skill.label,
description: skill.description,
source: skill.sourcePath,
skillKey: skill.key,
primaryEnv: null,
eligible: true,
disabled: false,
missingBins: const <String>[],
missingEnv: const <String>[],
missingConfig: const <String>[],
),
)
.toList(growable: false)
: typedController.skills.take(4).toList(growable: false);
final items = typedController.skills.take(4).toList(growable: false);
if (items.isEmpty) {
final bridgeEndpointMissing =
typedController.isSingleAgentMode &&
typedController.resolveExternalAcpEndpointForTargetInternal(
AssistantExecutionTarget.singleAgent,
AssistantExecutionTarget.gateway,
) ==
null;
return PreviewEmptyStateInternal(
message: typedController.isSingleAgentMode
? (typedController.currentSingleAgentNeedsBridgeProvider
? appText(
'Bridge 当前没有广告可用 Provider。恢复后这里会显示线程自己的技能摘要。',
'The bridge is not advertising any available providers right now. Thread-owned skill summaries will appear here after it recovers.',
)
: bridgeEndpointMissing
? appText(
'Bridge Server 当前不可用。恢复后这里会显示线程自己的技能摘要。',
'The bridge server is currently unavailable. Thread-owned skill summaries will appear here after it recovers.',
)
: appText(
'当前线程还没有已加载技能。切换 provider 后会读取该线程自己的 skills 列表。',
'No skills are loaded for this thread yet. Switching the provider reloads the thread-owned skills list.',
))
: typedController.connection.status ==
RuntimeConnectionStatus.connected
message: bridgeEndpointMissing
? appText(
'Bridge Server 当前不可用。恢复后这里会显示线程技能摘要。',
'The bridge server is currently unavailable. Thread skill summaries will appear here after it recovers.',
)
: typedController.connection.status == RuntimeConnectionStatus.connected
? appText(
'当前代理没有已加载技能。',
'No skills are loaded for the active agent.',
@ -277,9 +244,7 @@ class ClawHubFocusPreviewInternal extends StatelessWidget {
@override
Widget build(BuildContext context) {
final typedController = castAssistantFocusControllerInternal(controller);
final skillCount = typedController.isSingleAgentMode
? typedController.currentAssistantSkillCount
: typedController.skills.length;
final skillCount = typedController.skills.length;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@ -39,7 +39,6 @@ class SidebarNavigation extends StatelessWidget {
this.onSettingsTabChanged,
this.taskItems = const <SidebarTaskItem>[],
this.visibleExecutionTargets = const <AssistantExecutionTarget>[
AssistantExecutionTarget.singleAgent,
AssistantExecutionTarget.gateway,
],
this.assistantSkillCount = 0,

View File

@ -646,7 +646,6 @@ String _sidebarTaskUpdatedAtLabel(double? updatedAtMs) {
IconData _sidebarTaskTargetIcon(AssistantExecutionTarget target) {
return switch (target) {
AssistantExecutionTarget.singleAgent => Icons.hub_outlined,
AssistantExecutionTarget.gateway => Icons.cloud_outlined,
};
}