refactor: remove multi-agent orchestration subsystem (Path B)

Remove the entire multi-agent collaboration execution path, including:
- MultiAgentOrchestrator and its 4-phase pipeline (Architect→Engineer→Tester→Iteration)
- ARIS framework preset and mount infrastructure
- Hardcoded model defaults (kimi-k2.5, minimax-m2.7, glm-5)
- Deprecated runCliPromptInternal() and its fallback call chain
- All related types: MultiAgentConfig, AgentWorkerConfig, MultiAgentRole, etc.

This collapses the architecture to a single clean path:
Flutter → GoTaskServiceClient → ACP Transport → Go Bridge → Remote Execution

2886 lines removed across 41 files.
This commit is contained in:
Cowork 3P 2026-06-04 05:34:58 +00:00
parent db956dba2f
commit a908b5118f
41 changed files with 8 additions and 2886 deletions

View File

@ -33,12 +33,9 @@ import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/external_code_agent_acp_desktop_transport.dart';
import '../runtime/go_task_service_client.dart';
import '../runtime/go_task_service_desktop_service.dart';
import '../runtime/go_multi_agent_mount_desktop_client.dart';
import '../runtime/go_runtime_dispatch_desktop_client.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_mounts.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'task_thread_repositories.dart';
import 'app_controller_openclaw_task_queue.dart';
@ -71,7 +68,6 @@ class AppController extends ChangeNotifier {
AccountRuntimeClient Function(String baseUrl)? accountClientFactory,
Map<String, String>? environmentOverride,
GoTaskServiceClient? goTaskServiceClient,
MultiAgentMountManager? multiAgentMountManager,
}) {
environmentOverrideInternal = environmentOverride == null
? null
@ -165,19 +161,6 @@ class AppController extends ChangeNotifier {
taskEndpointResolver: resolveExternalAcpEndpointForRequestInternal,
),
);
multiAgentOrchestratorInternal = MultiAgentOrchestrator(
config: resolveMultiAgentConfigInternal(
settingsControllerInternal.snapshot,
),
);
multiAgentMountManagerInternal =
multiAgentMountManager ??
MultiAgentMountManager(
resolver: GoMultiAgentMountDesktopClient(
client: gatewayAcpClientInternal,
endpointResolver: resolveGatewayAcpEndpointInternal,
),
);
bridgeAgentProviderCatalogInternal = normalizeSingleAgentProviderList(
initialBridgeProviderCatalog ?? const <SingleAgentProvider>[],
);
@ -223,7 +206,6 @@ class AppController extends ChangeNotifier {
tasksControllerInternal.dispose();
storeInternal.dispose();
desktopPlatformServiceInternal.dispose();
unawaited(multiAgentMountManagerInternal.dispose());
unawaited(goTaskServiceClientInternal.dispose());
unawaited(gatewayAcpClientInternal.dispose());
super.dispose();
@ -249,9 +231,6 @@ class AppController extends ChangeNotifier {
late final DesktopPlatformService desktopPlatformServiceInternal;
late final GatewayAcpClient gatewayAcpClientInternal;
late final GoTaskServiceClient goTaskServiceClientInternal;
late final MultiAgentOrchestrator multiAgentOrchestratorInternal;
late final MultiAgentMountManager multiAgentMountManagerInternal;
GoTaskServiceClient get goTaskServiceClientForTest =>
goTaskServiceClientInternal;
@ -291,7 +270,6 @@ class AppController extends ChangeNotifier {
<String, OpenClawGatewayQueuedTurnInternal>{};
int get openClawGatewayActiveTasksInternal =>
openClawGatewayActiveTurnsInternal.length;
bool multiAgentRunPendingInternal = false;
int localMessageCounterInternal = 0;
int assistantDraftSessionCounterInternal = 0;
@ -370,10 +348,6 @@ class AppController extends ChangeNotifier {
GatewayAgentsController get agentsController => agentsControllerInternal;
GatewaySessionsController get sessionsController =>
sessionsControllerInternal;
MultiAgentOrchestrator get multiAgentOrchestrator =>
multiAgentOrchestratorInternal;
MultiAgentMountManager get multiAgentMountManager =>
multiAgentMountManagerInternal;
GatewayChatController get chatController => chatControllerInternal;
SkillsController get skillsController => skillsControllerInternal;
ModelsController get modelsController => modelsControllerInternal;
@ -618,11 +592,6 @@ class AppController extends ChangeNotifier {
selectedSkillLabels: selectedSkillLabels,
);
Future<void> refreshMultiAgentMounts({bool sync = false}) =>
AppControllerDesktopThreadSessions(
this,
).refreshMultiAgentMounts(sync: sync);
double get assistantSkillCount => skills.length.toDouble();
int get currentAssistantSkillCount => skills.length;
}

View File

@ -30,7 +30,6 @@ import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/go_task_service_client.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_thread_sessions.dart';

View File

@ -29,7 +29,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import '../runtime/account_runtime_client.dart';
import 'app_controller_desktop_core.dart';

View File

@ -29,7 +29,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_gateway.dart';

View File

@ -29,7 +29,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
@ -73,26 +72,6 @@ Future<void> refreshAcpCapabilitiesRuntimeInternal(
.toString()
.trim();
}
if (persistMountTargets && !controller.disposedInternal) {
try {
final currentConfig = controller.settings.multiAgent;
final nextConfig = await controller.multiAgentMountManagerInternal
.reconcile(
config: currentConfig,
aiGatewayUrl: controller.aiGatewayUrl,
);
if (jsonEncode(nextConfig.toJson()) !=
jsonEncode(currentConfig.toJson())) {
await controller.settingsControllerInternal.saveSnapshot(
controller.settings.copyWith(multiAgent: nextConfig),
);
controller.multiAgentOrchestratorInternal.updateConfig(nextConfig);
}
} catch (_) {
// Mount reconciliation is an optional bridge capability. A missing or
// older remote method must not block assistant startup or task execution.
}
}
if (!controller.disposedInternal) {
controller.notifyListeners();
}
@ -122,46 +101,6 @@ Future<void> refreshSingleAgentCapabilitiesRuntimeInternal(
}
}
List<ManagedMountTargetState>
mergeAcpCapabilitiesIntoMountTargetsRuntimeInternal(
AppController controller,
List<ManagedMountTargetState> current,
GatewayAcpCapabilities capabilities,
) {
final source = current.isEmpty ? ManagedMountTargetState.defaults() : current;
final agentProviders = capabilities.providerCatalog
.map((item) => item.providerId)
.toSet();
final gatewayProviders = capabilities.gatewayProviderCatalog
.map((item) => item.providerId)
.toSet();
return source
.map((item) {
final available = switch (item.targetId) {
'codex' => agentProviders.contains('codex'),
'opencode' => agentProviders.contains('opencode'),
'gemini' => agentProviders.contains('gemini'),
'openclaw' => gatewayProviders.contains('openclaw'),
_ => false,
};
return item.copyWith(
available: available,
discoveryState: available ? 'ready' : 'unavailable',
syncState: available ? item.syncState : 'idle',
detail: available
? appText(
'来源Gateway ACP capabilities',
'Source: Gateway ACP capabilities',
)
: appText(
'Gateway ACP 未报告该能力。',
'Gateway ACP did not report this capability.',
),
);
})
.toList(growable: false);
}
String? assistantWorkingDirectoryForSessionRuntimeInternal(
AppController controller,
String sessionKey,

View File

@ -33,7 +33,6 @@ import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/go_task_service_client.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
@ -602,15 +601,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
forceRefresh: forceRefresh,
);
List<ManagedMountTargetState> mergeAcpCapabilitiesIntoMountTargetsInternal(
List<ManagedMountTargetState> current,
GatewayAcpCapabilities capabilities,
) => mergeAcpCapabilitiesIntoMountTargetsRuntimeInternal(
this,
current,
capabilities,
);
String? assistantWorkingDirectoryForSessionInternal(String sessionKey) =>
assistantWorkingDirectoryForSessionRuntimeInternal(this, sessionKey);
@ -681,7 +671,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
cronJobsControllerInternal.addListener(relayChildChangeInternal);
devicesControllerInternal.addListener(relayChildChangeInternal);
tasksControllerInternal.addListener(relayChildChangeInternal);
multiAgentOrchestratorInternal.addListener(relayChildChangeInternal);
}
void detachChildListenersInternal() {
@ -696,7 +685,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
cronJobsControllerInternal.removeListener(relayChildChangeInternal);
devicesControllerInternal.removeListener(relayChildChangeInternal);
tasksControllerInternal.removeListener(relayChildChangeInternal);
multiAgentOrchestratorInternal.removeListener(relayChildChangeInternal);
}
void handleSettingsControllerChangeInternal() {
@ -737,7 +725,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
return;
}
setActiveAppLanguage(current.appLanguage);
multiAgentOrchestratorInternal.updateConfig(current.multiAgent);
if (previous.codeAgentRuntimeMode != current.codeAgentRuntimeMode) {
registerCodexExternalProviderInternal();
if (disposedInternal) {

View File

@ -30,7 +30,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
@ -64,10 +63,8 @@ extension AppControllerDesktopSettings on AppController {
return;
}
settingsDraftInternal = sanitizeFeatureFlagSettingsInternal(
sanitizeMultiAgentSettingsInternal(
sanitizeOllamaCloudSettingsInternal(
sanitizeCodeAgentSettingsInternal(snapshot),
),
sanitizeOllamaCloudSettingsInternal(
sanitizeCodeAgentSettingsInternal(snapshot),
),
);
settingsDraftInitializedInternal = true;
@ -304,7 +301,6 @@ extension AppControllerDesktopSettings on AppController {
openClawGatewayQueuedTurnsInternal.clear();
openClawGatewayQueuedTurnsBySessionInternal.clear();
openClawGatewayActiveTurnsInternal.clear();
multiAgentRunPendingInternal = false;
final sessionKey = createAssistantDraftSessionKeyInternal();
initializeAssistantThreadContext(
sessionKey,

View File

@ -31,7 +31,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
@ -434,11 +433,9 @@ extension AppControllerDesktopSettingsRuntime on AppController {
}
}
final normalized = sanitizeFeatureFlagSettingsInternal(
sanitizeMultiAgentSettingsInternal(
sanitizeOllamaCloudSettingsInternal(
sanitizeCodeAgentSettingsInternal(
settingsControllerInternal.snapshot,
),
sanitizeOllamaCloudSettingsInternal(
sanitizeCodeAgentSettingsInternal(
settingsControllerInternal.snapshot,
),
),
);
@ -460,7 +457,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
}
lastObservedSettingsSnapshotInternal = settings;
modelsControllerInternal.restoreFromSettings(settings.aiGateway);
multiAgentOrchestratorInternal.updateConfig(settings.multiAgent);
setActiveAppLanguage(settings.appLanguage);
await desktopPlatformServiceInternal.initialize(settings.linuxDesktop);
await desktopPlatformServiceInternal.setLaunchAtLogin(
@ -670,10 +666,8 @@ extension AppControllerDesktopSettingsRuntime on AppController {
SettingsSnapshot snapshot,
) async {
final sanitized = sanitizeFeatureFlagSettingsInternal(
sanitizeMultiAgentSettingsInternal(
sanitizeOllamaCloudSettingsInternal(
sanitizeCodeAgentSettingsInternal(snapshot),
),
sanitizeOllamaCloudSettingsInternal(
sanitizeCodeAgentSettingsInternal(snapshot),
),
);
lastObservedSettingsSnapshotInternal = sanitized;
@ -688,7 +682,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
required bool refreshAfterSave,
}) async {
setActiveAppLanguage(current.appLanguage);
multiAgentOrchestratorInternal.updateConfig(current.multiAgent);
agentsControllerInternal.restoreSelection(
current
.gatewayProfileForExecutionTarget(

View File

@ -29,7 +29,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';

View File

@ -31,7 +31,6 @@ import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/go_task_service_client.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_openclaw_task_queue.dart';
import 'app_controller_desktop_core.dart';
@ -74,11 +73,7 @@ extension AppControllerDesktopThreadActions on AppController {
(turn) => !turn.cancelled,
) ==
true ||
(multiAgentRunPendingInternal &&
matchesSessionKey(
normalized,
sessionsControllerInternal.currentSessionKey,
));
false;
}
Future<void> connectSavedGateway() async {
@ -1505,39 +1500,6 @@ extension AppControllerDesktopThreadActions on AppController {
}
Future<void> abortRun() async {
if (multiAgentRunPendingInternal) {
final sessionKey = normalizedAssistantSessionKeyInternal(
sessionsControllerInternal.currentSessionKey,
);
try {
await goTaskServiceClientInternal.cancelTask(
route: GoTaskServiceRoute.externalAcpMulti,
target: assistantExecutionTargetForSession(sessionKey),
sessionId: sessionKey,
threadId: sessionKey,
association: taskThreadForSessionInternal(
sessionKey,
)?.openClawTaskAssociation,
);
} catch (_) {
// Best effort cancellation only.
}
multiAgentRunPendingInternal = false;
upsertTaskThreadInternal(
sessionKey,
lifecycleStatus: 'ready',
lastRunAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
lastResultCode: 'aborted',
lastRemoteWorkingDirectory: '',
lastArtifactSyncAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
lastArtifactSyncStatus: 'failed',
lastTaskArtifactRelativePaths: const <String>[],
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
recomputeTasksInternal();
notifyIfActiveInternal();
return;
}
final sessionKey = normalizedAssistantSessionKeyInternal(
sessionsControllerInternal.currentSessionKey,
);

View File

@ -29,7 +29,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';

View File

@ -29,7 +29,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
@ -481,22 +480,6 @@ extension AppControllerDesktopThreadSessions on AppController {
Future<String> loadAiGatewayApiKey() =>
loadAiGatewayApiKeyThreadSessionInternal(this);
Future<void> saveMultiAgentConfig(MultiAgentConfig config) =>
saveMultiAgentConfigThreadSessionInternal(this, config);
Future<void> refreshMultiAgentMounts({bool sync = false}) =>
refreshMultiAgentMountsThreadSessionInternal(this, sync: sync);
Future<void> runMultiAgentCollaboration({
required String rawPrompt,
required String composedPrompt,
required List<CollaborationAttachment> attachments,
required List<String> selectedSkillLabels,
}) => runMultiAgentCollaborationThreadSessionInternal(
this,
rawPrompt: rawPrompt,
composedPrompt: composedPrompt,
attachments: attachments,
selectedSkillLabels: selectedSkillLabels,
);
Future<void> openOnlineWorkspace() =>
openOnlineWorkspaceThreadSessionInternal(this);
List<String> get aiGatewayModelChoices =>

View File

@ -30,7 +30,6 @@ import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/go_task_service_client.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
@ -51,241 +50,6 @@ Future<String> loadAiGatewayApiKeyThreadSessionInternal(
return controller.settingsControllerInternal.loadEffectiveAiGatewayApiKey();
}
Future<void> saveMultiAgentConfigThreadSessionInternal(
AppController controller,
MultiAgentConfig config,
) async {
final resolved = controller.resolveMultiAgentConfigInternal(
controller.settings.copyWith(multiAgent: config),
);
await AppControllerDesktopSettings(controller).saveSettings(
controller.settings.copyWith(multiAgent: resolved),
refreshAfterSave: false,
);
await refreshMultiAgentMountsThreadSessionInternal(
controller,
sync: resolved.autoSync,
);
}
Future<void> refreshMultiAgentMountsThreadSessionInternal(
AppController controller, {
bool sync = false,
}) async {
final currentConfig = controller.settings.multiAgent;
final effectiveConfig = currentConfig.copyWith(autoSync: sync);
var nextConfig = await controller.multiAgentMountManagerInternal.reconcile(
config: effectiveConfig,
aiGatewayUrl: controller.aiGatewayUrl,
);
if (nextConfig.autoSync != currentConfig.autoSync) {
nextConfig = nextConfig.copyWith(autoSync: currentConfig.autoSync);
}
if (jsonEncode(nextConfig.toJson()) != jsonEncode(currentConfig.toJson())) {
await controller.settingsControllerInternal.saveSnapshot(
controller.settings.copyWith(multiAgent: nextConfig),
);
controller.multiAgentOrchestratorInternal.updateConfig(nextConfig);
}
await controller.refreshAcpCapabilitiesInternal(forceRefresh: true);
if (!controller.disposedInternal) {
controller.notifyListeners();
}
}
Future<void> runMultiAgentCollaborationThreadSessionInternal(
AppController controller, {
required String rawPrompt,
required String composedPrompt,
required List<CollaborationAttachment> attachments,
required List<String> selectedSkillLabels,
}) async {
if (!controller.isAppOwnedAssistantSessionKeyInternal(
controller.currentSessionKey,
)) {
await controller.ensureActiveAssistantThreadInternal();
}
final sessionKey = controller.currentSessionKey.trim();
await controller.enqueueThreadTurnInternal<void>(sessionKey, () async {
await controller.ensureDesktopTaskThreadBindingInternal(
sessionKey,
executionTarget: controller.assistantExecutionTargetForSession(
sessionKey,
),
);
final workingDirectory = controller
.assistantWorkingDirectoryForSessionInternal(sessionKey);
final remoteWorkingDirectoryHint = controller
.assistantRemoteWorkingDirectoryHintForSessionInternal(sessionKey);
if (workingDirectory == null || workingDirectory.trim().isEmpty) {
final error = StateError(
appText(
'当前线程缺少工作路径,无法启动 Gateway 协作。',
'This thread has no workspace path, so gateway collaboration cannot start.',
),
);
controller.appendLocalSessionMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'assistant',
text: error.message.toString(),
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: 'Multi-Agent',
stopReason: null,
pending: false,
error: true,
),
);
controller.recomputeTasksInternal();
controller.notifyIfActiveInternal();
throw error;
}
controller.multiAgentRunPendingInternal = true;
controller.appendLocalSessionMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'user',
text: rawPrompt,
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: null,
stopReason: null,
pending: false,
error: false,
),
);
controller.recomputeTasksInternal();
try {
final taskPrompt = controller.taskWorkspaceContextPromptInternal(
sessionKey: sessionKey,
userPrompt: composedPrompt,
workingDirectory: workingDirectory,
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint?.trim() ?? '',
target: controller.assistantExecutionTargetForSession(sessionKey),
);
final result = await controller.goTaskServiceClientInternal.executeTask(
GoTaskServiceRequest(
sessionId: sessionKey,
threadId: sessionKey,
target: controller.assistantExecutionTargetForSession(sessionKey),
prompt: taskPrompt,
workingDirectory: workingDirectory,
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint?.trim() ?? '',
model: controller.assistantModelForSession(sessionKey),
thinking: 'medium',
selectedSkills: selectedSkillLabels,
inlineAttachments: const <GatewayChatAttachmentPayload>[],
localAttachments: attachments,
agentId: '',
metadata: const <String, dynamic>{},
routingHint: 'gateway',
collaborationMode: GoTaskServiceCollaborationMode.standard,
resumeSession: true,
),
onUpdate: (update) {
final text = update.text.trim().isNotEmpty
? update.text.trim()
: update.message.trim();
if (text.isEmpty) {
return;
}
controller.appendLocalSessionMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'assistant',
text: text,
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: 'Multi-Agent',
stopReason: null,
pending: update.pending,
error: update.error,
),
);
},
);
await controller.persistGoTaskArtifactsForSessionInternal(
sessionKey,
result,
);
controller.upsertTaskThreadInternal(
sessionKey,
lastRemoteWorkingDirectory:
result.remoteWorkingDirectory.trim().isNotEmpty
? result.remoteWorkingDirectory.trim()
: null,
lastRemoteWorkspaceRefKind: result.remoteWorkspaceRefKind,
updatedAtMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
);
controller.appendLocalSessionMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'assistant',
text: result.success
? (result.message.trim().isNotEmpty
? result.message.trim()
: appText(
'多 Agent 协作完成。',
'Multi-agent collaboration completed.',
))
: appText(
'多 Agent 协作失败:${result.errorMessage.trim().isEmpty ? result.message : result.errorMessage}',
'Multi-agent collaboration failed: ${result.errorMessage.trim().isEmpty ? result.message : result.errorMessage}',
),
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: 'Multi-Agent',
stopReason: null,
pending: false,
error: !result.success,
),
);
} on GatewayAcpException catch (error) {
controller.appendLocalSessionMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'assistant',
text: appText(
'多 Agent 协作不可用ACP${error.message}',
'Multi-agent collaboration is unavailable (ACP): ${error.message}',
),
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: 'Multi-Agent',
stopReason: null,
pending: false,
error: true,
),
);
} catch (error) {
controller.appendLocalSessionMessageInternal(
sessionKey,
GatewayChatMessage(
id: controller.nextLocalMessageIdInternal(),
role: 'assistant',
text: error.toString(),
timestampMs: DateTime.now().millisecondsSinceEpoch.toDouble(),
toolCallId: null,
toolName: 'Multi-Agent',
stopReason: null,
pending: false,
error: true,
),
);
} finally {
controller.multiAgentRunPendingInternal = false;
controller.recomputeTasksInternal();
controller.notifyIfActiveInternal();
}
});
}
Future<void> openOnlineWorkspaceThreadSessionInternal(
AppController controller,
) async {

View File

@ -29,7 +29,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
@ -166,17 +165,6 @@ extension AppControllerDesktopThreadStorage on AppController {
}
}
SettingsSnapshot sanitizeMultiAgentSettingsInternal(
SettingsSnapshot snapshot,
) {
final resolved = resolveMultiAgentConfigInternal(snapshot);
if (jsonEncode(snapshot.multiAgent.toJson()) ==
jsonEncode(resolved.toJson())) {
return snapshot;
}
return snapshot.copyWith(multiAgent: resolved);
}
SettingsSnapshot sanitizeFeatureFlagSettingsInternal(
SettingsSnapshot snapshot,
) {
@ -184,9 +172,6 @@ extension AppControllerDesktopThreadStorage on AppController {
final sanitizedExecutionTarget = sanitizeExecutionTargetInternal(
features.sanitizeExecutionTarget(snapshot.assistantExecutionTarget),
);
final multiAgentConfig = features.supportsMultiAgent
? snapshot.multiAgent
: snapshot.multiAgent.copyWith(enabled: false);
final experimentalCanvas =
features.allowsExperimentalSetting(
UiFeatureKeys.settingsExperimentalCanvas,
@ -207,7 +192,6 @@ extension AppControllerDesktopThreadStorage on AppController {
: false;
return snapshot.copyWith(
assistantExecutionTarget: sanitizedExecutionTarget,
multiAgent: multiAgentConfig,
experimentalCanvas: experimentalCanvas,
experimentalBridge: experimentalBridge,
experimentalDebug: experimentalDebug,
@ -271,39 +255,6 @@ extension AppControllerDesktopThreadStorage on AppController {
return sanitizeExecutionTargetInternal(target);
}
MultiAgentConfig resolveMultiAgentConfigInternal(SettingsSnapshot snapshot) {
final defaults = MultiAgentConfig.defaults();
final current = snapshot.multiAgent;
final ollamaEndpoint = snapshot.ollamaLocal.endpoint.trim().isEmpty
? current.ollamaEndpoint
: snapshot.ollamaLocal.endpoint.trim();
final engineerModel = current.engineer.model.trim().isNotEmpty
? current.engineer.model.trim()
: snapshot.ollamaLocal.defaultModel.trim().isNotEmpty
? snapshot.ollamaLocal.defaultModel.trim()
: defaults.engineer.model;
final architectModel = current.architect.model.trim().isNotEmpty
? current.architect.model.trim()
: defaults.architect.model;
final testerModel = current.tester.model.trim().isNotEmpty
? current.tester.model.trim()
: defaults.tester.model;
return current.copyWith(
framework: current.arisEnabled
? MultiAgentFramework.aris
: current.framework,
arisEnabled:
current.framework == MultiAgentFramework.aris || current.arisEnabled,
ollamaEndpoint: ollamaEndpoint,
architect: current.architect.copyWith(model: architectModel),
engineer: current.engineer.copyWith(model: engineerModel),
tester: current.tester.copyWith(model: testerModel),
mountTargets: current.mountTargets.isEmpty
? MultiAgentConfig.defaults().mountTargets
: current.mountTargets,
);
}
void appendAssistantThreadMessageInternal(
String sessionKey,
GatewayChatMessage message,

View File

@ -29,7 +29,6 @@ import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -15,7 +15,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -15,7 +15,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';
@ -154,15 +153,6 @@ extension AssistantPageStateActionsInternal on AssistantPageStateInternal {
sessionKey: submittedSessionKey,
thinking: thinkingLabelInternal,
attachments: attachmentPayloads,
localAttachments: submittedAttachments
.map(
(item) => CollaborationAttachment(
name: item.name,
description: item.mimeType,
path: item.path,
),
)
.toList(growable: false),
selectedSkillLabels: selectedSkillLabels,
);
clearComposerDraftForSessionInternal(submittedSessionKey);

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/gateway_acp_client.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -16,7 +16,6 @@ import '../../app/app_metadata.dart';
import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/multi_agent_orchestrator.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';

View File

@ -1,43 +0,0 @@
import 'dart:async';
class ArisLlmChatClient {
ArisLlmChatClient({Duration rpcTimeout = const Duration(minutes: 2)});
Future<String> chat({
required String endpoint,
required String apiKey,
required String model,
required String prompt,
String systemPrompt = '',
}) {
return _callTool(
toolName: 'chat',
environment: <String, String>{},
arguments: <String, dynamic>{},
);
}
Future<String> claudeReview({
required String prompt,
String model = '',
String systemPrompt = '',
String tools = '',
}) {
return _callTool(
toolName: 'claude_review',
environment: <String, String>{},
arguments: <String, dynamic>{},
);
}
Future<String> _callTool({
required String toolName,
required Map<String, String> environment,
required Map<String, dynamic> arguments,
}) async {
// Local Go core execution is deprecated in favor of bridge-mediated execution.
throw UnsupportedError(
'Local Go core execution is disabled. Use the managed bridge ACP runtime instead.',
);
}
}

View File

@ -1,77 +0,0 @@
import 'gateway_acp_client.dart';
import 'multi_agent_mount_resolver.dart';
import 'runtime_models.dart';
class GoMultiAgentMountDesktopClient implements MultiAgentMountResolver {
GoMultiAgentMountDesktopClient({
required GatewayAcpClient client,
required Uri? Function() endpointResolver,
}) : _client = client,
_endpointResolver = endpointResolver;
final GatewayAcpClient _client;
final Uri? Function() _endpointResolver;
@override
Future<MultiAgentConfig?> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
required String codexHome,
required String opencodeHome,
required ArisMountProbe arisProbe,
}) async {
final response = await _client.request(
method: 'xworkmate.mounts.reconcile',
params: <String, dynamic>{
'config': <String, dynamic>{
'autoSync': config.autoSync,
'usesAris': config.usesAris,
'managedMcpServers': config.managedMcpServers
.map((item) => item.toJson())
.toList(growable: false),
},
'aiGatewayUrl': aiGatewayUrl.trim(),
'codexHome': codexHome.trim(),
'opencodeHome': opencodeHome.trim(),
'aris': arisProbe.toJson(),
},
endpointOverride: _endpointResolver(),
);
final result = _castMap(response['result']);
final rawTargets = result['mountTargets'];
final mountTargets = rawTargets is List
? rawTargets
.whereType<Map>()
.map(
(item) => ManagedMountTargetState.fromJson(
item.cast<String, dynamic>(),
),
)
.toList(growable: false)
: config.mountTargets;
return config.copyWith(
mountTargets: mountTargets,
arisBundleVersion:
result['arisBundleVersion']?.toString().trim().isNotEmpty == true
? result['arisBundleVersion'].toString().trim()
: config.arisBundleVersion,
arisCompatStatus:
result['arisCompatStatus']?.toString().trim().isNotEmpty == true
? result['arisCompatStatus'].toString().trim()
: config.arisCompatStatus,
);
}
@override
Future<void> dispose() => _client.dispose();
Map<String, dynamic> _castMap(Object? value) {
if (value is Map<String, dynamic>) {
return value;
}
if (value is Map) {
return value.cast<String, dynamic>();
}
return const <String, dynamic>{};
}
}

View File

@ -1,74 +0,0 @@
import 'runtime_models.dart';
abstract class FrameworkPreset {
const FrameworkPreset();
String get id;
String get label;
Future<String> roleInstructionBlock({
required MultiAgentRole role,
required String tool,
required List<String> selectedSkills,
});
}
class NativeFrameworkPreset extends FrameworkPreset {
const NativeFrameworkPreset();
@override
String get id => MultiAgentFramework.native.name;
@override
String get label => MultiAgentFramework.native.label;
@override
Future<String> roleInstructionBlock({
required MultiAgentRole role,
required String tool,
required List<String> selectedSkills,
}) async {
final selected = selectedSkills.isEmpty
? '- 无'
: selectedSkills.map((item) => '- $item').join('\n');
return '''
$label
${role.label}
$tool
$selected
''';
}
}
class ArisFrameworkPreset extends FrameworkPreset {
const ArisFrameworkPreset();
@override
String get id => MultiAgentFramework.aris.name;
@override
String get label => MultiAgentFramework.aris.label;
@override
Future<String> roleInstructionBlock({
required MultiAgentRole role,
required String tool,
required List<String> selectedSkills,
}) async {
// ARIS data has been removed from assets.
// Fallback to basic instruction.
final selected = selectedSkills.isEmpty
? '- 无'
: selectedSkills.map((item) => '- $item').join('\n');
return '''
$label
${role.label}
$tool
$selected
''';
}
}

View File

@ -1,49 +0,0 @@
import 'runtime_models.dart';
class ArisMountProbe {
const ArisMountProbe({
required this.available,
required this.bundleVersion,
required this.llmChatServerPath,
required this.skillCount,
required this.bridgeAvailable,
this.error = '',
});
const ArisMountProbe.unavailable({this.error = ''})
: available = false,
bundleVersion = '',
llmChatServerPath = '',
skillCount = 0,
bridgeAvailable = false;
final bool available;
final String bundleVersion;
final String llmChatServerPath;
final int skillCount;
final bool bridgeAvailable;
final String error;
Map<String, dynamic> toJson() {
return <String, dynamic>{
'available': available,
'bundleVersion': bundleVersion,
'llmChatServerPath': llmChatServerPath,
'skillCount': skillCount,
'bridgeAvailable': bridgeAvailable,
'error': error,
};
}
}
abstract class MultiAgentMountResolver {
Future<MultiAgentConfig?> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
required String codexHome,
required String opencodeHome,
required ArisMountProbe arisProbe,
});
Future<void> dispose();
}

View File

@ -1,310 +0,0 @@
import 'codex_config_bridge.dart';
import 'multi_agent_mount_resolver.dart';
import 'opencode_config_bridge.dart';
import 'runtime_models.dart';
///
///
///
class MultiAgentMountManager {
MultiAgentMountManager({
CodexConfigBridge? codexConfigBridge,
OpencodeConfigBridge? opencodeConfigBridge,
MultiAgentMountResolver? resolver,
}) : this._(
codexConfigBridge: codexConfigBridge ?? CodexConfigBridge(),
opencodeConfigBridge: opencodeConfigBridge ?? OpencodeConfigBridge(),
resolver: resolver,
);
MultiAgentMountManager._({
required CodexConfigBridge codexConfigBridge,
required OpencodeConfigBridge opencodeConfigBridge,
MultiAgentMountResolver? resolver,
}) : _codexConfigBridge = codexConfigBridge,
_opencodeConfigBridge = opencodeConfigBridge,
_resolver = resolver,
_adapters = <CliMountAdapter>[
CodexMountAdapter(codexConfigBridge),
ClaudeMountAdapter(),
GeminiMountAdapter(),
OpencodeMountAdapter(opencodeConfigBridge),
OpenClawMountAdapter(),
];
final CodexConfigBridge _codexConfigBridge;
final OpencodeConfigBridge _opencodeConfigBridge;
final MultiAgentMountResolver? _resolver;
final List<CliMountAdapter> _adapters;
Future<MultiAgentConfig> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
}) async {
final resolved = await _resolver?.reconcile(
config: config,
aiGatewayUrl: aiGatewayUrl,
codexHome: _codexConfigBridge.codexHome,
opencodeHome: _opencodeConfigBridge.opencodeHome,
arisProbe: await _buildArisProbe(),
);
if (resolved != null) {
return resolved;
}
return _reconcileLocally(config: config, aiGatewayUrl: aiGatewayUrl);
}
Future<void> dispose() async {
await _resolver?.dispose();
}
Future<ArisMountProbe> _buildArisProbe() async {
return const ArisMountProbe(
available: false,
bundleVersion: '',
llmChatServerPath: '',
skillCount: 0,
bridgeAvailable: false,
error: 'Legacy local agent execution is disabled.',
);
}
Future<MultiAgentConfig> _reconcileLocally({
required MultiAgentConfig config,
required String aiGatewayUrl,
}) async {
final states = <ManagedMountTargetState>[];
for (final adapter in _adapters) {
states.add(
await adapter.reconcile(config: config, aiGatewayUrl: aiGatewayUrl),
);
}
return config.copyWith(
mountTargets: states,
arisBundleVersion: '',
arisCompatStatus: 'missing',
);
}
}
abstract class CliMountAdapter {
String get targetId;
String get label;
bool get supportsSkills;
bool get supportsMcp;
bool get supportsAiGatewayInjection;
Future<bool> isInstalled();
Future<ManagedMountTargetState> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
});
int countMcpTomlSections(String content) {
return RegExp(
r'^\[mcp_servers\.[^\]]+\]',
multiLine: true,
).allMatches(content).length;
}
}
class CodexMountAdapter extends CliMountAdapter {
CodexMountAdapter(CodexConfigBridge bridge);
@override
String get targetId => 'codex';
@override
String get label => 'Codex';
@override
bool get supportsSkills => true;
@override
bool get supportsMcp => true;
@override
bool get supportsAiGatewayInjection => true;
@override
Future<bool> isInstalled() async => false;
@override
Future<ManagedMountTargetState> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
}) async {
return ManagedMountTargetState.placeholder(
targetId: targetId,
label: label,
supportsSkills: supportsSkills,
supportsMcp: supportsMcp,
supportsAiGatewayInjection: supportsAiGatewayInjection,
).copyWith(
available: false,
discoveryState: 'missing',
syncState: 'missing',
detail:
'Local CLI interaction is disabled. Use bridge for orchestration.',
);
}
}
class ClaudeMountAdapter extends CliMountAdapter {
@override
String get targetId => 'claude';
@override
String get label => 'Claude';
@override
bool get supportsSkills => true;
@override
bool get supportsMcp => true;
@override
bool get supportsAiGatewayInjection => true;
@override
Future<bool> isInstalled() async => false;
@override
Future<ManagedMountTargetState> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
}) async {
return ManagedMountTargetState.placeholder(
targetId: targetId,
label: label,
supportsSkills: supportsSkills,
supportsMcp: supportsMcp,
supportsAiGatewayInjection: supportsAiGatewayInjection,
).copyWith(
available: false,
discoveryState: 'missing',
syncState: 'disabled',
detail: 'Local CLI interaction is disabled.',
);
}
}
class GeminiMountAdapter extends CliMountAdapter {
@override
String get targetId => 'gemini';
@override
String get label => 'Gemini';
@override
bool get supportsSkills => true;
@override
bool get supportsMcp => true;
@override
bool get supportsAiGatewayInjection => true;
@override
Future<bool> isInstalled() async => false;
@override
Future<ManagedMountTargetState> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
}) async {
return ManagedMountTargetState.placeholder(
targetId: targetId,
label: label,
supportsSkills: supportsSkills,
supportsMcp: supportsMcp,
supportsAiGatewayInjection: supportsAiGatewayInjection,
).copyWith(
available: false,
discoveryState: 'missing',
syncState: 'disabled',
detail: 'Local CLI interaction is disabled.',
);
}
}
class OpencodeMountAdapter extends CliMountAdapter {
OpencodeMountAdapter(OpencodeConfigBridge bridge);
@override
String get targetId => 'opencode';
@override
String get label => 'OpenCode';
@override
bool get supportsSkills => true;
@override
bool get supportsMcp => true;
@override
bool get supportsAiGatewayInjection => true;
@override
Future<bool> isInstalled() async => false;
@override
Future<ManagedMountTargetState> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
}) async {
return ManagedMountTargetState.placeholder(
targetId: targetId,
label: label,
supportsSkills: supportsSkills,
supportsMcp: supportsMcp,
supportsAiGatewayInjection: supportsAiGatewayInjection,
).copyWith(
available: false,
discoveryState: 'missing',
syncState: 'missing',
detail: 'Local CLI interaction is disabled.',
);
}
}
class OpenClawMountAdapter extends CliMountAdapter {
@override
String get targetId => 'openclaw';
@override
String get label => 'OpenClaw';
@override
bool get supportsSkills => true;
@override
bool get supportsMcp => false;
@override
bool get supportsAiGatewayInjection => true;
@override
Future<bool> isInstalled() async => false;
@override
Future<ManagedMountTargetState> reconcile({
required MultiAgentConfig config,
required String aiGatewayUrl,
}) async {
return ManagedMountTargetState.placeholder(
targetId: targetId,
label: label,
supportsSkills: supportsSkills,
supportsMcp: supportsMcp,
supportsAiGatewayInjection: supportsAiGatewayInjection,
).copyWith(
available: false,
discoveryState: 'missing',
syncState: 'disabled',
detail: 'Local CLI interaction is disabled.',
);
}
}

