refactor: remove stale skill routing paths

This commit is contained in:
Haitao Pan 2026-04-23 17:11:10 +08:00
parent be24832f80
commit bc2e31f13b
7 changed files with 62 additions and 150 deletions

View File

@ -646,15 +646,6 @@ class AppController extends ChangeNotifier {
AssistantExecutionTarget target,
) => resolvedDefaultModel.trim();
List<AssistantThreadSkillEntry> assistantImportedSkillsForSession(
String sessionKey,
) =>
assistantThreadRecordsInternal[normalizedAssistantSessionKeyInternal(
sessionKey,
)]
?.importedSkills ??
const [];
void navigateTo(WorkspaceDestination destination) =>
AppControllerDesktopNavigation(this).navigateTo(destination);
@ -692,6 +683,6 @@ class AppController extends ChangeNotifier {
this,
).refreshMultiAgentMounts(sync: sync);
double get assistantSkillCount => 0; // Legacy
int get currentAssistantSkillCount => 0; // Legacy
double get assistantSkillCount => skills.length.toDouble();
int get currentAssistantSkillCount => skills.length;
}

View File

@ -46,28 +46,27 @@ extension AppControllerDesktopExternalAcpRouting on AppController {
);
final thread = assistantThreadRecordsInternal[normalizedSessionKey];
const preferredGatewayTarget = kCanonicalGatewayProviderId;
final availableSkills =
assistantImportedSkillsForSession(normalizedSessionKey)
.map((item) {
return ExternalCodeAgentAcpAvailableSkill(
id: item.key,
label: item.label,
description: item.description,
);
})
.toList(growable: false);
final selectedSkills =
assistantSelectedSkillsForSession(normalizedSessionKey)
.map((item) {
return item.label.trim().isNotEmpty ? item.label : item.key;
})
.where((item) => item.trim().isNotEmpty)
.toList(growable: false);
final availableSkills = skills
.map((item) {
return ExternalCodeAgentAcpAvailableSkill(
id: item.skillKey,
label: item.name,
description: item.description,
);
})
.toList(growable: false);
final selectedSkills = assistantSelectedSkillKeysForSession(
normalizedSessionKey,
);
final currentTarget = assistantExecutionTargetForSession(
normalizedSessionKey,
);
final resolvedProvider = assistantProviderForSession(normalizedSessionKey);
final resolvedExecutionTarget =
explicitExecutionTarget?.trim().isNotEmpty == true
? explicitExecutionTarget!.trim()
: _routingExecutionTargetValueInternal(currentTarget);
final resolvedExplicitProviderId =
thread?.hasExplicitProviderSelection == true &&
!currentTarget.isGateway &&
@ -80,33 +79,10 @@ extension AppControllerDesktopExternalAcpRouting on AppController {
final resolvedExplicitSkills = thread?.hasExplicitSkillSelection ?? false
? selectedSkills
: const <String>[];
final hasAnyExplicitSelection =
resolvedExplicitProviderId.isNotEmpty ||
resolvedExplicitModel.trim().isNotEmpty ||
resolvedExplicitSkills.isNotEmpty;
final resolvedExplicitExecutionTarget =
explicitExecutionTarget?.trim().isNotEmpty == true
? explicitExecutionTarget!.trim()
: (hasAnyExplicitSelection || currentTarget.isGateway)
? _routingExecutionTargetValueInternal(currentTarget)
: '';
final hasExplicitSelection =
resolvedExplicitExecutionTarget.isNotEmpty ||
resolvedExplicitProviderId.isNotEmpty ||
resolvedExplicitModel.trim().isNotEmpty ||
resolvedExplicitSkills.isNotEmpty;
if (!hasExplicitSelection) {
return ExternalCodeAgentAcpRoutingConfig.auto(
preferredGatewayTarget: preferredGatewayTarget,
availableSkills: availableSkills,
);
}
return ExternalCodeAgentAcpRoutingConfig(
mode: ExternalCodeAgentAcpRoutingMode.explicit,
preferredGatewayTarget: preferredGatewayTarget,
explicitExecutionTarget: resolvedExplicitExecutionTarget,
explicitExecutionTarget: resolvedExecutionTarget,
explicitProviderId: resolvedExplicitProviderId,
explicitModel: resolvedExplicitModel,
explicitSkills: resolvedExplicitSkills,

View File

@ -214,40 +214,20 @@ extension AppControllerDesktopThreadSessions on AppController {
);
}
int assistantSkillCountForSession(String sessionKey) {
return skills.length;
}
int get currentAssistantSkillCount =>
assistantSkillCountForSession(currentSessionKey);
List<String> assistantSelectedSkillKeysForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,
);
final importedKeys = assistantImportedSkillsForSession(
normalizedSessionKey,
).map((item) => item.key).toSet();
final selected =
assistantThreadRecordsInternal[normalizedSessionKey]
?.selectedSkillKeys ??
const <String>[];
final availableKeys = skills.map((item) => item.skillKey).toSet();
return selected
.where((item) => importedKeys.contains(item))
.where(availableKeys.contains)
.toList(growable: false);
}
List<AssistantThreadSkillEntry> assistantSelectedSkillsForSession(
String sessionKey,
) {
final selectedKeys = assistantSelectedSkillKeysForSession(
sessionKey,
).toSet();
return assistantImportedSkillsForSession(
sessionKey,
).where((item) => selectedKeys.contains(item.key)).toList(growable: false);
}
String assistantModelForSession(String sessionKey) {
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
sessionKey,

View File

@ -372,10 +372,8 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
if (normalizedSkillKey.isEmpty) {
return;
}
final importedKeys = assistantImportedSkillsForSession(
normalizedSessionKey,
).map((item) => item.key).toSet();
if (!importedKeys.contains(normalizedSkillKey)) {
final availableKeys = skills.map((item) => item.skillKey).toSet();
if (!availableKeys.contains(normalizedSkillKey)) {
return;
}
final nextSelected = List<String>.from(

View File

@ -37,58 +37,13 @@ import 'assistant_page_composer_skill_picker.dart';
import 'assistant_page_composer_clipboard.dart';
import 'assistant_page_components_core.dart';
const List<ComposerSkillOptionInternal> fallbackSkillOptionsInternal =
<ComposerSkillOptionInternal>[
ComposerSkillOptionInternal(
key: '1password',
label: '1password',
description: '安全读取和注入本地凭据。',
sourceLabel: 'Local',
icon: Icons.auto_awesome_rounded,
),
ComposerSkillOptionInternal(
key: 'xlsx',
label: 'xlsx',
description: '读取、整理和生成表格文件。',
sourceLabel: 'Local',
icon: Icons.auto_awesome_rounded,
),
ComposerSkillOptionInternal(
key: 'web-processing',
label: '网页处理',
description: '打开网页、提取内容并完成网页操作。',
sourceLabel: 'Web',
icon: Icons.language_rounded,
),
ComposerSkillOptionInternal(
key: 'apple-reminders',
label: 'apple-reminders',
description: '管理提醒事项和任务提醒。',
sourceLabel: 'Local',
icon: Icons.auto_awesome_rounded,
),
ComposerSkillOptionInternal(
key: 'blogwatcher',
label: 'blogwatcher',
description: '跟踪博客更新并生成摘要。',
sourceLabel: 'Local',
icon: Icons.auto_awesome_rounded,
),
];
ComposerSkillOptionInternal skillOptionFromGatewayInternal(
GatewaySkillSummary skill,
) {
final normalizedKey = skill.skillKey.trim().toLowerCase();
final normalizedName = skill.name.trim().toLowerCase();
final isWebSkill =
normalizedKey.contains('browser') ||
normalizedKey.contains('open-link') ||
normalizedKey.contains('web') ||
normalizedName.contains('browser') ||
normalizedName.contains('网页');
final label = isWebSkill ? '网页处理' : skill.name.trim();
final key = isWebSkill ? 'web-processing' : normalizedKey;
final key = skill.skillKey.trim().isEmpty
? skill.name.trim().toLowerCase()
: skill.skillKey.trim();
final label = skill.name.trim().isEmpty ? key : skill.name.trim();
final sourceLabel = skill.source.trim().isEmpty ? 'Gateway' : skill.source;
final description = skill.description.trim().isEmpty
? appText('可在当前任务中调用的技能。', 'Skill available in the current task.')
@ -99,22 +54,6 @@ ComposerSkillOptionInternal skillOptionFromGatewayInternal(
label: label,
description: description,
sourceLabel: sourceLabel,
icon: isWebSkill ? Icons.language_rounded : Icons.auto_awesome_rounded,
);
}
ComposerSkillOptionInternal skillOptionFromThreadSkillInternal(
AssistantThreadSkillEntry skill,
) {
return ComposerSkillOptionInternal(
key: skill.key,
label: skill.label.trim().isEmpty ? skill.key : skill.label.trim(),
description: skill.description.trim().isEmpty
? appText('已绑定到当前线程的本地技能。', 'Local skill bound to this thread.')
: skill.description.trim(),
sourceLabel: skill.sourceLabel.trim().isEmpty
? skill.sourcePath
: skill.sourceLabel.trim(),
icon: Icons.auto_awesome_rounded,
);
}

View File

@ -262,17 +262,20 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
addOption(option);
}
for (final option in fallbackSkillOptionsInternal) {
addOption(option);
}
return options;
}
List<String> selectedSkillKeysForInternal(AppController controller) {
return controller.assistantSelectedSkillKeysForSession(
controller.currentSessionKey,
);
final selected =
controller.taskThreadForSessionInternal(controller.currentSessionKey)
?.selectedSkillKeys ??
const <String>[];
final availableKeys = availableSkillOptionsInternal(controller)
.map((option) => option.key)
.toSet();
return selected
.where(availableKeys.contains)
.toList(growable: false);
}
List<String> resolveSelectedSkillLabelsInternal(AppController controller) {

View File

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:xworkmate/app/app_controller.dart';
import 'package:xworkmate/app/app_controller_desktop_external_acp_routing.dart';
import 'package:xworkmate/features/assistant/assistant_page_composer_skill_models.dart';
import 'package:xworkmate/runtime/go_task_service_client.dart';
import 'package:xworkmate/runtime/runtime_models.dart';
import 'package:xworkmate/runtime/secure_config_store.dart';
@ -213,6 +214,30 @@ void main() {
},
);
test(
'bridge skill summaries preserve bridge key and name without remap',
() {
final option = skillOptionFromGatewayInternal(
const GatewaySkillSummary(
name: 'Browser Fetch',
description: 'Bridge-managed browser skill',
source: 'bridge',
skillKey: 'browser-fetch',
primaryEnv: null,
eligible: true,
disabled: false,
missingBins: <String>[],
missingEnv: <String>[],
missingConfig: <String>[],
),
);
expect(option.key, 'browser-fetch');
expect(option.label, 'Browser Fetch');
expect(option.description, 'Bridge-managed browser skill');
},
);
test(
'locks the gateway provider catalog to the canonical openclaw contract',
() {