Merge branch 'codex/cleanup-single-agent-garbage'
This commit is contained in:
commit
879451ba98
@ -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;
|
||||
|
||||
@ -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',
|
||||
};
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -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);
|
||||
@ -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',
|
||||
);
|
||||
}
|
||||
@ -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:
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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 ||
|
||||
|
||||
@ -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'),
|
||||
);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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']),
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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: '',
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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: [
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user