View File

@ -1,4 +0,0 @@
export 'multi_agent_orchestrator_protocol.dart';
export 'multi_agent_orchestrator_workflow.dart';
export 'multi_agent_orchestrator_support.dart';
export 'multi_agent_orchestrator_core.dart';

View File

@ -1,384 +0,0 @@
// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'embedded_agent_launch_policy.dart';
import 'multi_agent_frameworks.dart';
import 'runtime_models.dart';
import 'multi_agent_orchestrator_protocol.dart';
import 'multi_agent_orchestrator_workflow.dart';
import 'multi_agent_orchestrator_support.dart';
/// Agent
///
/// Architect/ Lead Engineer Worker/Review worker +
///
///
/// ACP
class MultiAgentOrchestrator extends ChangeNotifier {
MultiAgentOrchestrator({
required MultiAgentConfig config,
HttpClient Function()? httpClientFactory,
}) : configInternal = config,
httpClientFactoryInternal = httpClientFactory ?? HttpClient.new;
///
MultiAgentConfig configInternal;
MultiAgentConfig get config => configInternal;
final HttpClient Function() httpClientFactoryInternal;
HttpClient? activeHttpClientInternal;
bool abortRequestedInternal = false;
///
bool collaborationEnabledInternal = false;
bool get collaborationEnabled => collaborationEnabledInternal;
///
bool isRunningInternal = false;
bool get isRunning => isRunningInternal;
///
String? lastErrorInternal;
String? get lastError => lastErrorInternal;
///
int currentIterationInternal = 0;
int get currentIteration => currentIterationInternal;
///
final List<CollaborationLogEntry> logEntriesInternal = [];
List<CollaborationLogEntry> get logEntries =>
List.unmodifiable(logEntriesInternal);
///
void updateConfig(MultiAgentConfig config) {
configInternal = config;
collaborationEnabledInternal = config.enabled;
notifyListeners();
}
Future<void> abort() async {
abortRequestedInternal = true;
final client = activeHttpClientInternal;
activeHttpClientInternal = null;
if (client != null) {
try {
client.close(force: true);
} catch (_) {
// Best effort only.
}
}
}
///
void enable() {
configInternal = configInternal.copyWith(enabled: true);
collaborationEnabledInternal = true;
lastErrorInternal = null;
notifyListeners();
}
///
void disable() {
configInternal = configInternal.copyWith(enabled: false);
collaborationEnabledInternal = false;
notifyListeners();
}
///
void toggle() {
if (collaborationEnabledInternal) {
disable();
} else {
enable();
}
}
///
Future<CollaborationResult> runCollaboration({
required String taskPrompt,
required String workingDirectory,
List<CollaborationAttachment> attachments = const [],
List<String> selectedSkills = const [],
void Function(MultiAgentRunEvent event)? onEvent,
}) async {
if (isRunningInternal) {
throw StateError('Collaboration is already running');
}
isRunningInternal = true;
currentIterationInternal = 0;
abortRequestedInternal = false;
logEntriesInternal.clear();
lastErrorInternal = null;
notifyListeners();
final startTime = DateTime.now();
final steps = <CollaborationStep>[];
final preset = configInternal.usesAris
? const ArisFrameworkPreset()
: const NativeFrameworkPreset();
try {
// === Phase 1: Architect ===
throwIfAbortedInternal();
logInternal(
CollaborationLogLevel.info,
'🎨',
'${roleLabelInternal(MultiAgentRole.architect)} 开始分析任务...',
);
emitEventInternal(
onEvent,
MultiAgentRunEvent(
type: 'step',
title: roleLabelInternal(MultiAgentRole.architect),
message: '${roleLabelInternal(MultiAgentRole.architect)} 开始分析任务…',
pending: true,
error: false,
role: 'architect',
),
);
final architectResult = await runArchitectInternal(
taskPrompt,
preset: preset,
selectedSkills: selectedSkills,
);
steps.add(
CollaborationStep(
role: 'architect',
status: StepStatus.completed,
output: architectResult.output,
duration: architectResult.duration,
),
);
emitEventInternal(
onEvent,
MultiAgentRunEvent(
type: 'step',
title: roleLabelInternal(MultiAgentRole.architect),
message: '完成任务分析并生成执行分解。',
pending: false,
error: false,
role: 'architect',
data: <String, dynamic>{
'taskCount': architectResult.decomposedTasks.length,
},
),
);
// === Phase 2: Engineer ===
throwIfAbortedInternal();
logInternal(
CollaborationLogLevel.info,
'🔧',
'${roleLabelInternal(MultiAgentRole.engineer)} 开始实现...',
);
emitEventInternal(
onEvent,
MultiAgentRunEvent(
type: 'step',
title: roleLabelInternal(MultiAgentRole.engineer),
message: '${roleLabelInternal(MultiAgentRole.engineer)} 开始实现任务…',
pending: true,
error: false,
role: 'engineer',
),
);
final engineerResult = await runEngineerInternal(
architectResult.decomposedTasks,
workingDirectory,
attachments,
preset: preset,
selectedSkills: selectedSkills,
);
steps.add(
CollaborationStep(
role: 'engineer',
status: StepStatus.completed,
output: engineerResult.output,
duration: engineerResult.duration,
),
);
emitEventInternal(
onEvent,
MultiAgentRunEvent(
type: 'step',
title: roleLabelInternal(MultiAgentRole.engineer),
message: '完成首轮实现。',
pending: false,
error: false,
role: 'engineer',
),
);
// === Phase 3: Tester ===
throwIfAbortedInternal();
logInternal(
CollaborationLogLevel.info,
'🔍',
'${roleLabelInternal(MultiAgentRole.testerDoc)} 开始审阅...',
);
emitEventInternal(
onEvent,
MultiAgentRunEvent(
type: 'step',
title: roleLabelInternal(MultiAgentRole.testerDoc),
message: '${roleLabelInternal(MultiAgentRole.testerDoc)} 开始审阅实现…',
pending: true,
error: false,
role: 'tester',
),
);
final testerResult = await runTesterInternal(
engineerResult.codeOutput,
preset: preset,
);
steps.add(
CollaborationStep(
role: 'tester',
status: StepStatus.completed,
output: testerResult.output,
duration: testerResult.duration,
score: testerResult.score,
),
);
emitEventInternal(
onEvent,
MultiAgentRunEvent(
type: 'step',
title: roleLabelInternal(MultiAgentRole.testerDoc),
message: '完成代码审阅。',
pending: false,
error: false,
role: 'tester',
score: testerResult.score,
),
);
// === Phase 4: ===
if (testerResult.score < configInternal.minAcceptableScore) {
logInternal(
CollaborationLogLevel.warning,
'⚠️',
'质量评分 ${testerResult.score}/10 未达标,开始迭代审阅...',
);
for (var i = 0; i < configInternal.maxIterations; i++) {
throwIfAbortedInternal();
currentIterationInternal = i + 1;
logInternal(
CollaborationLogLevel.info,
'🔄',
'迭代 $currentIterationInternal/${configInternal.maxIterations}...',
);
notifyListeners();
// Lead Engineer
final fixedResult = await runFixInternal(
engineerResult.codeOutput,
testerResult.feedback,
workingDirectory,
preset: preset,
);
steps.add(
CollaborationStep(
role: 'engineer',
status: StepStatus.completed,
output: fixedResult.output,
duration: fixedResult.duration,
iteration: currentIterationInternal,
),
);
// Tester
final reReview = await runTesterInternal(
fixedResult.codeOutput,
preset: preset,
);
steps.add(
CollaborationStep(
role: 'tester',
status: StepStatus.completed,
output: reReview.output,
duration: reReview.duration,
score: reReview.score,
iteration: currentIterationInternal,
),
);
if (reReview.score >= configInternal.minAcceptableScore) {
logInternal(
CollaborationLogLevel.success,
'',
'质量达标 (${reReview.score}/10),迭代结束',
);
engineerResult.codeOutput = fixedResult.codeOutput;
break;
} else if (currentIterationInternal >= configInternal.maxIterations) {
logInternal(
CollaborationLogLevel.error,
'',
'达到最大迭代次数 ${configInternal.maxIterations},质量仍未达标',
);
}
}
} else {
logInternal(
CollaborationLogLevel.success,
'',
'质量达标 (${testerResult.score}/10),无需迭代',
);
}
final duration = DateTime.now().difference(startTime);
isRunningInternal = false;
notifyListeners();
return CollaborationResult(
success: true,
steps: steps,
finalCode: engineerResult.codeOutput,
finalScore: testerResult.score,
duration: duration,
iterations: currentIterationInternal,
);
} catch (e) {
lastErrorInternal = e.toString();
logInternal(CollaborationLogLevel.error, '', '协作失败: $lastErrorInternal');
isRunningInternal = false;
notifyListeners();
return CollaborationResult(
success: false,
steps: steps,
finalCode: '',
finalScore: 0,
duration: DateTime.now().difference(startTime),
iterations: currentIterationInternal,
error: lastErrorInternal,
);
}
}
///
void logInternal(CollaborationLogLevel level, String emoji, String message) {
logEntriesInternal.add(
CollaborationLogEntry(
timestamp: DateTime.now(),
level: level,
emoji: emoji,
message: message,
),
);
notifyListeners();
}
///
void clearLogs() {
logEntriesInternal.clear();
notifyListeners();
}
}

