Refine assistant task dialog mode mapping
This commit is contained in:
parent
bafcf3e25d
commit
155c4e3de1
@ -480,7 +480,8 @@ class AppController extends ChangeNotifier {
|
||||
bool get hasPendingSettingsApply => pendingSettingsApplyInternal;
|
||||
String get settingsDraftStatusMessage => settingsDraftStatusMessageInternal;
|
||||
List<GatewayAgentSummary> get agents => agentsControllerInternal.agents;
|
||||
List<GatewaySessionSummary> get sessions => sessionsControllerInternal.sessions;
|
||||
List<GatewaySessionSummary> get sessions =>
|
||||
sessionsControllerInternal.sessions;
|
||||
List<GatewaySessionSummary> get assistantSessions =>
|
||||
assistantSessionsInternal();
|
||||
List<GatewayInstanceSummary> get instances =>
|
||||
@ -568,7 +569,9 @@ class AppController extends ChangeNotifier {
|
||||
normalizeSingleAgentProviderList(bridgeProviderCatalogInternal);
|
||||
|
||||
List<SingleAgentProvider> get assistantProviderCatalog {
|
||||
final catalog = bridgeProviderCatalog;
|
||||
final catalog = normalizeBridgeOwnedSingleAgentProviderList(
|
||||
bridgeProviderCatalogInternal,
|
||||
);
|
||||
if (catalog.isNotEmpty) {
|
||||
return catalog;
|
||||
}
|
||||
@ -589,7 +592,9 @@ class AppController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
SingleAgentProvider resolveAssistantProvider(String? providerId) {
|
||||
final normalizedProviderId = normalizeSingleAgentProviderId(providerId ?? '');
|
||||
final normalizedProviderId = normalizeSingleAgentProviderId(
|
||||
providerId ?? '',
|
||||
);
|
||||
final catalog = assistantProviderCatalog;
|
||||
if (normalizedProviderId.isNotEmpty) {
|
||||
for (final provider in catalog) {
|
||||
@ -609,12 +614,18 @@ class AppController extends ChangeNotifier {
|
||||
sessionKey,
|
||||
);
|
||||
final thread = taskThreadForSessionInternal(normalizedSessionKey);
|
||||
final executionTarget = assistantExecutionTargetForSession(
|
||||
normalizedSessionKey,
|
||||
);
|
||||
if (executionTarget.isGateway) {
|
||||
return SingleAgentProvider.openclaw;
|
||||
}
|
||||
return resolveAssistantProvider(thread?.executionBinding.providerId);
|
||||
}
|
||||
|
||||
List<AssistantExecutionTarget> visibleAssistantExecutionTargets(
|
||||
Iterable<AssistantExecutionTarget> supportedTargets,
|
||||
) => const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
|
||||
) => compactAssistantExecutionTargets(supportedTargets);
|
||||
|
||||
List<String> get aiGatewayConversationModelChoices {
|
||||
final availableModels =
|
||||
|
||||
@ -64,10 +64,14 @@ extension AppControllerDesktopExternalAcpRouting on AppController {
|
||||
.where((item) => item.trim().isNotEmpty)
|
||||
.toList(growable: false);
|
||||
|
||||
final currentTarget = assistantExecutionTargetForSession(
|
||||
normalizedSessionKey,
|
||||
);
|
||||
final resolvedProvider = assistantProviderForSession(normalizedSessionKey);
|
||||
final resolvedExplicitProviderId =
|
||||
thread?.hasExplicitProviderSelection == true &&
|
||||
!resolvedProvider.isUnspecified
|
||||
final resolvedExplicitProviderId = currentTarget.isGateway
|
||||
? kCanonicalGatewayProviderId
|
||||
: thread?.hasExplicitProviderSelection == true &&
|
||||
!resolvedProvider.isUnspecified
|
||||
? resolvedProvider.providerId
|
||||
: '';
|
||||
final resolvedExplicitModel = thread?.hasExplicitModelSelection ?? false
|
||||
@ -85,9 +89,7 @@ extension AppControllerDesktopExternalAcpRouting on AppController {
|
||||
explicitExecutionTarget?.trim().isNotEmpty == true
|
||||
? explicitExecutionTarget!.trim()
|
||||
: hasAnyExplicitSelection
|
||||
? _routingExecutionTargetValueInternal(
|
||||
assistantExecutionTargetForSession(normalizedSessionKey),
|
||||
)
|
||||
? _routingExecutionTargetValueInternal(currentTarget)
|
||||
: '';
|
||||
final hasExplicitSelection =
|
||||
resolvedExplicitExecutionTarget.isNotEmpty ||
|
||||
@ -115,8 +117,6 @@ extension AppControllerDesktopExternalAcpRouting on AppController {
|
||||
}
|
||||
|
||||
String _routingExecutionTargetValueInternal(AssistantExecutionTarget target) {
|
||||
return switch (target) {
|
||||
AssistantExecutionTarget.gateway => 'gateway',
|
||||
};
|
||||
return target.promptValue;
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,6 +187,7 @@ GatewayMode bridgeGatewayModeRuntimeInternal(AppController controller) {
|
||||
return GatewayMode.offline;
|
||||
}
|
||||
return switch (controller.currentAssistantExecutionTarget) {
|
||||
AssistantExecutionTarget.agent => GatewayMode.remote,
|
||||
AssistantExecutionTarget.gateway => GatewayMode.remote,
|
||||
};
|
||||
}
|
||||
|
||||
@ -250,8 +250,9 @@ extension AppControllerDesktopSkillPermissions on AppController {
|
||||
final nextExecutionTarget =
|
||||
executionTarget ??
|
||||
switch (existing?.executionBinding.executionMode) {
|
||||
ThreadExecutionMode.agent => AssistantExecutionTarget.agent,
|
||||
ThreadExecutionMode.gateway => AssistantExecutionTarget.gateway,
|
||||
null => AssistantExecutionTarget.gateway,
|
||||
null => AssistantExecutionTarget.agent,
|
||||
};
|
||||
final nextImportedSkills =
|
||||
importedSkills ??
|
||||
@ -293,11 +294,15 @@ extension AppControllerDesktopSkillPermissions on AppController {
|
||||
final requestedProvider = singleAgentProvider?.isUnspecified == false
|
||||
? singleAgentProvider
|
||||
: null;
|
||||
final nextProvider = resolveAssistantProvider(
|
||||
final nextProviderId = normalizeSingleAgentProviderId(
|
||||
requestedProvider?.providerId ??
|
||||
existing?.executionBinding.providerId ??
|
||||
existing?.contextState.latestResolvedProviderId,
|
||||
existing?.contextState.latestResolvedProviderId ??
|
||||
'',
|
||||
);
|
||||
final nextProvider = nextProviderId.isEmpty
|
||||
? SingleAgentProvider.unspecified
|
||||
: resolveAssistantProvider(nextProviderId);
|
||||
final nextProviderSource =
|
||||
singleAgentProviderSource ??
|
||||
existing?.executionBinding.providerSource ??
|
||||
@ -306,13 +311,18 @@ extension AppControllerDesktopSkillPermissions on AppController {
|
||||
(executionBinding ??
|
||||
existing?.executionBinding ??
|
||||
ExecutionBinding(
|
||||
executionMode: ThreadExecutionMode.gateway,
|
||||
executionMode:
|
||||
threadExecutionModeFromAssistantExecutionTarget(
|
||||
nextExecutionTarget,
|
||||
),
|
||||
executorId: nextProvider.providerId,
|
||||
providerId: nextProvider.providerId,
|
||||
endpointId: '',
|
||||
))
|
||||
.copyWith(
|
||||
executionMode: ThreadExecutionMode.gateway,
|
||||
executionMode: threadExecutionModeFromAssistantExecutionTarget(
|
||||
nextExecutionTarget,
|
||||
),
|
||||
executorId: nextProvider.providerId,
|
||||
providerId: nextProvider.providerId,
|
||||
executionModeSource:
|
||||
|
||||
@ -219,22 +219,30 @@ extension AppControllerDesktopThreadBinding on AppController {
|
||||
required AssistantExecutionTarget executionTarget,
|
||||
ExecutionBinding? existingBinding,
|
||||
}) {
|
||||
final selectedProvider = resolveAssistantProvider(
|
||||
existingBinding?.providerId,
|
||||
final persistedProviderId = normalizeSingleAgentProviderId(
|
||||
existingBinding?.providerId ?? '',
|
||||
);
|
||||
final selectedProvider = persistedProviderId.isEmpty
|
||||
? SingleAgentProvider.unspecified
|
||||
: resolveAssistantProvider(persistedProviderId);
|
||||
return (existingBinding ??
|
||||
ExecutionBinding(
|
||||
executionMode: ThreadExecutionMode.gateway,
|
||||
executionMode: threadExecutionModeFromAssistantExecutionTarget(
|
||||
executionTarget,
|
||||
),
|
||||
executorId: selectedProvider.providerId,
|
||||
providerId: selectedProvider.providerId,
|
||||
endpointId: '',
|
||||
))
|
||||
.copyWith(
|
||||
executionMode: ThreadExecutionMode.gateway,
|
||||
executionMode: threadExecutionModeFromAssistantExecutionTarget(
|
||||
executionTarget,
|
||||
),
|
||||
executorId: selectedProvider.providerId,
|
||||
providerId: selectedProvider.providerId,
|
||||
providerSource:
|
||||
existingBinding?.providerSource ?? ThreadSelectionSource.inherited,
|
||||
existingBinding?.providerSource ??
|
||||
ThreadSelectionSource.inherited,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -303,7 +303,9 @@ extension AppControllerDesktopThreadSessions on AppController {
|
||||
final sessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
);
|
||||
final items = List<GatewayChatMessage>.from(chatControllerInternal.messages);
|
||||
final items = List<GatewayChatMessage>.from(
|
||||
chatControllerInternal.messages,
|
||||
);
|
||||
final threadItems = assistantThreadMessagesInternal[sessionKey];
|
||||
if (threadItems != null && threadItems.isNotEmpty) {
|
||||
items.addAll(threadItems);
|
||||
@ -312,7 +314,8 @@ extension AppControllerDesktopThreadSessions on AppController {
|
||||
if (localItems != null && localItems.isNotEmpty) {
|
||||
items.addAll(localItems);
|
||||
}
|
||||
final streaming = chatControllerInternal.streamingAssistantText?.trim() ?? '';
|
||||
final streaming =
|
||||
chatControllerInternal.streamingAssistantText?.trim() ?? '';
|
||||
if (streaming.isNotEmpty) {
|
||||
items.add(
|
||||
GatewayChatMessage(
|
||||
@ -419,8 +422,17 @@ AssistantExecutionTarget resolveAssistantExecutionTargetFromRecordsForTest(
|
||||
}) {
|
||||
final record = primaryRecord ?? fallbackRecord;
|
||||
return record == null
|
||||
? AssistantExecutionTarget.gateway
|
||||
: assistantExecutionTargetFromExecutionMode(
|
||||
record.executionBinding.executionMode,
|
||||
);
|
||||
? AssistantExecutionTarget.agent
|
||||
: (() {
|
||||
final resolved = assistantExecutionTargetFromExecutionMode(
|
||||
record.executionBinding.executionMode,
|
||||
);
|
||||
if (resolved.isGateway &&
|
||||
isBridgeOwnedSingleAgentProviderId(
|
||||
record.executionBinding.providerId,
|
||||
)) {
|
||||
return AssistantExecutionTarget.agent;
|
||||
}
|
||||
return resolved;
|
||||
})();
|
||||
}
|
||||
|
||||
@ -62,7 +62,9 @@ extension AppControllerDesktopThreadStorage on AppController {
|
||||
}
|
||||
|
||||
Future<void> ensureActiveAssistantThreadInternal() async {
|
||||
if (!isAssistantTaskArchived(sessionsControllerInternal.currentSessionKey)) {
|
||||
if (!isAssistantTaskArchived(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
final fallback = assistantSessionSummariesInternal().firstWhere(
|
||||
@ -683,11 +685,18 @@ extension AppControllerDesktopThreadStorage on AppController {
|
||||
record.executionBinding.executionMode,
|
||||
),
|
||||
);
|
||||
const recordProvider = SingleAgentProvider(
|
||||
providerId: kCanonicalGatewayProviderId,
|
||||
label: kCanonicalGatewayProviderLabel,
|
||||
badge: 'OC',
|
||||
final recordProviderId = normalizeSingleAgentProviderId(
|
||||
record.executionBinding.providerId,
|
||||
);
|
||||
final recordProvider = recordProviderId.isEmpty
|
||||
? SingleAgentProvider.unspecified
|
||||
: resolveAssistantProvider(recordProviderId);
|
||||
final normalizedExecutionTarget =
|
||||
recordExecutionTarget.isGateway &&
|
||||
recordProviderId.isNotEmpty &&
|
||||
isBridgeOwnedSingleAgentProviderId(recordProviderId)
|
||||
? AssistantExecutionTarget.agent
|
||||
: recordExecutionTarget;
|
||||
final workspaceBinding = record.workspaceBinding.copyWith(
|
||||
workspaceId: sessionKey,
|
||||
displayPath: record.workspaceKind == WorkspaceKind.localFs
|
||||
@ -707,19 +716,19 @@ extension AppControllerDesktopThreadStorage on AppController {
|
||||
)
|
||||
.toList(growable: false),
|
||||
assistantModelId: record.assistantModelId.trim().isEmpty
|
||||
? resolvedAssistantModelForTargetInternal(recordExecutionTarget)
|
||||
? resolvedAssistantModelForTargetInternal(normalizedExecutionTarget)
|
||||
: record.assistantModelId.trim(),
|
||||
gatewayEntryState: (record.gatewayEntryState ?? '').trim().isEmpty
|
||||
? gatewayEntryStateForTargetInternal(recordExecutionTarget)
|
||||
? gatewayEntryStateForTargetInternal(normalizedExecutionTarget)
|
||||
: record.gatewayEntryState,
|
||||
workspaceBinding: workspaceBinding,
|
||||
executionBinding: record.executionBinding.copyWith(
|
||||
executionMode: threadExecutionModeFromAssistantExecutionTarget(
|
||||
recordExecutionTarget,
|
||||
normalizedExecutionTarget,
|
||||
),
|
||||
executorId: recordProvider.providerId,
|
||||
providerId: recordProvider.providerId,
|
||||
providerSource: ThreadSelectionSource.inherited,
|
||||
providerSource: record.executionBinding.providerSource,
|
||||
),
|
||||
lifecycleState: record.lifecycleState.copyWith(status: 'ready'),
|
||||
);
|
||||
|
||||
@ -122,17 +122,27 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
|
||||
if (!assistantThreadRecordsInternal.containsKey(sessionKey)) {
|
||||
initializeAssistantThreadContext(
|
||||
sessionKey,
|
||||
executionTarget: assistantExecutionTargetForSession(sessionKey),
|
||||
executionTarget: AssistantExecutionTarget.agent,
|
||||
messageViewMode: assistantMessageViewModeForSession(sessionKey),
|
||||
);
|
||||
}
|
||||
upsertTaskThreadInternal(
|
||||
sessionKey,
|
||||
executionTarget: AssistantExecutionTarget.agent,
|
||||
executionTargetSource: ThreadSelectionSource.explicit,
|
||||
singleAgentProvider: resolvedProvider,
|
||||
singleAgentProviderSource: ThreadSelectionSource.explicit,
|
||||
gatewayEntryState: gatewayEntryStateForTargetInternal(
|
||||
AssistantExecutionTarget.agent,
|
||||
),
|
||||
latestResolvedProviderId: '',
|
||||
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
|
||||
);
|
||||
await applyAssistantExecutionTargetInternal(
|
||||
AssistantExecutionTarget.agent,
|
||||
sessionKey: sessionKey,
|
||||
persistDefaultSelection: true,
|
||||
);
|
||||
await flushAssistantThreadPersistenceInternal();
|
||||
recomputeTasksInternal();
|
||||
notifyIfActiveInternal();
|
||||
|
||||
@ -520,16 +520,19 @@ class UiFeatureAccess {
|
||||
}
|
||||
|
||||
List<AssistantExecutionTarget> get availableExecutionTargets {
|
||||
if (supportsRelayGateway) {
|
||||
return const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
|
||||
}
|
||||
return const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
|
||||
return const <AssistantExecutionTarget>[
|
||||
AssistantExecutionTarget.agent,
|
||||
AssistantExecutionTarget.gateway,
|
||||
];
|
||||
}
|
||||
|
||||
AssistantExecutionTarget sanitizeExecutionTarget(
|
||||
AssistantExecutionTarget? target,
|
||||
) {
|
||||
return AssistantExecutionTarget.gateway;
|
||||
final resolved = target ?? AssistantExecutionTarget.agent;
|
||||
return availableExecutionTargets.contains(resolved)
|
||||
? resolved
|
||||
: AssistantExecutionTarget.agent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -82,7 +82,7 @@ class AssistantTaskRailStateInternal extends State<AssistantTaskRailInternal> {
|
||||
final groupedTasks = groupTasksForRailInternal(
|
||||
tasks,
|
||||
widget.controller.visibleAssistantExecutionTargets(
|
||||
const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway],
|
||||
AssistantExecutionTarget.values,
|
||||
),
|
||||
);
|
||||
final runningCount = tasks
|
||||
@ -578,10 +578,7 @@ class AssistantEmptyStateInternal extends StatelessWidget {
|
||||
? appText('开始输入', 'Start typing')
|
||||
: reconnectAvailable
|
||||
? appText('重新连接 Bridge', 'Reconnect bridge')
|
||||
: appText(
|
||||
'连接 Bridge',
|
||||
'Connect xworkmate-bridge',
|
||||
),
|
||||
: appText('连接 Bridge', 'Connect xworkmate-bridge'),
|
||||
),
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size(0, 28),
|
||||
|
||||
@ -198,7 +198,7 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
|
||||
}
|
||||
|
||||
void handleControllerChangedInternal() {
|
||||
if (!mounted || !skillPickerPortalControllerInternal.isShowing) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {});
|
||||
@ -379,13 +379,18 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
|
||||
PopupMenuButton<AssistantExecutionTarget>(
|
||||
key: const Key('assistant-execution-target-button'),
|
||||
tooltip: appText('任务对话模式', 'Task Dialog Mode'),
|
||||
onSelected: (value) {
|
||||
onSelected: (value) async {
|
||||
final resolvedTarget =
|
||||
resolveGatewayExecutionTargetFromVisibleTargets(
|
||||
resolveAssistantExecutionTargetFromVisibleTargets(
|
||||
visibleExecutionTargets,
|
||||
currentTarget: executionTarget,
|
||||
currentTarget: value,
|
||||
);
|
||||
controller.setAssistantExecutionTarget(resolvedTarget);
|
||||
await controller.setAssistantExecutionTarget(
|
||||
resolvedTarget,
|
||||
);
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => compactExecutionTargets
|
||||
.map(
|
||||
@ -420,7 +425,7 @@ class ComposerBarStateInternal extends State<ComposerBarInternal> {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
if (availableProviders.isNotEmpty) ...[
|
||||
if (executionTarget.isAgent && availableProviders.isNotEmpty) ...[
|
||||
PopupMenuButton<String>(
|
||||
key: const Key('assistant-provider-button'),
|
||||
tooltip: appText('智能体 Provider', 'Agent Provider'),
|
||||
|
||||
@ -195,6 +195,7 @@ class ComposerToolbarChipStateInternal
|
||||
|
||||
extension AssistantExecutionTargetIconInternal on AssistantExecutionTarget {
|
||||
IconData get icon => switch (this) {
|
||||
AssistantExecutionTarget.agent => Icons.cloud_outlined,
|
||||
AssistantExecutionTarget.gateway => Icons.cloud_outlined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -120,9 +120,7 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
|
||||
taskSeedsInternal[controller.currentSessionKey]?.title ??
|
||||
fallbackSessionTitleInternal(controller.currentSessionKey),
|
||||
preview: rawPrompt,
|
||||
status:
|
||||
controller.hasAssistantPendingRun ||
|
||||
connectionState.connected
|
||||
status: controller.hasAssistantPendingRun || connectionState.connected
|
||||
? 'running'
|
||||
: 'queued',
|
||||
owner: autoAgent?.name ?? conversationOwnerLabelInternal(controller),
|
||||
@ -408,7 +406,7 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
|
||||
final inheritedTarget = pickDraftThreadExecutionTargetInternal(
|
||||
currentTarget: widget.controller.currentAssistantExecutionTarget,
|
||||
visibleTargets: widget.controller.visibleAssistantExecutionTargets(
|
||||
const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway],
|
||||
AssistantExecutionTarget.values,
|
||||
),
|
||||
localWorkspaceAvailable: widget.controller.settings.workspacePath
|
||||
.trim()
|
||||
@ -515,9 +513,7 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
|
||||
surface: 'Assistant',
|
||||
executionTarget: resolvedVisibleExecutionTargetInternal(
|
||||
widget.controller,
|
||||
supportedTargets: const <AssistantExecutionTarget>[
|
||||
AssistantExecutionTarget.gateway,
|
||||
],
|
||||
supportedTargets: AssistantExecutionTarget.values,
|
||||
),
|
||||
isCurrent: true,
|
||||
draft: true,
|
||||
|
||||
@ -228,8 +228,7 @@ class GoTaskServiceRequest {
|
||||
multiAgent ||
|
||||
collaborationMode == GoTaskServiceCollaborationMode.multiAgent;
|
||||
|
||||
AssistantExecutionTarget get normalizedTarget =>
|
||||
target.isGateway ? AssistantExecutionTarget.gateway : target;
|
||||
AssistantExecutionTarget get normalizedTarget => target;
|
||||
|
||||
GoTaskServiceRoute get route {
|
||||
if (isMultiAgentRequest) {
|
||||
@ -243,7 +242,7 @@ class GoTaskServiceRequest {
|
||||
}
|
||||
|
||||
String get routingExecutionTarget {
|
||||
return 'gateway';
|
||||
return normalizedTarget.promptValue;
|
||||
}
|
||||
|
||||
bool get hasInlineAttachments => inlineAttachments.isNotEmpty;
|
||||
@ -307,9 +306,11 @@ class GoTaskServiceRequest {
|
||||
ExternalCodeAgentAcpRoutingConfig _synthesizedRouting() {
|
||||
final gatewayTarget = normalizedTarget;
|
||||
final preferredGatewayTarget = switch (gatewayTarget) {
|
||||
AssistantExecutionTarget.agent => kCanonicalGatewayProviderId,
|
||||
AssistantExecutionTarget.gateway => kCanonicalGatewayProviderId,
|
||||
};
|
||||
final explicitExecutionTarget = switch (gatewayTarget) {
|
||||
AssistantExecutionTarget.agent => 'agent',
|
||||
AssistantExecutionTarget.gateway => 'gateway',
|
||||
};
|
||||
final explicitProviderId = provider.isUnspecified
|
||||
|
||||
@ -42,55 +42,63 @@ bool isLegacyAutoAssistantExecutionTargetValue(String? value) {
|
||||
return value?.trim().toLowerCase() == 'auto';
|
||||
}
|
||||
|
||||
enum AssistantExecutionTarget { gateway }
|
||||
enum AssistantExecutionTarget { agent, gateway }
|
||||
|
||||
extension AssistantExecutionTargetCopy on AssistantExecutionTarget {
|
||||
String get label => switch (this) {
|
||||
AssistantExecutionTarget.agent => appText('智能体', 'Agent'),
|
||||
AssistantExecutionTarget.gateway => appText('Gateway', 'Gateway'),
|
||||
};
|
||||
|
||||
String get promptValue => switch (this) {
|
||||
AssistantExecutionTarget.agent => 'agent',
|
||||
AssistantExecutionTarget.gateway => 'gateway',
|
||||
};
|
||||
|
||||
bool get isAgent => this == AssistantExecutionTarget.agent;
|
||||
bool get isGateway => this == AssistantExecutionTarget.gateway;
|
||||
|
||||
String get compactLabel => switch (this) {
|
||||
AssistantExecutionTarget.agent => appText('智能体', 'Agent'),
|
||||
AssistantExecutionTarget.gateway => appText('Gateway', 'Gateway'),
|
||||
};
|
||||
|
||||
static AssistantExecutionTarget fromJsonValue(String? value) {
|
||||
return AssistantExecutionTarget.gateway;
|
||||
return AssistantExecutionTarget.values.firstWhere(
|
||||
(item) => item.name == value?.trim() || item.promptValue == value?.trim(),
|
||||
orElse: () => AssistantExecutionTarget.agent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<AssistantExecutionTarget> compactAssistantExecutionTargets(
|
||||
Iterable<AssistantExecutionTarget> targets,
|
||||
) {
|
||||
if (targets.contains(AssistantExecutionTarget.gateway)) {
|
||||
return const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
|
||||
final ordered = <AssistantExecutionTarget>[];
|
||||
for (final candidate in AssistantExecutionTarget.values) {
|
||||
if (targets.contains(candidate)) {
|
||||
ordered.add(candidate);
|
||||
}
|
||||
}
|
||||
return const <AssistantExecutionTarget>[AssistantExecutionTarget.gateway];
|
||||
return ordered.isEmpty ? AssistantExecutionTarget.values : ordered;
|
||||
}
|
||||
|
||||
AssistantExecutionTarget collapseAssistantExecutionTargetForDisplay(
|
||||
AssistantExecutionTarget target,
|
||||
) => target;
|
||||
|
||||
AssistantExecutionTarget resolveGatewayExecutionTargetFromVisibleTargets(
|
||||
AssistantExecutionTarget resolveAssistantExecutionTargetFromVisibleTargets(
|
||||
Iterable<AssistantExecutionTarget> visibleTargets, {
|
||||
AssistantExecutionTarget? currentTarget,
|
||||
}) {
|
||||
final visible = visibleTargets.toList(growable: false);
|
||||
if (currentTarget != null && currentTarget.isGateway) {
|
||||
if (visible.contains(AssistantExecutionTarget.gateway)) {
|
||||
return AssistantExecutionTarget.gateway;
|
||||
}
|
||||
if (currentTarget != null && visible.contains(currentTarget)) {
|
||||
return currentTarget;
|
||||
}
|
||||
if (visible.contains(AssistantExecutionTarget.gateway)) {
|
||||
return AssistantExecutionTarget.gateway;
|
||||
if (visible.isNotEmpty) {
|
||||
return visible.first;
|
||||
}
|
||||
return AssistantExecutionTarget.gateway;
|
||||
return AssistantExecutionTarget.agent;
|
||||
}
|
||||
|
||||
String normalizeSingleAgentProviderId(String value) {
|
||||
@ -211,6 +219,12 @@ class SingleAgentProvider {
|
||||
badge: 'G',
|
||||
);
|
||||
|
||||
static const SingleAgentProvider openclaw = SingleAgentProvider(
|
||||
providerId: kCanonicalGatewayProviderId,
|
||||
label: kCanonicalGatewayProviderLabel,
|
||||
badge: 'OC',
|
||||
);
|
||||
|
||||
final String providerId;
|
||||
final String label;
|
||||
final String badge;
|
||||
@ -257,6 +271,7 @@ class SingleAgentProvider {
|
||||
'opencode' => opencode,
|
||||
'claude' => claude,
|
||||
'gemini' => gemini,
|
||||
kCanonicalGatewayProviderId => openclaw,
|
||||
'auto' || '' => unspecified,
|
||||
_ => SingleAgentProvider(
|
||||
providerId: normalized,
|
||||
|
||||
@ -513,11 +513,14 @@ bool isLegacyAutoThreadExecutionModeValue(String? value) {
|
||||
return value?.trim().toLowerCase() == 'auto';
|
||||
}
|
||||
|
||||
enum ThreadExecutionMode { gateway }
|
||||
enum ThreadExecutionMode { agent, gateway }
|
||||
|
||||
extension ThreadExecutionModeCopy on ThreadExecutionMode {
|
||||
static ThreadExecutionMode fromJsonValue(String? value) {
|
||||
return ThreadExecutionMode.gateway;
|
||||
return ThreadExecutionMode.values.firstWhere(
|
||||
(item) => item.name == value?.trim(),
|
||||
orElse: () => ThreadExecutionMode.gateway,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -711,13 +714,19 @@ class ExecutionBinding {
|
||||
ThreadExecutionMode threadExecutionModeFromAssistantExecutionTarget(
|
||||
AssistantExecutionTarget target,
|
||||
) {
|
||||
return ThreadExecutionMode.gateway;
|
||||
return switch (target) {
|
||||
AssistantExecutionTarget.agent => ThreadExecutionMode.agent,
|
||||
AssistantExecutionTarget.gateway => ThreadExecutionMode.gateway,
|
||||
};
|
||||
}
|
||||
|
||||
AssistantExecutionTarget assistantExecutionTargetFromExecutionMode(
|
||||
ThreadExecutionMode mode,
|
||||
) {
|
||||
return AssistantExecutionTarget.gateway;
|
||||
return switch (mode) {
|
||||
ThreadExecutionMode.agent => AssistantExecutionTarget.agent,
|
||||
ThreadExecutionMode.gateway => AssistantExecutionTarget.gateway,
|
||||
};
|
||||
}
|
||||
|
||||
WorkspaceRefKind workspaceRefKindFromWorkspaceKind(WorkspaceKind kind) {
|
||||
|
||||
@ -111,7 +111,7 @@ class SettingsSnapshot {
|
||||
accountLocalMode: true,
|
||||
acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults(),
|
||||
linuxDesktop: LinuxDesktopConfig.defaults(),
|
||||
assistantExecutionTarget: AssistantExecutionTarget.gateway,
|
||||
assistantExecutionTarget: AssistantExecutionTarget.agent,
|
||||
assistantPermissionLevel: AssistantPermissionLevel.defaultAccess,
|
||||
);
|
||||
}
|
||||
|
||||
@ -646,6 +646,7 @@ String _sidebarTaskUpdatedAtLabel(double? updatedAtMs) {
|
||||
|
||||
IconData _sidebarTaskTargetIcon(AssistantExecutionTarget target) {
|
||||
return switch (target) {
|
||||
AssistantExecutionTarget.agent => Icons.hub_rounded,
|
||||
AssistantExecutionTarget.gateway => Icons.cloud_outlined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -9,6 +9,60 @@ import 'package:xworkmate/widgets/surface_card.dart';
|
||||
|
||||
void main() {
|
||||
group('AssistantLowerPaneInternal', () {
|
||||
testWidgets('shows agent and gateway task dialog modes', (tester) async {
|
||||
final controller = AppController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(child: _buildLowerPane(controller: controller)),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byKey(const Key('assistant-provider-button')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(
|
||||
find.byKey(const Key('assistant-execution-target-button')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-agent')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-gateway')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-gateway')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(controller.assistantExecutionTarget.name, 'gateway');
|
||||
expect(find.byKey(const Key('assistant-provider-button')), findsNothing);
|
||||
|
||||
await tester.tap(
|
||||
find.byKey(const Key('assistant-execution-target-button')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.byKey(const Key('assistant-execution-target-menu-item-agent')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(controller.assistantExecutionTarget.name, 'agent');
|
||||
expect(
|
||||
find.byKey(const Key('assistant-provider-button')),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('shows assistant providers and allows switching provider', (
|
||||
tester,
|
||||
) async {
|
||||
@ -18,9 +72,7 @@ void main() {
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(
|
||||
child: _buildLowerPane(controller: controller),
|
||||
),
|
||||
_buildTestApp(child: _buildLowerPane(controller: controller)),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@ -46,7 +98,8 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
controller.assistantProviderForSession(controller.currentSessionKey)
|
||||
controller
|
||||
.assistantProviderForSession(controller.currentSessionKey)
|
||||
.providerId,
|
||||
'opencode',
|
||||
);
|
||||
@ -88,9 +141,7 @@ Widget _buildTestApp({required Widget child}) {
|
||||
return MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: SizedBox(width: 1400, height: 360, child: child),
|
||||
),
|
||||
child: Center(child: SizedBox(width: 1400, height: 360, child: child)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
49
test/runtime/assistant_execution_target_test.dart
Normal file
49
test/runtime/assistant_execution_target_test.dart
Normal file
@ -0,0 +1,49 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:xworkmate/runtime/runtime_models.dart';
|
||||
|
||||
void main() {
|
||||
group('AssistantExecutionTarget', () {
|
||||
test('maps agent and gateway values without collapsing them', () {
|
||||
expect(
|
||||
threadExecutionModeFromAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.agent,
|
||||
),
|
||||
ThreadExecutionMode.agent,
|
||||
);
|
||||
expect(
|
||||
threadExecutionModeFromAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
),
|
||||
ThreadExecutionMode.gateway,
|
||||
);
|
||||
expect(
|
||||
assistantExecutionTargetFromExecutionMode(ThreadExecutionMode.agent),
|
||||
AssistantExecutionTarget.agent,
|
||||
);
|
||||
expect(
|
||||
assistantExecutionTargetFromExecutionMode(ThreadExecutionMode.gateway),
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps both task dialog modes visible when both are supported', () {
|
||||
expect(
|
||||
compactAssistantExecutionTargets(const <AssistantExecutionTarget>[
|
||||
AssistantExecutionTarget.agent,
|
||||
AssistantExecutionTarget.gateway,
|
||||
]),
|
||||
const <AssistantExecutionTarget>[
|
||||
AssistantExecutionTarget.agent,
|
||||
AssistantExecutionTarget.gateway,
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('recognizes openclaw as the canonical gateway provider', () {
|
||||
final provider = SingleAgentProvider.fromJsonValue('openclaw');
|
||||
|
||||
expect(provider.providerId, kCanonicalGatewayProviderId);
|
||||
expect(provider.label, kCanonicalGatewayProviderLabel);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user