View File

@ -1,200 +0,0 @@
// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'embedded_agent_launch_policy.dart';
import 'go_core.dart';
import 'aris_llm_chat_client.dart';
import 'multi_agent_frameworks.dart';
import 'runtime_models.dart';
import 'multi_agent_orchestrator_workflow.dart';
import 'multi_agent_orchestrator_support.dart';
import 'multi_agent_orchestrator_core.dart';
typedef CliProcessStarter =
Future<Process> Function(
String executable,
List<String> arguments, {
Map<String, String>? environment,
String? workingDirectory,
});
///
class CollaborationLogEntry {
const CollaborationLogEntry({
required this.timestamp,
required this.level,
required this.emoji,
required this.message,
});
final DateTime timestamp;
final CollaborationLogLevel level;
final String emoji;
final String message;
String get formattedTime {
final h = timestamp.hour.toString().padLeft(2, '0');
final m = timestamp.minute.toString().padLeft(2, '0');
final s = timestamp.second.toString().padLeft(2, '0');
return '$h:$m:$s';
}
}
enum CollaborationLogLevel { debug, info, warning, error, success }
/// CLI
class CliResult {
const CliResult({
required this.output,
required this.error,
required this.exitCode,
});
final String output;
final String error;
final int exitCode;
bool get success => exitCode == 0 && error.isEmpty;
}
/// Architect
class ArchitectResult {
ArchitectResult({
required this.output,
required this.decomposedTasks,
required this.duration,
});
final String output;
final List<SubTask> decomposedTasks;
final Duration duration;
}
/// Engineer
class EngineerResult {
EngineerResult({
required this.output,
required this.codeOutput,
required this.completedTasks,
required this.duration,
});
final String output;
String codeOutput;
final List<SubTask> completedTasks;
final Duration duration;
}
/// Tester
class TesterResult {
TesterResult({
required this.output,
required this.score,
required this.feedback,
required this.duration,
});
final String output;
final int score;
final String feedback;
final Duration duration;
}
///
class CollaborationStep {
const CollaborationStep({
required this.role,
required this.status,
required this.output,
required this.duration,
this.iteration,
this.score,
});
final String role;
final StepStatus status;
final String output;
final Duration duration;
final int? iteration;
final int? score;
Map<String, dynamic> toJson() {
return {
'role': role,
'status': status.name,
'output': output,
'durationMs': duration.inMilliseconds,
if (iteration != null) 'iteration': iteration,
if (score != null) 'score': score,
};
}
}
enum StepStatus { pending, running, completed, failed }
///
class SubTask {
const SubTask({
required this.id,
required this.description,
required this.order,
required this.type,
});
final String id;
final String description;
final int order;
final SubTaskType type;
}
enum SubTaskType { design, implementation, testing, documentation, deployment }
///
class CollaborationAttachment {
const CollaborationAttachment({
required this.name,
required this.description,
required this.path,
});
final String name;
final String description;
final String path;
}
///
class CollaborationResult {
const CollaborationResult({
required this.success,
required this.steps,
required this.finalCode,
required this.finalScore,
required this.duration,
required this.iterations,
this.error,
});
final bool success;
final List<CollaborationStep> steps;
final String finalCode;
final int finalScore;
final Duration duration;
final int iterations;
final String? error;
Map<String, dynamic> toJson() {
return {
'success': success,
'steps': steps.map((item) => item.toJson()).toList(growable: false),
'finalCode': finalCode,
'finalScore': finalScore,
'durationMs': duration.inMilliseconds,
'iterations': iterations,
if (error != null) 'error': error,
};
}
}

View File

@ -1,269 +0,0 @@
// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'embedded_agent_launch_policy.dart';
import 'go_core.dart';
import 'aris_llm_chat_client.dart';
import 'multi_agent_frameworks.dart';
import 'runtime_models.dart';
import 'multi_agent_orchestrator_protocol.dart';
import 'multi_agent_orchestrator_workflow.dart';
import 'multi_agent_orchestrator_core.dart';
extension MultiAgentOrchestratorSupportInternal on MultiAgentOrchestrator {
String openAiCompatibleBaseUrlInternal() {
final normalized = configInternal.ollamaEndpoint.trim();
return normalized.endsWith('/v1') ? normalized : '$normalized/v1';
}
String openAiCompatibleApiKeyInternal() {
return 'ollama';
}
String systemPromptForRoleInternal(MultiAgentRole role) {
return switch (role) {
MultiAgentRole.architect =>
'You are the architecture and documentation lane in a multi-agent coding workflow. Focus on requirements, acceptance evidence, task slicing, and milestones.',
MultiAgentRole.engineer =>
'You are the lead engineer in a multi-agent coding workflow. Produce implementation-oriented output for the critical path.',
MultiAgentRole.testerDoc =>
'You are the worker-review lane in a multi-agent coding workflow. Review, score, and suggest follow-up fixes and worker follow-ups.',
};
}
String roleLabelInternal(MultiAgentRole role) {
return switch (role) {
MultiAgentRole.architect => 'Architect',
MultiAgentRole.engineer => 'Lead Engineer',
MultiAgentRole.testerDoc => 'Worker/Review',
};
}
String modelForRoleInternal(MultiAgentRole role) {
return switch (role) {
MultiAgentRole.architect => configInternal.architect.model,
MultiAgentRole.engineer => configInternal.engineer.model,
MultiAgentRole.testerDoc => configInternal.tester.model,
};
}
bool prefersOllamaLaunchInternal({
required String tool,
required String model,
}) {
final normalizedTool = tool.trim().toLowerCase();
final normalizedModel = model.trim();
if (normalizedModel.isEmpty) {
return false;
}
if (normalizedTool != 'claude' &&
normalizedTool != 'codex' &&
normalizedTool != 'opencode') {
return false;
}
return true;
}
List<String> buildOllamaLaunchArgsInternal({
required String tool,
required String model,
required String prompt,
required String cwd,
}) {
final args = <String>['launch', tool, '--model', model];
if (tool == 'claude') {
args.add('--yes');
args.addAll(<String>['--', '-p', prompt]);
return args;
}
if (tool == 'codex') {
args.addAll(<String>[
'--',
'exec',
'--skip-git-repo-check',
'--color',
'never',
if (cwd.isNotEmpty) ...<String>['-C', cwd],
prompt,
]);
return args;
}
if (tool == 'opencode') {
args.addAll(<String>[
'--',
'run',
'--format',
'default',
if (cwd.isNotEmpty) ...<String>['--dir', cwd],
prompt,
]);
return args;
}
args.addAll(<String>['--', '-p', prompt]);
return args;
}
void throwIfAbortedInternal() {
if (abortRequestedInternal) {
throw StateError('Multi-agent collaboration aborted.');
}
}
/// Architect
List<SubTask> parseDecomposedTasksInternal(String architectOutput) {
final tasks = <SubTask>[];
final lines = architectOutput.split('\n');
var order = 1;
for (final line in lines) {
final trimmed = line.trim();
if (trimmed.isEmpty) continue;
// "- 描述" "1. 描述"
final dashMatch = RegExp(r'^[-*]\s+(.+)').firstMatch(trimmed);
final numMatch = RegExp(r'^\d+[.、)]\s*(.+)').firstMatch(trimmed);
String? description;
if (dashMatch != null) {
description = dashMatch.group(1);
} else if (numMatch != null) {
description = numMatch.group(1);
}
if (description != null && description.isNotEmpty) {
//
description = description.replaceAll(RegExp(r'\s*\|.*'), '').trim();
//
SubTaskType type = SubTaskType.implementation;
final lower = description.toLowerCase();
if (lower.contains('测试') || lower.contains('test')) {
type = SubTaskType.testing;
} else if (lower.contains('文档') || lower.contains('doc')) {
type = SubTaskType.documentation;
} else if (lower.contains('设计') || lower.contains('design')) {
type = SubTaskType.design;
} else if (lower.contains('部署') || lower.contains('deploy')) {
type = SubTaskType.deployment;
}
tasks.add(
SubTask(
id: order.toString(),
description: description,
order: order,
type: type,
),
);
order++;
}
}
//
if (tasks.isEmpty) {
tasks.add(
SubTask(
id: '1',
description: architectOutput.length > 200
? '${architectOutput.substring(0, 200)}...'
: architectOutput,
order: 1,
type: SubTaskType.implementation,
),
);
}
return tasks;
}
///
int parseReviewScoreInternal(String output) {
// "评分 (1-10)"
final patterns = [
RegExp(r'评分\s*\(?[100]\)?\s*[:]?\s*(\d+)'),
RegExp(r'score\s*[:]?\s*(\d+)', caseSensitive: false),
RegExp(r'评分[:\s]*(\d+)'),
RegExp(r'\*\*(\d+)\s*/\s*10\*\*'),
RegExp(r'(\d+)\s*/\s*10'),
];
for (final pattern in patterns) {
final match = pattern.firstMatch(output);
if (match != null) {
final scoreStr = match.group(1)!;
final score = int.tryParse(
scoreStr.replaceAll('', '1').replaceAll('', '0'),
);
if (score != null && score >= 1 && score <= 10) {
return score;
}
}
}
//
return 5;
}
///
String extractFeedbackInternal(String output) {
final feedbackIndex = output.indexOf(RegExp(r'##?\s*问题|##?\s*改进|##?\s*建议'));
if (feedbackIndex >= 0) {
final endIndex = output.indexOf(
RegExp(r'##?\s*测试|##?\s*文档'),
feedbackIndex + 1,
);
if (endIndex > feedbackIndex) {
return output.substring(feedbackIndex, endIndex).trim();
}
return output.substring(feedbackIndex).trim();
}
return output;
}
/// Ollama
Map<String, String> buildCliEnvVarsInternal({required String tool}) {
final baseEnv = <String, String>{...Platform.environment};
final ollamaEndpoint = configInternal.ollamaEndpoint.trim();
if (ollamaEndpoint.isNotEmpty) {
baseEnv['OLLAMA_BASE_URL'] = ollamaEndpoint;
baseEnv['OLLAMA_HOST'] = ollamaEndpoint;
baseEnv['OPENAI_API_KEY'] = 'ollama';
baseEnv['OPENAI_BASE_URL'] = ollamaEndpoint.endsWith('/v1')
? ollamaEndpoint
: '$ollamaEndpoint/v1';
}
if (tool == 'claude' || tool == 'codex') {
baseEnv['ANTHROPIC_AUTH_TOKEN'] = 'ollama';
baseEnv['ANTHROPIC_API_KEY'] = '';
baseEnv['ANTHROPIC_BASE_URL'] = ollamaEndpoint;
}
return baseEnv;
}
/// CLI
String resolveCliPathInternal(String tool) {
switch (tool) {
case 'claude':
return 'claude';
case 'codex':
return 'codex';
case 'gemini':
return 'gemini';
case 'opencode':
return 'opencode';
default:
return tool;
}
}
void emitEventInternal(
void Function(MultiAgentRunEvent event)? onEvent,
MultiAgentRunEvent event,
) {
onEvent?.call(event);
}
}

View File

@ -1,456 +0,0 @@
// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'embedded_agent_launch_policy.dart';
import 'go_core.dart';
import 'aris_llm_chat_client.dart';
import 'multi_agent_frameworks.dart';
import 'runtime_models.dart';
import 'multi_agent_orchestrator_protocol.dart';
import 'multi_agent_orchestrator_support.dart';
import 'multi_agent_orchestrator_core.dart';
extension MultiAgentOrchestratorWorkflowInternal on MultiAgentOrchestrator {
/// Architect/
Future<ArchitectResult> runArchitectInternal(
String task, {
required FrameworkPreset preset,
required List<String> selectedSkills,
}) async {
final stopwatch = Stopwatch()..start();
try {
// Architect
if (configInternal.architectEnabled) {
final tool = await resolveToolForRoleInternal(
MultiAgentRole.architect,
configInternal.architectTool,
);
final instructionBlock = await preset.roleInstructionBlock(
role: MultiAgentRole.architect,
tool: tool,
selectedSkills: selectedSkills,
);
final result = await runCliPromptInternal(
role: MultiAgentRole.architect,
tool: tool,
model: resolvedModelForRoleInternal(
MultiAgentRole.architect,
configuredModel: configInternal.architectModel,
),
prompt: buildArchitectPromptInternal(
task,
selectedSkills,
instructionBlock,
),
cwd: '',
);
stopwatch.stop();
//
final tasks = parseDecomposedTasksInternal(result.output);
return ArchitectResult(
output: result.output,
decomposedTasks: tasks,
duration: stopwatch.elapsed,
);
} else {
// Architect
stopwatch.stop();
return ArchitectResult(
output: task,
decomposedTasks: [
SubTask(
id: '1',
description: task,
order: 1,
type: SubTaskType.implementation,
),
],
duration: stopwatch.elapsed,
);
}
} catch (e) {
stopwatch.stop();
rethrow;
}
}
/// Lead Engineer
Future<EngineerResult> runEngineerInternal(
List<SubTask> tasks,
String workingDirectory,
List<CollaborationAttachment> attachments, {
required FrameworkPreset preset,
required List<String> selectedSkills,
}) async {
final stopwatch = Stopwatch()..start();
final tool = await resolveToolForRoleInternal(
MultiAgentRole.engineer,
configInternal.engineerTool,
);
final instructionBlock = await preset.roleInstructionBlock(
role: MultiAgentRole.engineer,
tool: tool,
selectedSkills: selectedSkills,
);
final taskList = tasks
.map((t) => '## ${t.order}. ${t.description}')
.join('\n\n');
final prompt =
'''
$instructionBlock
###
$taskList
###
$workingDirectory
###
${attachments.map((a) => '- ${a.name}: ${a.description}').join('\n')}
###
${selectedSkills.isEmpty ? '- 无' : selectedSkills.map((item) => '- $item').join('\n')}
''';
final result = await runCliPromptInternal(
role: MultiAgentRole.engineer,
tool: tool,
model: resolvedModelForRoleInternal(
MultiAgentRole.engineer,
configuredModel: configInternal.engineerModel,
),
prompt: prompt,
cwd: workingDirectory,
);
stopwatch.stop();
return EngineerResult(
output: result.output,
codeOutput: result.output,
completedTasks: tasks,
duration: stopwatch.elapsed,
);
}
/// Worker/Review
Future<TesterResult> runTesterInternal(
String codeOutput, {
required FrameworkPreset preset,
}) async {
final stopwatch = Stopwatch()..start();
final tool = await resolveToolForRoleInternal(
MultiAgentRole.testerDoc,
configInternal.testerTool,
);
final instructionBlock = await preset.roleInstructionBlock(
role: MultiAgentRole.testerDoc,
tool: tool,
selectedSkills: const <String>[],
);
final prompt =
'''
$instructionBlock
## (1-10)
[1-10 10 ]
##
[- (: //)]
##
[]
##
```[]
[]
```
##
[]
###
${codeOutput.length > 4000 ? '${codeOutput.substring(0, 4000)}\n...[代码已截断]' : codeOutput}
''';
final testerModel = resolvedModelForRoleInternal(
MultiAgentRole.testerDoc,
configuredModel: configInternal.testerModel,
);
final result = configInternal.usesAris && tool == 'claude'
? await runArisTesterViaClaudeReviewInternal(
model: testerModel,
prompt: prompt,
)
: await runCliPromptInternal(
role: MultiAgentRole.testerDoc,
tool: tool,
model: testerModel,
prompt: prompt,
cwd: '',
);
stopwatch.stop();
final score = parseReviewScoreInternal(result.output);
final feedback = extractFeedbackInternal(result.output);
return TesterResult(
output: result.output,
score: score,
feedback: feedback,
duration: stopwatch.elapsed,
);
}
///
Future<EngineerResult> runFixInternal(
String originalCode,
String feedback,
String workingDirectory, {
required FrameworkPreset preset,
}) async {
final stopwatch = Stopwatch()..start();
final tool = await resolveToolForRoleInternal(
MultiAgentRole.engineer,
configInternal.engineerTool,
);
final instructionBlock = await preset.roleInstructionBlock(
role: MultiAgentRole.engineer,
tool: tool,
selectedSkills: const <String>[],
);
final prompt =
'''
$instructionBlock
##
$feedback
##
$originalCode
''';
final result = await runCliPromptInternal(
role: MultiAgentRole.engineer,
tool: tool,
model: resolvedModelForRoleInternal(
MultiAgentRole.engineer,
configuredModel: configInternal.engineerModel,
),
prompt: prompt,
cwd: workingDirectory,
);
stopwatch.stop();
return EngineerResult(
output: result.output,
codeOutput: result.output,
completedTasks: [],
duration: stopwatch.elapsed,
);
}
/// CLI (DEPRECATED: Use bridge instead)
Future<CliResult> runCliPromptInternal({
required MultiAgentRole role,
required String tool,
required String model,
required String prompt,
required String cwd,
}) async {
// In cloud-neutral architecture, local CLI execution is disabled.
// We should fallback to OpenAI compatible API or bridge execution.
return runArisFallbackInternal(role: role, model: model, prompt: prompt);
}
/// Architect Prompt
String buildArchitectPromptInternal(
String task,
List<String> selectedSkills,
String instructionBlock,
) {
return '''
$instructionBlock
Agent requirements -> acceptance evidence/worker分工
##
$task
##
${selectedSkills.isEmpty ? '- 无' : selectedSkills.map((item) => '- $item').join('\n')}
1. 2-3
2. 3-5
-
- //worker
-
-
3.
##
[]
##
1. [] | [//worker] | [] | []
2. [] | [//worker] | [] | []
...
''';
}
Future<String> resolveToolForRoleInternal(
MultiAgentRole role,
String configuredTool,
) async {
return configuredTool;
}
String resolvedModelForRoleInternal(
MultiAgentRole role, {
required String configuredModel,
}) {
final trimmed = configuredModel.trim();
if (trimmed.isNotEmpty) {
return trimmed;
}
switch (role) {
case MultiAgentRole.architect:
return 'kimi-k2.5:cloud';
case MultiAgentRole.engineer:
return 'minimax-m2.7:cloud';
case MultiAgentRole.testerDoc:
return 'glm-5:cloud';
}
}
Future<bool> binaryExistsInternal(String command) async {
return false;
}
Future<CliResult> runArisFallbackInternal({
required MultiAgentRole role,
required String model,
required String prompt,
}) async {
if (role == MultiAgentRole.testerDoc) {
final viaLlmChat = await runArisTesterViaLlmChatInternal(
model: model,
prompt: prompt,
);
if (viaLlmChat.success) {
return viaLlmChat;
}
}
return runOpenAiCompatiblePromptInternal(
role: role,
model: model,
prompt: prompt,
);
}
Future<CliResult> runArisTesterViaLlmChatInternal({
required String model,
required String prompt,
}) async {
return runOpenAiCompatiblePromptInternal(
role: MultiAgentRole.testerDoc,
model: model,
prompt: prompt,
);
}
Future<CliResult> runArisTesterViaClaudeReviewInternal({
required String model,
required String prompt,
}) async {
return runArisFallbackInternal(
role: MultiAgentRole.testerDoc,
model: model,
prompt: prompt,
);
}
Future<CliResult> runOpenAiCompatiblePromptInternal({
required MultiAgentRole role,
required String model,
required String prompt,
}) async {
final client = httpClientFactoryInternal();
activeHttpClientInternal = client;
try {
final request = await client.postUrl(
Uri.parse(
'${openAiCompatibleBaseUrlInternal().replaceAll(RegExp(r'/$'), '')}/chat/completions',
),
);
request.headers.set(HttpHeaders.contentTypeHeader, 'application/json');
request.headers.set(
HttpHeaders.authorizationHeader,
'Bearer ${openAiCompatibleApiKeyInternal()}',
);
request.add(
utf8.encode(
jsonEncode(<String, dynamic>{
'model': model,
'stream': false,
'messages': <Map<String, String>>[
<String, String>{
'role': 'system',
'content': systemPromptForRoleInternal(role),
},
<String, String>{'role': 'user', 'content': prompt},
],
}),
),
);
final response = await request.close().timeout(
Duration(seconds: configInternal.timeoutSeconds),
);
final body = await utf8.decodeStream(response);
if (response.statusCode < 200 || response.statusCode >= 300) {
return CliResult(
output: '',
error: body,
exitCode: response.statusCode,
);
}
final decoded = jsonDecode(body) as Map<String, dynamic>;
final choices = decoded['choices'] as List? ?? const <Object>[];
final firstChoice = choices.isNotEmpty ? choices.first : null;
final output =
((firstChoice as Map?)?['message'] as Map?)?['content']?.toString() ??
'';
return CliResult(output: output, error: '', exitCode: 0);
} catch (error) {
return CliResult(output: '', error: error.toString(), exitCode: -1);
} finally {
activeHttpClientInternal = null;
try {
client.close(force: true);
} catch (_) {
// Best effort only.
}
}
}
}

View File

@ -6,5 +6,3 @@ export 'runtime_models_settings_snapshot.dart';
export 'runtime_models_ui_state.dart';
export 'runtime_models_runtime_payloads.dart';
export 'runtime_models_gateway_entities.dart';
export 'runtime_models_multi_agent.dart';
export 'multi_agent_orchestrator_protocol.dart';

View File

@ -1,107 +1,3 @@
// ignore_for_file: unused_import, unnecessary_import
import 'dart:convert';
import '../i18n/app_language.dart';
import '../models/app_models.dart';
import 'runtime_models_connection.dart';
import 'runtime_models_profiles.dart';
import 'runtime_models_configs.dart';
import 'runtime_models_settings_snapshot.dart';
import 'runtime_models_runtime_payloads.dart';
import 'runtime_models_gateway_entities.dart';
enum MultiAgentRole {
architect, // /
engineer, //
testerDoc, // worker/review
}
enum MultiAgentFramework { native, aris }
extension MultiAgentFrameworkCopy on MultiAgentFramework {
String get label => switch (this) {
MultiAgentFramework.native => appText('原生多 Agent', 'Native Multi-Agent'),
MultiAgentFramework.aris => appText('ARIS 框架', 'ARIS Framework'),
};
static MultiAgentFramework fromJsonValue(String? value) {
return MultiAgentFramework.values.firstWhere(
(item) => item.name == value,
orElse: () => MultiAgentFramework.native,
);
}
}
extension MultiAgentRoleCopy on MultiAgentRole {
String get label => switch (this) {
MultiAgentRole.architect => 'Architect调度/文档)',
MultiAgentRole.engineer => 'Lead Engineer主程',
MultiAgentRole.testerDoc => 'Worker/ReviewWorker 池)',
};
String get description => switch (this) {
MultiAgentRole.architect => '负责需求收口、接受标准、文档与协作调度',
MultiAgentRole.engineer => '负责主实现、关键改动、集成收口',
MultiAgentRole.testerDoc => '负责并行 worker、复审、回归和补充说明',
};
}
enum AiGatewayInjectionPolicy { disabled, launchScoped, appManagedDefault }
extension AiGatewayInjectionPolicyCopy on AiGatewayInjectionPolicy {
String get label => switch (this) {
AiGatewayInjectionPolicy.disabled => appText('禁用', 'Disabled'),
AiGatewayInjectionPolicy.launchScoped => appText(
'仅当前协作运行',
'Launch scoped',
),
AiGatewayInjectionPolicy.appManagedDefault => appText(
'XWorkmate 默认',
'XWorkmate default',
),
};
static AiGatewayInjectionPolicy fromJsonValue(String? value) {
return AiGatewayInjectionPolicy.values.firstWhere(
(item) => item.name == value,
orElse: () => AiGatewayInjectionPolicy.appManagedDefault,
);
}
}
/// Agent Worker
class AgentWorkerConfig {
const AgentWorkerConfig({
required this.role,
required this.cliTool,
required this.model,
required this.enabled,
this.maxRetries = 2,
});
final MultiAgentRole role;
final String cliTool; // e.g. 'claude' | 'codex' | 'opencode' | 'gemini'
final String model;
final bool enabled;
final int maxRetries;
AgentWorkerConfig copyWith({
MultiAgentRole? role,
String? cliTool,
String? model,
bool? enabled,
int? maxRetries,
}) {
return AgentWorkerConfig(
role: role ?? this.role,
cliTool: cliTool ?? this.cliTool,
model: model ?? this.model,
enabled: enabled ?? this.enabled,
maxRetries: maxRetries ?? this.maxRetries,
);
}
}
class ManagedSkillEntry {
const ManagedSkillEntry({
required this.key,
@ -426,415 +322,3 @@ class ManagedMountTargetState {
];
}
}
/// Agent
class MultiAgentConfig {
const MultiAgentConfig({
required this.enabled,
required this.autoSync,
required this.framework,
required this.arisEnabled,
required this.arisMode,
required this.arisBundleVersion,
required this.arisCompatStatus,
required this.architect,
required this.engineer,
required this.tester,
required this.ollamaEndpoint,
required this.maxIterations,
required this.minAcceptableScore,
required this.timeoutSeconds,
required this.aiGatewayInjectionPolicy,
required this.managedSkills,
required this.managedMcpServers,
required this.mountTargets,
});
final bool enabled;
final bool autoSync;
final MultiAgentFramework framework;
final bool arisEnabled;
final String arisMode;
final String arisBundleVersion;
final String arisCompatStatus;
final AgentWorkerConfig architect;
final AgentWorkerConfig engineer;
final AgentWorkerConfig tester;
final String ollamaEndpoint;
final int maxIterations;
final int minAcceptableScore;
final int timeoutSeconds;
final AiGatewayInjectionPolicy aiGatewayInjectionPolicy;
final List<ManagedSkillEntry> managedSkills;
final List<ManagedMcpServerEntry> managedMcpServers;
final List<ManagedMountTargetState> mountTargets;
/// Architect 便访
bool get architectEnabled => architect.enabled;
String get architectTool => architect.cliTool;
String get architectModel => architect.model;
/// Engineer 便访
String get engineerTool => engineer.cliTool;
String get engineerModel => engineer.model;
/// Tester 便访
String get testerTool => tester.cliTool;
String get testerModel => tester.model;
bool get usesAris => arisEnabled || framework == MultiAgentFramework.aris;
factory MultiAgentConfig.defaults() {
return MultiAgentConfig(
enabled: false,
autoSync: true,
framework: MultiAgentFramework.native,
arisEnabled: false,
arisMode: 'full',
arisBundleVersion: '',
arisCompatStatus: 'idle',
architect: const AgentWorkerConfig(
role: MultiAgentRole.architect,
cliTool: 'claude',
model: 'kimi-k2.5:cloud',
enabled: true,
),
engineer: const AgentWorkerConfig(
role: MultiAgentRole.engineer,
cliTool: 'codex',
model: 'minimax-m2.7:cloud',
enabled: true,
),
tester: const AgentWorkerConfig(
role: MultiAgentRole.testerDoc,
cliTool: 'opencode',
model: 'glm-5:cloud',
enabled: true,
),
ollamaEndpoint: 'http://127.0.0.1:11434',
maxIterations: 3,
minAcceptableScore: 7,
timeoutSeconds: 120,
aiGatewayInjectionPolicy: AiGatewayInjectionPolicy.appManagedDefault,
managedSkills: const <ManagedSkillEntry>[],
managedMcpServers: const <ManagedMcpServerEntry>[],
mountTargets: const <ManagedMountTargetState>[
ManagedMountTargetState(
targetId: 'aris',
label: 'ARIS',
available: false,
supportsSkills: true,
supportsMcp: true,
supportsAiGatewayInjection: false,
discoveryState: 'idle',
syncState: 'idle',
discoveredSkillCount: 0,
discoveredMcpCount: 0,
managedMcpCount: 0,
detail: '',
),
ManagedMountTargetState(
targetId: 'codex',
label: 'Codex',
available: false,
supportsSkills: true,
supportsMcp: true,
supportsAiGatewayInjection: true,
discoveryState: 'idle',
syncState: 'idle',
discoveredSkillCount: 0,
discoveredMcpCount: 0,
managedMcpCount: 0,
detail: '',
),
ManagedMountTargetState(
targetId: 'claude',
label: 'Claude',
available: false,
supportsSkills: true,
supportsMcp: true,
supportsAiGatewayInjection: true,
discoveryState: 'idle',
syncState: 'idle',
discoveredSkillCount: 0,
discoveredMcpCount: 0,
managedMcpCount: 0,
detail: '',
),
ManagedMountTargetState(
targetId: 'gemini',
label: 'Gemini',
available: false,
supportsSkills: true,
supportsMcp: true,
supportsAiGatewayInjection: true,
discoveryState: 'idle',
syncState: 'idle',
discoveredSkillCount: 0,
discoveredMcpCount: 0,
managedMcpCount: 0,
detail: '',
),
ManagedMountTargetState(
targetId: 'opencode',
label: 'OpenCode',
available: false,
supportsSkills: true,
supportsMcp: true,
supportsAiGatewayInjection: true,
discoveryState: 'idle',
syncState: 'idle',
discoveredSkillCount: 0,
discoveredMcpCount: 0,
managedMcpCount: 0,
detail: '',
),
ManagedMountTargetState(
targetId: 'openclaw',
label: 'OpenClaw',
available: false,
supportsSkills: true,
supportsMcp: false,
supportsAiGatewayInjection: true,
discoveryState: 'idle',
syncState: 'idle',
discoveredSkillCount: 0,
discoveredMcpCount: 0,
managedMcpCount: 0,
detail: '',
),
],
);
}
MultiAgentConfig copyWith({
bool? enabled,
bool? autoSync,
MultiAgentFramework? framework,
bool? arisEnabled,
String? arisMode,
String? arisBundleVersion,
String? arisCompatStatus,
AgentWorkerConfig? architect,
AgentWorkerConfig? engineer,
AgentWorkerConfig? tester,
String? ollamaEndpoint,
int? maxIterations,
int? minAcceptableScore,
int? timeoutSeconds,
AiGatewayInjectionPolicy? aiGatewayInjectionPolicy,
List<ManagedSkillEntry>? managedSkills,
List<ManagedMcpServerEntry>? managedMcpServers,
List<ManagedMountTargetState>? mountTargets,
}) {
return MultiAgentConfig(
enabled: enabled ?? this.enabled,
autoSync: autoSync ?? this.autoSync,
framework: framework ?? this.framework,
arisEnabled: arisEnabled ?? this.arisEnabled,
arisMode: arisMode ?? this.arisMode,
arisBundleVersion: arisBundleVersion ?? this.arisBundleVersion,
arisCompatStatus: arisCompatStatus ?? this.arisCompatStatus,
architect: architect ?? this.architect,
engineer: engineer ?? this.engineer,
tester: tester ?? this.tester,
ollamaEndpoint: ollamaEndpoint ?? this.ollamaEndpoint,
maxIterations: maxIterations ?? this.maxIterations,
minAcceptableScore: minAcceptableScore ?? this.minAcceptableScore,
timeoutSeconds: timeoutSeconds ?? this.timeoutSeconds,
aiGatewayInjectionPolicy:
aiGatewayInjectionPolicy ?? this.aiGatewayInjectionPolicy,
managedSkills: managedSkills ?? this.managedSkills,
managedMcpServers: managedMcpServers ?? this.managedMcpServers,
mountTargets: mountTargets ?? this.mountTargets,
);
}
Map<String, dynamic> toJson() {
return {
'enabled': enabled,
'autoSync': autoSync,
'framework': framework.name,
'arisEnabled': arisEnabled,
'arisMode': arisMode,
'arisBundleVersion': arisBundleVersion,
'arisCompatStatus': arisCompatStatus,
'architect': {
'role': architect.role.name,
'cliTool': architect.cliTool,
'model': architect.model,
'enabled': architect.enabled,
'maxRetries': architect.maxRetries,
},
'engineer': {
'role': engineer.role.name,
'cliTool': engineer.cliTool,
'model': engineer.model,
'enabled': engineer.enabled,
'maxRetries': engineer.maxRetries,
},
'tester': {
'role': tester.role.name,
'cliTool': tester.cliTool,
'model': tester.model,
'enabled': tester.enabled,
'maxRetries': tester.maxRetries,
},
'ollamaEndpoint': ollamaEndpoint,
'maxIterations': maxIterations,
'minAcceptableScore': minAcceptableScore,
'timeoutSeconds': timeoutSeconds,
'aiGatewayInjectionPolicy': aiGatewayInjectionPolicy.name,
'managedSkills': managedSkills.map((item) => item.toJson()).toList(),
'managedMcpServers': managedMcpServers
.map((item) => item.toJson())
.toList(),
'mountTargets': mountTargets.map((item) => item.toJson()).toList(),
};
}
factory MultiAgentConfig.fromJson(Map<String, dynamic> json) {
final defaults = MultiAgentConfig.defaults();
final architectJson = json['architect'] as Map<String, dynamic>? ?? {};
final engineerJson = json['engineer'] as Map<String, dynamic>? ?? {};
final testerJson = json['tester'] as Map<String, dynamic>? ?? {};
final rawManagedSkills = json['managedSkills'];
final rawManagedMcpServers = json['managedMcpServers'];
final rawMountTargets = json['mountTargets'];
AgentWorkerConfig parseWorker(
Map<String, dynamic> m,
MultiAgentRole role,
String defaultTool,
) {
return AgentWorkerConfig(
role: role,
cliTool: m['cliTool'] as String? ?? defaultTool,
model: m['model'] as String? ?? '',
enabled: m['enabled'] as bool? ?? true,
maxRetries: m['maxRetries'] as int? ?? 2,
);
}
return MultiAgentConfig(
enabled: json['enabled'] as bool? ?? false,
autoSync: json['autoSync'] as bool? ?? defaults.autoSync,
framework: MultiAgentFrameworkCopy.fromJsonValue(
json['framework'] as String?,
),
arisEnabled: json['arisEnabled'] as bool? ?? defaults.arisEnabled,
arisMode: json['arisMode'] as String? ?? defaults.arisMode,
arisBundleVersion:
json['arisBundleVersion'] as String? ?? defaults.arisBundleVersion,
arisCompatStatus:
json['arisCompatStatus'] as String? ?? defaults.arisCompatStatus,
architect: parseWorker(
architectJson,
MultiAgentRole.architect,
defaults.architect.cliTool,
),
engineer: parseWorker(
engineerJson,
MultiAgentRole.engineer,
defaults.engineer.cliTool,
),
tester: parseWorker(
testerJson,
MultiAgentRole.testerDoc,
defaults.tester.cliTool,
),
ollamaEndpoint:
json['ollamaEndpoint'] as String? ?? defaults.ollamaEndpoint,
maxIterations: json['maxIterations'] as int? ?? defaults.maxIterations,
minAcceptableScore:
json['minAcceptableScore'] as int? ?? defaults.minAcceptableScore,
timeoutSeconds: json['timeoutSeconds'] as int? ?? defaults.timeoutSeconds,
aiGatewayInjectionPolicy: AiGatewayInjectionPolicyCopy.fromJsonValue(
json['aiGatewayInjectionPolicy'] as String?,
),
managedSkills: rawManagedSkills is List
? rawManagedSkills
.whereType<Map>()
.map(
(item) =>
ManagedSkillEntry.fromJson(item.cast<String, dynamic>()),
)
.toList(growable: false)
: defaults.managedSkills,
managedMcpServers: rawManagedMcpServers is List
? rawManagedMcpServers
.whereType<Map>()
.map(
(item) => ManagedMcpServerEntry.fromJson(
item.cast<String, dynamic>(),
),
)
.toList(growable: false)
: defaults.managedMcpServers,
mountTargets: rawMountTargets is List
? rawMountTargets
.whereType<Map>()
.map(
(item) => ManagedMountTargetState.fromJson(
item.cast<String, dynamic>(),
),
)
.toList(growable: false)
: defaults.mountTargets,
);
}
}
class MultiAgentRunEvent {
const MultiAgentRunEvent({
required this.type,
required this.title,
required this.message,
required this.pending,
required this.error,
this.role,
this.iteration,
this.score,
this.data = const <String, dynamic>{},
});
final String type;
final String title;
final String message;
final bool pending;
final bool error;
final String? role;
final int? iteration;
final int? score;
final Map<String, dynamic> data;
Map<String, dynamic> toJson() {
return {
'type': type,
'title': title,
'message': message,
'pending': pending,
'error': error,
if (role != null) 'role': role,
if (iteration != null) 'iteration': iteration,
if (score != null) 'score': score,
'data': data,
};
}
factory MultiAgentRunEvent.fromJson(Map<String, dynamic> json) {
return MultiAgentRunEvent(
type: json['type'] as String? ?? 'status',
title: json['title'] as String? ?? '',
message: json['message'] as String? ?? '',
pending: json['pending'] as bool? ?? false,
error: json['error'] as bool? ?? false,
role: json['role'] as String?,
iteration: (json['iteration'] as num?)?.toInt(),
score: (json['score'] as num?)?.toInt(),
data:
(json['data'] as Map?)?.cast<String, dynamic>() ??
const <String, dynamic>{},
);
}
}

View File

@ -10,7 +10,6 @@ import 'runtime_models_profiles.dart';
import 'runtime_models_configs.dart';
import 'runtime_models_runtime_payloads.dart';
import 'runtime_models_gateway_entities.dart';
import 'runtime_models_multi_agent.dart';
const int settingsSnapshotSchemaVersion = 2;
@ -34,7 +33,6 @@ class SettingsSnapshot {
required this.vault,
required this.aiGateway,
required this.webSessionPersistence,
required this.multiAgent,
required this.themeMode,
required this.experimentalCanvas,
required this.experimentalBridge,
@ -67,7 +65,6 @@ class SettingsSnapshot {
final VaultConfig vault;
final AiGatewayProfile aiGateway;
final WebSessionPersistenceConfig webSessionPersistence;
final MultiAgentConfig multiAgent;
final ThemeMode themeMode;
final bool experimentalCanvas;
final bool experimentalBridge;
@ -101,7 +98,6 @@ class SettingsSnapshot {
vault: VaultConfig.defaults(),
aiGateway: AiGatewayProfile.defaults(),
webSessionPersistence: WebSessionPersistenceConfig.defaults(),
multiAgent: MultiAgentConfig.defaults(),
themeMode: ThemeMode.system,
experimentalCanvas: false,
experimentalBridge: false,
@ -136,7 +132,6 @@ class SettingsSnapshot {
VaultConfig? vault,
AiGatewayProfile? aiGateway,
WebSessionPersistenceConfig? webSessionPersistence,
MultiAgentConfig? multiAgent,
ThemeMode? themeMode,
bool? experimentalCanvas,
bool? experimentalBridge,
@ -179,7 +174,6 @@ class SettingsSnapshot {
aiGateway: aiGateway ?? this.aiGateway,
webSessionPersistence:
webSessionPersistence ?? this.webSessionPersistence,
multiAgent: multiAgent ?? this.multiAgent,
themeMode: themeMode ?? this.themeMode,
experimentalCanvas: experimentalCanvas ?? this.experimentalCanvas,
experimentalBridge: experimentalBridge ?? this.experimentalBridge,
@ -223,7 +217,6 @@ class SettingsSnapshot {
'vault': vault.toJson(),
'aiGateway': aiGateway.toJson(),
'webSessionPersistence': webSessionPersistence.toJson(),
'multiAgent': multiAgent.toJson(),
'themeMode': themeMode.name,
'experimentalCanvas': experimentalCanvas,
'experimentalBridge': experimentalBridge,
@ -307,9 +300,6 @@ class SettingsSnapshot {
(json['webSessionPersistence'] as Map?)?.cast<String, dynamic>() ??
const {},
),
multiAgent: MultiAgentConfig.fromJson(
(json['multiAgent'] as Map?)?.cast<String, dynamic>() ?? const {},
),
themeMode: ThemeMode.values.firstWhere(
(m) => m.name == json['themeMode'],
orElse: () => ThemeMode.system,