2026-03-11 07:32:52 +08:00
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
2026-03-11 15:15:47 +08:00
|
|
|
|
import '../i18n/app_language.dart';
|
2026-03-15 10:14:33 +08:00
|
|
|
|
import '../models/app_models.dart';
|
2026-03-11 15:15:47 +08:00
|
|
|
|
|
2026-03-11 07:32:52 +08:00
|
|
|
|
enum RuntimeConnectionMode { unconfigured, local, remote }
|
|
|
|
|
|
|
|
|
|
|
|
extension RuntimeConnectionModeCopy on RuntimeConnectionMode {
|
|
|
|
|
|
String get label => switch (this) {
|
2026-03-11 15:15:47 +08:00
|
|
|
|
RuntimeConnectionMode.unconfigured => appText('未配置', 'Unconfigured'),
|
|
|
|
|
|
RuntimeConnectionMode.local => appText('本地', 'Local'),
|
|
|
|
|
|
RuntimeConnectionMode.remote => appText('远程', 'Remote'),
|
2026-03-11 07:32:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static RuntimeConnectionMode fromJsonValue(String? value) {
|
|
|
|
|
|
return RuntimeConnectionMode.values.firstWhere(
|
|
|
|
|
|
(item) => item.name == value,
|
|
|
|
|
|
orElse: () => RuntimeConnectionMode.unconfigured,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum RuntimeConnectionStatus { offline, connecting, connected, error }
|
|
|
|
|
|
|
|
|
|
|
|
extension RuntimeConnectionStatusCopy on RuntimeConnectionStatus {
|
|
|
|
|
|
String get label => switch (this) {
|
2026-03-11 15:15:47 +08:00
|
|
|
|
RuntimeConnectionStatus.offline => appText('离线', 'Offline'),
|
|
|
|
|
|
RuntimeConnectionStatus.connecting => appText('连接中', 'Connecting'),
|
|
|
|
|
|
RuntimeConnectionStatus.connected => appText('已连接', 'Connected'),
|
|
|
|
|
|
RuntimeConnectionStatus.error => appText('错误', 'Error'),
|
2026-03-11 07:32:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 15:45:06 +08:00
|
|
|
|
enum AssistantExecutionTarget { aiGatewayOnly, local, remote }
|
2026-03-11 14:04:29 +08:00
|
|
|
|
|
|
|
|
|
|
extension AssistantExecutionTargetCopy on AssistantExecutionTarget {
|
|
|
|
|
|
String get label => switch (this) {
|
2026-03-19 15:45:06 +08:00
|
|
|
|
AssistantExecutionTarget.aiGatewayOnly => appText(
|
|
|
|
|
|
'仅 AI Gateway',
|
|
|
|
|
|
'AI Gateway Only',
|
|
|
|
|
|
),
|
|
|
|
|
|
AssistantExecutionTarget.local => appText(
|
|
|
|
|
|
'本地 OpenClaw Gateway',
|
|
|
|
|
|
'Local OpenClaw Gateway',
|
|
|
|
|
|
),
|
|
|
|
|
|
AssistantExecutionTarget.remote => appText(
|
|
|
|
|
|
'远程 OpenClaw Gateway',
|
|
|
|
|
|
'Remote OpenClaw Gateway',
|
|
|
|
|
|
),
|
2026-03-11 14:04:29 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
String get promptValue => switch (this) {
|
2026-03-19 15:45:06 +08:00
|
|
|
|
AssistantExecutionTarget.aiGatewayOnly => 'ai-gateway-only',
|
2026-03-11 14:04:29 +08:00
|
|
|
|
AssistantExecutionTarget.local => 'local',
|
|
|
|
|
|
AssistantExecutionTarget.remote => 'remote',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static AssistantExecutionTarget fromJsonValue(String? value) {
|
|
|
|
|
|
return AssistantExecutionTarget.values.firstWhere(
|
|
|
|
|
|
(item) => item.name == value,
|
|
|
|
|
|
orElse: () => AssistantExecutionTarget.local,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-22 12:29:47 +08:00
|
|
|
|
class AssistantThreadConnectionState {
|
|
|
|
|
|
const AssistantThreadConnectionState({
|
|
|
|
|
|
required this.executionTarget,
|
|
|
|
|
|
required this.status,
|
|
|
|
|
|
required this.primaryLabel,
|
|
|
|
|
|
required this.detailLabel,
|
|
|
|
|
|
required this.ready,
|
|
|
|
|
|
required this.pairingRequired,
|
|
|
|
|
|
required this.gatewayTokenMissing,
|
|
|
|
|
|
required this.lastError,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final AssistantExecutionTarget executionTarget;
|
|
|
|
|
|
final RuntimeConnectionStatus status;
|
|
|
|
|
|
final String primaryLabel;
|
|
|
|
|
|
final String detailLabel;
|
|
|
|
|
|
final bool ready;
|
|
|
|
|
|
final bool pairingRequired;
|
|
|
|
|
|
final bool gatewayTokenMissing;
|
|
|
|
|
|
final String? lastError;
|
|
|
|
|
|
|
|
|
|
|
|
bool get isAiGatewayOnly =>
|
|
|
|
|
|
executionTarget == AssistantExecutionTarget.aiGatewayOnly;
|
|
|
|
|
|
|
|
|
|
|
|
bool get connected => ready;
|
|
|
|
|
|
|
|
|
|
|
|
bool get connecting =>
|
|
|
|
|
|
!isAiGatewayOnly && status == RuntimeConnectionStatus.connecting;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 12:37:19 +08:00
|
|
|
|
enum AssistantMessageViewMode { rendered, raw }
|
|
|
|
|
|
|
|
|
|
|
|
extension AssistantMessageViewModeCopy on AssistantMessageViewMode {
|
|
|
|
|
|
String get label => switch (this) {
|
|
|
|
|
|
AssistantMessageViewMode.rendered => appText('渲染', 'Rendered'),
|
|
|
|
|
|
AssistantMessageViewMode.raw => 'RAW',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static AssistantMessageViewMode fromJsonValue(String? value) {
|
|
|
|
|
|
return AssistantMessageViewMode.values.firstWhere(
|
|
|
|
|
|
(item) => item.name == value,
|
|
|
|
|
|
orElse: () => AssistantMessageViewMode.rendered,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 14:04:29 +08:00
|
|
|
|
enum AssistantPermissionLevel { defaultAccess, fullAccess }
|
|
|
|
|
|
|
|
|
|
|
|
extension AssistantPermissionLevelCopy on AssistantPermissionLevel {
|
|
|
|
|
|
String get label => switch (this) {
|
2026-03-11 15:15:47 +08:00
|
|
|
|
AssistantPermissionLevel.defaultAccess => appText('默认权限', 'Default Access'),
|
|
|
|
|
|
AssistantPermissionLevel.fullAccess => appText('完全访问权限', 'Full Access'),
|
2026-03-11 14:04:29 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
String get promptValue => switch (this) {
|
|
|
|
|
|
AssistantPermissionLevel.defaultAccess => 'default',
|
|
|
|
|
|
AssistantPermissionLevel.fullAccess => 'full-access',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static AssistantPermissionLevel fromJsonValue(String? value) {
|
|
|
|
|
|
return AssistantPermissionLevel.values.firstWhere(
|
|
|
|
|
|
(item) => item.name == value,
|
|
|
|
|
|
orElse: () => AssistantPermissionLevel.defaultAccess,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-15 08:19:53 +08:00
|
|
|
|
enum CodeAgentRuntimeMode { builtIn, externalCli }
|
|
|
|
|
|
|
|
|
|
|
|
extension CodeAgentRuntimeModeCopy on CodeAgentRuntimeMode {
|
|
|
|
|
|
String get label => switch (this) {
|
|
|
|
|
|
CodeAgentRuntimeMode.externalCli => appText(
|
|
|
|
|
|
'外部 Codex CLI',
|
|
|
|
|
|
'External Codex CLI',
|
|
|
|
|
|
),
|
|
|
|
|
|
CodeAgentRuntimeMode.builtIn => appText('内置 Codex', 'Built-in Codex'),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static CodeAgentRuntimeMode fromJsonValue(String? value) {
|
|
|
|
|
|
return CodeAgentRuntimeMode.values.firstWhere(
|
|
|
|
|
|
(item) => item.name == value,
|
|
|
|
|
|
orElse: () => CodeAgentRuntimeMode.externalCli,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-16 17:58:37 +08:00
|
|
|
|
enum VpnMode { tunnel, proxy }
|
|
|
|
|
|
|
|
|
|
|
|
extension VpnModeCopy on VpnMode {
|
|
|
|
|
|
String get label => switch (this) {
|
|
|
|
|
|
VpnMode.tunnel => appText('隧道', 'Tunnel'),
|
|
|
|
|
|
VpnMode.proxy => appText('代理', 'Proxy'),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static VpnMode fromJsonValue(String? value) {
|
|
|
|
|
|
return VpnMode.values.firstWhere(
|
|
|
|
|
|
(item) => item.name == value,
|
|
|
|
|
|
orElse: () => VpnMode.proxy,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum DesktopEnvironment { unknown, gnome, kde }
|
|
|
|
|
|
|
|
|
|
|
|
extension DesktopEnvironmentCopy on DesktopEnvironment {
|
|
|
|
|
|
String get label => switch (this) {
|
|
|
|
|
|
DesktopEnvironment.unknown => appText('未知桌面', 'Unknown Desktop'),
|
|
|
|
|
|
DesktopEnvironment.gnome => 'GNOME',
|
|
|
|
|
|
DesktopEnvironment.kde => 'KDE Plasma',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static DesktopEnvironment fromJsonValue(String? value) {
|
|
|
|
|
|
return DesktopEnvironment.values.firstWhere(
|
|
|
|
|
|
(item) => item.name == value,
|
|
|
|
|
|
orElse: () => DesktopEnvironment.unknown,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class LinuxDesktopConfig {
|
|
|
|
|
|
const LinuxDesktopConfig({
|
|
|
|
|
|
required this.preferredMode,
|
|
|
|
|
|
required this.vpnConnectionName,
|
|
|
|
|
|
required this.proxyHost,
|
|
|
|
|
|
required this.proxyPort,
|
|
|
|
|
|
required this.trayEnabled,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final VpnMode preferredMode;
|
|
|
|
|
|
final String vpnConnectionName;
|
|
|
|
|
|
final String proxyHost;
|
|
|
|
|
|
final int proxyPort;
|
|
|
|
|
|
final bool trayEnabled;
|
|
|
|
|
|
|
|
|
|
|
|
factory LinuxDesktopConfig.defaults() {
|
|
|
|
|
|
return const LinuxDesktopConfig(
|
|
|
|
|
|
preferredMode: VpnMode.proxy,
|
|
|
|
|
|
vpnConnectionName: 'XWorkmate Tunnel',
|
|
|
|
|
|
proxyHost: '127.0.0.1',
|
|
|
|
|
|
proxyPort: 7890,
|
|
|
|
|
|
trayEnabled: true,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LinuxDesktopConfig copyWith({
|
|
|
|
|
|
VpnMode? preferredMode,
|
|
|
|
|
|
String? vpnConnectionName,
|
|
|
|
|
|
String? proxyHost,
|
|
|
|
|
|
int? proxyPort,
|
|
|
|
|
|
bool? trayEnabled,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return LinuxDesktopConfig(
|
|
|
|
|
|
preferredMode: preferredMode ?? this.preferredMode,
|
|
|
|
|
|
vpnConnectionName: vpnConnectionName ?? this.vpnConnectionName,
|
|
|
|
|
|
proxyHost: proxyHost ?? this.proxyHost,
|
|
|
|
|
|
proxyPort: proxyPort ?? this.proxyPort,
|
|
|
|
|
|
trayEnabled: trayEnabled ?? this.trayEnabled,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'preferredMode': preferredMode.name,
|
|
|
|
|
|
'vpnConnectionName': vpnConnectionName,
|
|
|
|
|
|
'proxyHost': proxyHost,
|
|
|
|
|
|
'proxyPort': proxyPort,
|
|
|
|
|
|
'trayEnabled': trayEnabled,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory LinuxDesktopConfig.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
final defaults = LinuxDesktopConfig.defaults();
|
|
|
|
|
|
return LinuxDesktopConfig(
|
|
|
|
|
|
preferredMode: VpnModeCopy.fromJsonValue(
|
|
|
|
|
|
json['preferredMode'] as String?,
|
|
|
|
|
|
),
|
|
|
|
|
|
vpnConnectionName:
|
|
|
|
|
|
json['vpnConnectionName'] as String? ?? defaults.vpnConnectionName,
|
|
|
|
|
|
proxyHost: json['proxyHost'] as String? ?? defaults.proxyHost,
|
|
|
|
|
|
proxyPort: json['proxyPort'] as int? ?? defaults.proxyPort,
|
|
|
|
|
|
trayEnabled: json['trayEnabled'] as bool? ?? defaults.trayEnabled,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class SystemProxyState {
|
|
|
|
|
|
const SystemProxyState({
|
|
|
|
|
|
required this.enabled,
|
|
|
|
|
|
required this.host,
|
|
|
|
|
|
required this.port,
|
|
|
|
|
|
required this.backend,
|
|
|
|
|
|
required this.lastAppliedMode,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final bool enabled;
|
|
|
|
|
|
final String host;
|
|
|
|
|
|
final int port;
|
|
|
|
|
|
final String backend;
|
|
|
|
|
|
final VpnMode lastAppliedMode;
|
|
|
|
|
|
|
|
|
|
|
|
factory SystemProxyState.defaults({LinuxDesktopConfig? config}) {
|
|
|
|
|
|
final resolvedConfig = config ?? LinuxDesktopConfig.defaults();
|
|
|
|
|
|
return SystemProxyState(
|
|
|
|
|
|
enabled: resolvedConfig.preferredMode == VpnMode.proxy,
|
|
|
|
|
|
host: resolvedConfig.proxyHost,
|
|
|
|
|
|
port: resolvedConfig.proxyPort,
|
|
|
|
|
|
backend: '',
|
|
|
|
|
|
lastAppliedMode: resolvedConfig.preferredMode,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SystemProxyState copyWith({
|
|
|
|
|
|
bool? enabled,
|
|
|
|
|
|
String? host,
|
|
|
|
|
|
int? port,
|
|
|
|
|
|
String? backend,
|
|
|
|
|
|
VpnMode? lastAppliedMode,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return SystemProxyState(
|
|
|
|
|
|
enabled: enabled ?? this.enabled,
|
|
|
|
|
|
host: host ?? this.host,
|
|
|
|
|
|
port: port ?? this.port,
|
|
|
|
|
|
backend: backend ?? this.backend,
|
|
|
|
|
|
lastAppliedMode: lastAppliedMode ?? this.lastAppliedMode,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'enabled': enabled,
|
|
|
|
|
|
'host': host,
|
|
|
|
|
|
'port': port,
|
|
|
|
|
|
'backend': backend,
|
|
|
|
|
|
'lastAppliedMode': lastAppliedMode.name,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory SystemProxyState.fromJson(
|
|
|
|
|
|
Map<String, dynamic> json, {
|
|
|
|
|
|
LinuxDesktopConfig? config,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
final defaults = SystemProxyState.defaults(config: config);
|
|
|
|
|
|
return SystemProxyState(
|
|
|
|
|
|
enabled: json['enabled'] as bool? ?? defaults.enabled,
|
|
|
|
|
|
host: json['host'] as String? ?? defaults.host,
|
|
|
|
|
|
port: json['port'] as int? ?? defaults.port,
|
|
|
|
|
|
backend: json['backend'] as String? ?? defaults.backend,
|
|
|
|
|
|
lastAppliedMode: VpnModeCopy.fromJsonValue(
|
|
|
|
|
|
json['lastAppliedMode'] as String?,
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class TunnelSessionState {
|
|
|
|
|
|
const TunnelSessionState({
|
|
|
|
|
|
required this.available,
|
|
|
|
|
|
required this.connected,
|
|
|
|
|
|
required this.connectionName,
|
|
|
|
|
|
required this.backend,
|
|
|
|
|
|
required this.lastError,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final bool available;
|
|
|
|
|
|
final bool connected;
|
|
|
|
|
|
final String connectionName;
|
|
|
|
|
|
final String backend;
|
|
|
|
|
|
final String lastError;
|
|
|
|
|
|
|
|
|
|
|
|
factory TunnelSessionState.defaults({LinuxDesktopConfig? config}) {
|
|
|
|
|
|
final resolvedConfig = config ?? LinuxDesktopConfig.defaults();
|
|
|
|
|
|
return TunnelSessionState(
|
|
|
|
|
|
available: false,
|
|
|
|
|
|
connected: false,
|
|
|
|
|
|
connectionName: resolvedConfig.vpnConnectionName,
|
|
|
|
|
|
backend: '',
|
|
|
|
|
|
lastError: '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TunnelSessionState copyWith({
|
|
|
|
|
|
bool? available,
|
|
|
|
|
|
bool? connected,
|
|
|
|
|
|
String? connectionName,
|
|
|
|
|
|
String? backend,
|
|
|
|
|
|
String? lastError,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return TunnelSessionState(
|
|
|
|
|
|
available: available ?? this.available,
|
|
|
|
|
|
connected: connected ?? this.connected,
|
|
|
|
|
|
connectionName: connectionName ?? this.connectionName,
|
|
|
|
|
|
backend: backend ?? this.backend,
|
|
|
|
|
|
lastError: lastError ?? this.lastError,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'available': available,
|
|
|
|
|
|
'connected': connected,
|
|
|
|
|
|
'connectionName': connectionName,
|
|
|
|
|
|
'backend': backend,
|
|
|
|
|
|
'lastError': lastError,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory TunnelSessionState.fromJson(
|
|
|
|
|
|
Map<String, dynamic> json, {
|
|
|
|
|
|
LinuxDesktopConfig? config,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
final defaults = TunnelSessionState.defaults(config: config);
|
|
|
|
|
|
return TunnelSessionState(
|
|
|
|
|
|
available: json['available'] as bool? ?? defaults.available,
|
|
|
|
|
|
connected: json['connected'] as bool? ?? defaults.connected,
|
|
|
|
|
|
connectionName:
|
|
|
|
|
|
json['connectionName'] as String? ?? defaults.connectionName,
|
|
|
|
|
|
backend: json['backend'] as String? ?? defaults.backend,
|
|
|
|
|
|
lastError: json['lastError'] as String? ?? defaults.lastError,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class DesktopIntegrationState {
|
|
|
|
|
|
const DesktopIntegrationState({
|
|
|
|
|
|
required this.isSupported,
|
|
|
|
|
|
required this.environment,
|
|
|
|
|
|
required this.mode,
|
|
|
|
|
|
required this.trayAvailable,
|
|
|
|
|
|
required this.trayEnabled,
|
|
|
|
|
|
required this.autostartEnabled,
|
|
|
|
|
|
required this.networkManagerAvailable,
|
|
|
|
|
|
required this.systemProxy,
|
|
|
|
|
|
required this.tunnel,
|
|
|
|
|
|
required this.statusMessage,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final bool isSupported;
|
|
|
|
|
|
final DesktopEnvironment environment;
|
|
|
|
|
|
final VpnMode mode;
|
|
|
|
|
|
final bool trayAvailable;
|
|
|
|
|
|
final bool trayEnabled;
|
|
|
|
|
|
final bool autostartEnabled;
|
|
|
|
|
|
final bool networkManagerAvailable;
|
|
|
|
|
|
final SystemProxyState systemProxy;
|
|
|
|
|
|
final TunnelSessionState tunnel;
|
|
|
|
|
|
final String statusMessage;
|
|
|
|
|
|
|
|
|
|
|
|
factory DesktopIntegrationState.loading() {
|
|
|
|
|
|
final config = LinuxDesktopConfig.defaults();
|
|
|
|
|
|
return DesktopIntegrationState(
|
|
|
|
|
|
isSupported: true,
|
|
|
|
|
|
environment: DesktopEnvironment.unknown,
|
|
|
|
|
|
mode: config.preferredMode,
|
|
|
|
|
|
trayAvailable: false,
|
|
|
|
|
|
trayEnabled: config.trayEnabled,
|
|
|
|
|
|
autostartEnabled: false,
|
|
|
|
|
|
networkManagerAvailable: false,
|
|
|
|
|
|
systemProxy: SystemProxyState.defaults(config: config),
|
|
|
|
|
|
tunnel: TunnelSessionState.defaults(config: config),
|
|
|
|
|
|
statusMessage: '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory DesktopIntegrationState.unsupported({
|
|
|
|
|
|
LinuxDesktopConfig? config,
|
|
|
|
|
|
String message = '',
|
|
|
|
|
|
}) {
|
|
|
|
|
|
final resolvedConfig = config ?? LinuxDesktopConfig.defaults();
|
|
|
|
|
|
return DesktopIntegrationState(
|
|
|
|
|
|
isSupported: false,
|
|
|
|
|
|
environment: DesktopEnvironment.unknown,
|
|
|
|
|
|
mode: resolvedConfig.preferredMode,
|
|
|
|
|
|
trayAvailable: false,
|
|
|
|
|
|
trayEnabled: false,
|
|
|
|
|
|
autostartEnabled: false,
|
|
|
|
|
|
networkManagerAvailable: false,
|
|
|
|
|
|
systemProxy: SystemProxyState.defaults(config: resolvedConfig),
|
|
|
|
|
|
tunnel: TunnelSessionState.defaults(config: resolvedConfig),
|
|
|
|
|
|
statusMessage: message,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DesktopIntegrationState copyWith({
|
|
|
|
|
|
bool? isSupported,
|
|
|
|
|
|
DesktopEnvironment? environment,
|
|
|
|
|
|
VpnMode? mode,
|
|
|
|
|
|
bool? trayAvailable,
|
|
|
|
|
|
bool? trayEnabled,
|
|
|
|
|
|
bool? autostartEnabled,
|
|
|
|
|
|
bool? networkManagerAvailable,
|
|
|
|
|
|
SystemProxyState? systemProxy,
|
|
|
|
|
|
TunnelSessionState? tunnel,
|
|
|
|
|
|
String? statusMessage,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return DesktopIntegrationState(
|
|
|
|
|
|
isSupported: isSupported ?? this.isSupported,
|
|
|
|
|
|
environment: environment ?? this.environment,
|
|
|
|
|
|
mode: mode ?? this.mode,
|
|
|
|
|
|
trayAvailable: trayAvailable ?? this.trayAvailable,
|
|
|
|
|
|
trayEnabled: trayEnabled ?? this.trayEnabled,
|
|
|
|
|
|
autostartEnabled: autostartEnabled ?? this.autostartEnabled,
|
|
|
|
|
|
networkManagerAvailable:
|
|
|
|
|
|
networkManagerAvailable ?? this.networkManagerAvailable,
|
|
|
|
|
|
systemProxy: systemProxy ?? this.systemProxy,
|
|
|
|
|
|
tunnel: tunnel ?? this.tunnel,
|
|
|
|
|
|
statusMessage: statusMessage ?? this.statusMessage,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'isSupported': isSupported,
|
|
|
|
|
|
'environment': environment.name,
|
|
|
|
|
|
'mode': mode.name,
|
|
|
|
|
|
'trayAvailable': trayAvailable,
|
|
|
|
|
|
'trayEnabled': trayEnabled,
|
|
|
|
|
|
'autostartEnabled': autostartEnabled,
|
|
|
|
|
|
'networkManagerAvailable': networkManagerAvailable,
|
|
|
|
|
|
'systemProxy': systemProxy.toJson(),
|
|
|
|
|
|
'tunnel': tunnel.toJson(),
|
|
|
|
|
|
'statusMessage': statusMessage,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory DesktopIntegrationState.fromJson(
|
|
|
|
|
|
Map<String, dynamic> json, {
|
|
|
|
|
|
LinuxDesktopConfig? fallbackConfig,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
final config = fallbackConfig ?? LinuxDesktopConfig.defaults();
|
|
|
|
|
|
return DesktopIntegrationState(
|
|
|
|
|
|
isSupported: json['isSupported'] as bool? ?? true,
|
|
|
|
|
|
environment: DesktopEnvironmentCopy.fromJsonValue(
|
|
|
|
|
|
json['environment'] as String?,
|
|
|
|
|
|
),
|
|
|
|
|
|
mode: VpnModeCopy.fromJsonValue(json['mode'] as String?),
|
|
|
|
|
|
trayAvailable: json['trayAvailable'] as bool? ?? false,
|
|
|
|
|
|
trayEnabled: json['trayEnabled'] as bool? ?? config.trayEnabled,
|
|
|
|
|
|
autostartEnabled: json['autostartEnabled'] as bool? ?? false,
|
|
|
|
|
|
networkManagerAvailable:
|
|
|
|
|
|
json['networkManagerAvailable'] as bool? ?? false,
|
|
|
|
|
|
systemProxy: SystemProxyState.fromJson(
|
|
|
|
|
|
(json['systemProxy'] as Map?)?.cast<String, dynamic>() ?? const {},
|
|
|
|
|
|
config: config,
|
|
|
|
|
|
),
|
|
|
|
|
|
tunnel: TunnelSessionState.fromJson(
|
|
|
|
|
|
(json['tunnel'] as Map?)?.cast<String, dynamic>() ?? const {},
|
|
|
|
|
|
config: config,
|
|
|
|
|
|
),
|
|
|
|
|
|
statusMessage: json['statusMessage'] as String? ?? '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 07:32:52 +08:00
|
|
|
|
class GatewayConnectionProfile {
|
|
|
|
|
|
const GatewayConnectionProfile({
|
|
|
|
|
|
required this.mode,
|
|
|
|
|
|
required this.useSetupCode,
|
|
|
|
|
|
required this.setupCode,
|
|
|
|
|
|
required this.host,
|
|
|
|
|
|
required this.port,
|
|
|
|
|
|
required this.tls,
|
|
|
|
|
|
required this.selectedAgentId,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final RuntimeConnectionMode mode;
|
|
|
|
|
|
final bool useSetupCode;
|
|
|
|
|
|
final String setupCode;
|
|
|
|
|
|
final String host;
|
|
|
|
|
|
final int port;
|
|
|
|
|
|
final bool tls;
|
|
|
|
|
|
final String selectedAgentId;
|
|
|
|
|
|
|
|
|
|
|
|
factory GatewayConnectionProfile.defaults() {
|
|
|
|
|
|
return const GatewayConnectionProfile(
|
|
|
|
|
|
mode: RuntimeConnectionMode.remote,
|
|
|
|
|
|
useSetupCode: false,
|
|
|
|
|
|
setupCode: '',
|
|
|
|
|
|
host: 'openclaw.svc.plus',
|
|
|
|
|
|
port: 443,
|
|
|
|
|
|
tls: true,
|
|
|
|
|
|
selectedAgentId: '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GatewayConnectionProfile copyWith({
|
|
|
|
|
|
RuntimeConnectionMode? mode,
|
|
|
|
|
|
bool? useSetupCode,
|
|
|
|
|
|
String? setupCode,
|
|
|
|
|
|
String? host,
|
|
|
|
|
|
int? port,
|
|
|
|
|
|
bool? tls,
|
|
|
|
|
|
String? selectedAgentId,
|
|
|
|
|
|
}) {
|
2026-03-12 09:55:17 +08:00
|
|
|
|
final normalized = _normalizeGatewayManualEndpoint(
|
|
|
|
|
|
host: host ?? this.host,
|
|
|
|
|
|
port: port ?? this.port,
|
|
|
|
|
|
tls: tls ?? this.tls,
|
|
|
|
|
|
);
|
2026-03-11 07:32:52 +08:00
|
|
|
|
return GatewayConnectionProfile(
|
|
|
|
|
|
mode: mode ?? this.mode,
|
|
|
|
|
|
useSetupCode: useSetupCode ?? this.useSetupCode,
|
|
|
|
|
|
setupCode: setupCode ?? this.setupCode,
|
2026-03-12 09:55:17 +08:00
|
|
|
|
host: normalized.host,
|
|
|
|
|
|
port: normalized.port,
|
|
|
|
|
|
tls: normalized.tls,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
selectedAgentId: selectedAgentId ?? this.selectedAgentId,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'mode': mode.name,
|
|
|
|
|
|
'useSetupCode': useSetupCode,
|
|
|
|
|
|
'setupCode': setupCode,
|
|
|
|
|
|
'host': host,
|
|
|
|
|
|
'port': port,
|
|
|
|
|
|
'tls': tls,
|
|
|
|
|
|
'selectedAgentId': selectedAgentId,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory GatewayConnectionProfile.fromJson(Map<String, dynamic> json) {
|
2026-03-12 09:55:17 +08:00
|
|
|
|
final defaults = GatewayConnectionProfile.defaults();
|
|
|
|
|
|
final normalized = _normalizeGatewayManualEndpoint(
|
|
|
|
|
|
host: json['host'] as String? ?? defaults.host,
|
|
|
|
|
|
port: json['port'] as int? ?? defaults.port,
|
|
|
|
|
|
tls: json['tls'] as bool? ?? defaults.tls,
|
|
|
|
|
|
);
|
2026-03-11 07:32:52 +08:00
|
|
|
|
return GatewayConnectionProfile(
|
|
|
|
|
|
mode: RuntimeConnectionModeCopy.fromJsonValue(json['mode'] as String?),
|
|
|
|
|
|
useSetupCode: json['useSetupCode'] as bool? ?? false,
|
|
|
|
|
|
setupCode: json['setupCode'] as String? ?? '',
|
2026-03-12 09:55:17 +08:00
|
|
|
|
host: normalized.host,
|
|
|
|
|
|
port: normalized.port,
|
|
|
|
|
|
tls: normalized.tls,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
selectedAgentId: json['selectedAgentId'] as String? ?? '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 09:55:17 +08:00
|
|
|
|
({String host, int port, bool tls}) _normalizeGatewayManualEndpoint({
|
|
|
|
|
|
required String host,
|
|
|
|
|
|
required int port,
|
|
|
|
|
|
required bool tls,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
final trimmedHost = host.trim();
|
|
|
|
|
|
if (trimmedHost.isEmpty) {
|
|
|
|
|
|
return (host: trimmedHost, port: port, tls: tls);
|
|
|
|
|
|
}
|
|
|
|
|
|
final normalizedInput = trimmedHost.contains('://')
|
|
|
|
|
|
? trimmedHost
|
|
|
|
|
|
: '${tls ? 'https' : 'http'}://$trimmedHost:${port > 0 ? port : (tls ? 443 : 18789)}';
|
|
|
|
|
|
final uri = Uri.tryParse(normalizedInput);
|
|
|
|
|
|
final normalizedHost = uri?.host.trim() ?? trimmedHost;
|
|
|
|
|
|
if (normalizedHost.isEmpty) {
|
|
|
|
|
|
return (host: trimmedHost, port: port, tls: tls);
|
|
|
|
|
|
}
|
|
|
|
|
|
final scheme = uri?.scheme.trim().toLowerCase() ?? (tls ? 'https' : 'http');
|
|
|
|
|
|
final normalizedTls = switch (scheme) {
|
|
|
|
|
|
'ws' || 'http' => false,
|
|
|
|
|
|
_ => true,
|
|
|
|
|
|
};
|
|
|
|
|
|
final normalizedPort = uri?.hasPort == true
|
|
|
|
|
|
? uri!.port
|
|
|
|
|
|
: normalizedTls
|
|
|
|
|
|
? 443
|
|
|
|
|
|
: 18789;
|
|
|
|
|
|
return (
|
|
|
|
|
|
host: normalizedHost,
|
|
|
|
|
|
port: normalizedPort > 0 ? normalizedPort : port,
|
|
|
|
|
|
tls: normalizedTls,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 07:32:52 +08:00
|
|
|
|
class OllamaLocalConfig {
|
|
|
|
|
|
const OllamaLocalConfig({
|
|
|
|
|
|
required this.endpoint,
|
|
|
|
|
|
required this.defaultModel,
|
|
|
|
|
|
required this.autoDiscover,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String endpoint;
|
|
|
|
|
|
final String defaultModel;
|
|
|
|
|
|
final bool autoDiscover;
|
|
|
|
|
|
|
|
|
|
|
|
factory OllamaLocalConfig.defaults() {
|
|
|
|
|
|
return const OllamaLocalConfig(
|
|
|
|
|
|
endpoint: 'http://127.0.0.1:11434',
|
|
|
|
|
|
defaultModel: 'qwen2.5-coder:latest',
|
|
|
|
|
|
autoDiscover: true,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
OllamaLocalConfig copyWith({
|
|
|
|
|
|
String? endpoint,
|
|
|
|
|
|
String? defaultModel,
|
|
|
|
|
|
bool? autoDiscover,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return OllamaLocalConfig(
|
|
|
|
|
|
endpoint: endpoint ?? this.endpoint,
|
|
|
|
|
|
defaultModel: defaultModel ?? this.defaultModel,
|
|
|
|
|
|
autoDiscover: autoDiscover ?? this.autoDiscover,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'endpoint': endpoint,
|
|
|
|
|
|
'defaultModel': defaultModel,
|
|
|
|
|
|
'autoDiscover': autoDiscover,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory OllamaLocalConfig.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return OllamaLocalConfig(
|
2026-03-11 14:04:29 +08:00
|
|
|
|
endpoint:
|
|
|
|
|
|
json['endpoint'] as String? ?? OllamaLocalConfig.defaults().endpoint,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
defaultModel:
|
|
|
|
|
|
json['defaultModel'] as String? ??
|
|
|
|
|
|
OllamaLocalConfig.defaults().defaultModel,
|
|
|
|
|
|
autoDiscover: json['autoDiscover'] as bool? ?? true,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class OllamaCloudConfig {
|
|
|
|
|
|
const OllamaCloudConfig({
|
|
|
|
|
|
required this.baseUrl,
|
|
|
|
|
|
required this.organization,
|
|
|
|
|
|
required this.workspace,
|
|
|
|
|
|
required this.defaultModel,
|
|
|
|
|
|
required this.apiKeyRef,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String baseUrl;
|
|
|
|
|
|
final String organization;
|
|
|
|
|
|
final String workspace;
|
|
|
|
|
|
final String defaultModel;
|
|
|
|
|
|
final String apiKeyRef;
|
|
|
|
|
|
|
|
|
|
|
|
factory OllamaCloudConfig.defaults() {
|
|
|
|
|
|
return const OllamaCloudConfig(
|
2026-03-20 09:13:22 +08:00
|
|
|
|
baseUrl: 'https://ollama.com',
|
2026-03-11 07:32:52 +08:00
|
|
|
|
organization: '',
|
|
|
|
|
|
workspace: '',
|
|
|
|
|
|
defaultModel: 'gpt-oss:120b',
|
|
|
|
|
|
apiKeyRef: 'ollama_cloud_api_key',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
OllamaCloudConfig copyWith({
|
|
|
|
|
|
String? baseUrl,
|
|
|
|
|
|
String? organization,
|
|
|
|
|
|
String? workspace,
|
|
|
|
|
|
String? defaultModel,
|
|
|
|
|
|
String? apiKeyRef,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return OllamaCloudConfig(
|
|
|
|
|
|
baseUrl: baseUrl ?? this.baseUrl,
|
|
|
|
|
|
organization: organization ?? this.organization,
|
|
|
|
|
|
workspace: workspace ?? this.workspace,
|
|
|
|
|
|
defaultModel: defaultModel ?? this.defaultModel,
|
|
|
|
|
|
apiKeyRef: apiKeyRef ?? this.apiKeyRef,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'baseUrl': baseUrl,
|
|
|
|
|
|
'organization': organization,
|
|
|
|
|
|
'workspace': workspace,
|
|
|
|
|
|
'defaultModel': defaultModel,
|
|
|
|
|
|
'apiKeyRef': apiKeyRef,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory OllamaCloudConfig.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return OllamaCloudConfig(
|
2026-03-11 14:04:29 +08:00
|
|
|
|
baseUrl:
|
|
|
|
|
|
json['baseUrl'] as String? ?? OllamaCloudConfig.defaults().baseUrl,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
organization: json['organization'] as String? ?? '',
|
|
|
|
|
|
workspace: json['workspace'] as String? ?? '',
|
|
|
|
|
|
defaultModel:
|
|
|
|
|
|
json['defaultModel'] as String? ??
|
|
|
|
|
|
OllamaCloudConfig.defaults().defaultModel,
|
|
|
|
|
|
apiKeyRef:
|
2026-03-11 14:04:29 +08:00
|
|
|
|
json['apiKeyRef'] as String? ??
|
|
|
|
|
|
OllamaCloudConfig.defaults().apiKeyRef,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class VaultConfig {
|
|
|
|
|
|
const VaultConfig({
|
|
|
|
|
|
required this.address,
|
|
|
|
|
|
required this.namespace,
|
|
|
|
|
|
required this.authMode,
|
|
|
|
|
|
required this.tokenRef,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String address;
|
|
|
|
|
|
final String namespace;
|
|
|
|
|
|
final String authMode;
|
|
|
|
|
|
final String tokenRef;
|
|
|
|
|
|
|
|
|
|
|
|
factory VaultConfig.defaults() {
|
|
|
|
|
|
return const VaultConfig(
|
|
|
|
|
|
address: 'http://127.0.0.1:8200',
|
|
|
|
|
|
namespace: 'default',
|
|
|
|
|
|
authMode: 'token',
|
|
|
|
|
|
tokenRef: 'vault_token',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
VaultConfig copyWith({
|
|
|
|
|
|
String? address,
|
|
|
|
|
|
String? namespace,
|
|
|
|
|
|
String? authMode,
|
|
|
|
|
|
String? tokenRef,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return VaultConfig(
|
|
|
|
|
|
address: address ?? this.address,
|
|
|
|
|
|
namespace: namespace ?? this.namespace,
|
|
|
|
|
|
authMode: authMode ?? this.authMode,
|
|
|
|
|
|
tokenRef: tokenRef ?? this.tokenRef,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'address': address,
|
|
|
|
|
|
'namespace': namespace,
|
|
|
|
|
|
'authMode': authMode,
|
|
|
|
|
|
'tokenRef': tokenRef,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory VaultConfig.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return VaultConfig(
|
|
|
|
|
|
address: json['address'] as String? ?? VaultConfig.defaults().address,
|
2026-03-11 14:04:29 +08:00
|
|
|
|
namespace:
|
|
|
|
|
|
json['namespace'] as String? ?? VaultConfig.defaults().namespace,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
authMode: json['authMode'] as String? ?? VaultConfig.defaults().authMode,
|
|
|
|
|
|
tokenRef: json['tokenRef'] as String? ?? VaultConfig.defaults().tokenRef,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 15:21:23 +08:00
|
|
|
|
class AiGatewayProfile {
|
|
|
|
|
|
const AiGatewayProfile({
|
2026-03-11 07:32:52 +08:00
|
|
|
|
required this.name,
|
2026-03-13 15:21:23 +08:00
|
|
|
|
required this.baseUrl,
|
|
|
|
|
|
required this.apiKeyRef,
|
|
|
|
|
|
required this.availableModels,
|
|
|
|
|
|
required this.selectedModels,
|
|
|
|
|
|
required this.syncState,
|
|
|
|
|
|
required this.syncMessage,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String name;
|
2026-03-13 15:21:23 +08:00
|
|
|
|
final String baseUrl;
|
|
|
|
|
|
final String apiKeyRef;
|
|
|
|
|
|
final List<String> availableModels;
|
|
|
|
|
|
final List<String> selectedModels;
|
|
|
|
|
|
final String syncState;
|
|
|
|
|
|
final String syncMessage;
|
|
|
|
|
|
|
|
|
|
|
|
factory AiGatewayProfile.defaults() {
|
|
|
|
|
|
return const AiGatewayProfile(
|
|
|
|
|
|
name: 'AI Gateway',
|
|
|
|
|
|
baseUrl: '',
|
|
|
|
|
|
apiKeyRef: 'ai_gateway_api_key',
|
|
|
|
|
|
availableModels: <String>[],
|
|
|
|
|
|
selectedModels: <String>[],
|
|
|
|
|
|
syncState: 'idle',
|
|
|
|
|
|
syncMessage: 'Ready to sync models',
|
2026-03-11 07:32:52 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 15:21:23 +08:00
|
|
|
|
AiGatewayProfile copyWith({
|
2026-03-11 07:32:52 +08:00
|
|
|
|
String? name,
|
2026-03-13 15:21:23 +08:00
|
|
|
|
String? baseUrl,
|
|
|
|
|
|
String? apiKeyRef,
|
|
|
|
|
|
List<String>? availableModels,
|
|
|
|
|
|
List<String>? selectedModels,
|
|
|
|
|
|
String? syncState,
|
|
|
|
|
|
String? syncMessage,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
}) {
|
2026-03-13 15:21:23 +08:00
|
|
|
|
return AiGatewayProfile(
|
2026-03-11 07:32:52 +08:00
|
|
|
|
name: name ?? this.name,
|
2026-03-13 15:21:23 +08:00
|
|
|
|
baseUrl: baseUrl ?? this.baseUrl,
|
|
|
|
|
|
apiKeyRef: apiKeyRef ?? this.apiKeyRef,
|
|
|
|
|
|
availableModels: availableModels ?? this.availableModels,
|
|
|
|
|
|
selectedModels: selectedModels ?? this.selectedModels,
|
|
|
|
|
|
syncState: syncState ?? this.syncState,
|
|
|
|
|
|
syncMessage: syncMessage ?? this.syncMessage,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'name': name,
|
2026-03-13 15:21:23 +08:00
|
|
|
|
'baseUrl': baseUrl,
|
|
|
|
|
|
'apiKeyRef': apiKeyRef,
|
|
|
|
|
|
'availableModels': availableModels,
|
|
|
|
|
|
'selectedModels': selectedModels,
|
|
|
|
|
|
'syncState': syncState,
|
|
|
|
|
|
'syncMessage': syncMessage,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 15:21:23 +08:00
|
|
|
|
factory AiGatewayProfile.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
List<String> normalizeList(Object? value) {
|
|
|
|
|
|
if (value is! List) {
|
|
|
|
|
|
return const <String>[];
|
|
|
|
|
|
}
|
|
|
|
|
|
return value
|
|
|
|
|
|
.map((item) => item.toString().trim())
|
|
|
|
|
|
.where((item) => item.isNotEmpty)
|
|
|
|
|
|
.toList(growable: false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final defaults = AiGatewayProfile.defaults();
|
|
|
|
|
|
final availableModels = normalizeList(json['availableModels']);
|
|
|
|
|
|
final selectedModels = normalizeList(json['selectedModels'])
|
|
|
|
|
|
.where(
|
|
|
|
|
|
(item) => availableModels.isEmpty || availableModels.contains(item),
|
|
|
|
|
|
)
|
|
|
|
|
|
.toList(growable: false);
|
|
|
|
|
|
final legacyFilePath = json['filePath'] as String?;
|
|
|
|
|
|
final legacyBaseUrl =
|
|
|
|
|
|
legacyFilePath != null && legacyFilePath.trim().startsWith('http')
|
|
|
|
|
|
? legacyFilePath.trim()
|
|
|
|
|
|
: null;
|
|
|
|
|
|
return AiGatewayProfile(
|
|
|
|
|
|
name: json['name'] as String? ?? defaults.name,
|
|
|
|
|
|
baseUrl: json['baseUrl'] as String? ?? legacyBaseUrl ?? defaults.baseUrl,
|
|
|
|
|
|
apiKeyRef: json['apiKeyRef'] as String? ?? defaults.apiKeyRef,
|
|
|
|
|
|
availableModels: availableModels,
|
|
|
|
|
|
selectedModels: selectedModels,
|
|
|
|
|
|
syncState: json['syncState'] as String? ?? defaults.syncState,
|
|
|
|
|
|
syncMessage: json['syncMessage'] as String? ?? defaults.syncMessage,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 15:21:23 +08:00
|
|
|
|
class AiGatewayConnectionCheck {
|
|
|
|
|
|
const AiGatewayConnectionCheck({
|
|
|
|
|
|
required this.state,
|
|
|
|
|
|
required this.message,
|
|
|
|
|
|
required this.endpoint,
|
|
|
|
|
|
required this.modelCount,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String state;
|
|
|
|
|
|
final String message;
|
|
|
|
|
|
final String endpoint;
|
|
|
|
|
|
final int modelCount;
|
|
|
|
|
|
|
|
|
|
|
|
bool get success => state == 'ready' || state == 'empty';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-22 10:50:29 +08:00
|
|
|
|
enum WebSessionPersistenceMode { browser, remote }
|
|
|
|
|
|
|
|
|
|
|
|
extension WebSessionPersistenceModeCopy on WebSessionPersistenceMode {
|
|
|
|
|
|
String get label => switch (this) {
|
|
|
|
|
|
WebSessionPersistenceMode.browser => appText('浏览器本地缓存', 'Browser cache'),
|
|
|
|
|
|
WebSessionPersistenceMode.remote => appText(
|
|
|
|
|
|
'远端 Session API',
|
|
|
|
|
|
'Remote session API',
|
|
|
|
|
|
),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static WebSessionPersistenceMode fromJsonValue(String? value) {
|
|
|
|
|
|
return WebSessionPersistenceMode.values.firstWhere(
|
|
|
|
|
|
(item) => item.name == value,
|
|
|
|
|
|
orElse: () => WebSessionPersistenceMode.browser,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class WebSessionPersistenceConfig {
|
|
|
|
|
|
const WebSessionPersistenceConfig({
|
|
|
|
|
|
required this.mode,
|
|
|
|
|
|
required this.remoteBaseUrl,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final WebSessionPersistenceMode mode;
|
|
|
|
|
|
final String remoteBaseUrl;
|
|
|
|
|
|
|
|
|
|
|
|
factory WebSessionPersistenceConfig.defaults() {
|
|
|
|
|
|
return const WebSessionPersistenceConfig(
|
|
|
|
|
|
mode: WebSessionPersistenceMode.browser,
|
|
|
|
|
|
remoteBaseUrl: '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool get usesRemoteApi =>
|
|
|
|
|
|
mode == WebSessionPersistenceMode.remote &&
|
|
|
|
|
|
remoteBaseUrl.trim().isNotEmpty;
|
|
|
|
|
|
|
|
|
|
|
|
WebSessionPersistenceConfig copyWith({
|
|
|
|
|
|
WebSessionPersistenceMode? mode,
|
|
|
|
|
|
String? remoteBaseUrl,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return WebSessionPersistenceConfig(
|
|
|
|
|
|
mode: mode ?? this.mode,
|
|
|
|
|
|
remoteBaseUrl: remoteBaseUrl ?? this.remoteBaseUrl,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {'mode': mode.name, 'remoteBaseUrl': remoteBaseUrl};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory WebSessionPersistenceConfig.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
final defaults = WebSessionPersistenceConfig.defaults();
|
|
|
|
|
|
return WebSessionPersistenceConfig(
|
|
|
|
|
|
mode: WebSessionPersistenceModeCopy.fromJsonValue(
|
|
|
|
|
|
json['mode'] as String?,
|
|
|
|
|
|
),
|
|
|
|
|
|
remoteBaseUrl: json['remoteBaseUrl'] as String? ?? defaults.remoteBaseUrl,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 07:32:52 +08:00
|
|
|
|
class SettingsSnapshot {
|
|
|
|
|
|
const SettingsSnapshot({
|
2026-03-11 15:15:47 +08:00
|
|
|
|
required this.appLanguage,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
required this.appActive,
|
|
|
|
|
|
required this.launchAtLogin,
|
|
|
|
|
|
required this.showDockIcon,
|
|
|
|
|
|
required this.workspacePath,
|
|
|
|
|
|
required this.remoteProjectRoot,
|
|
|
|
|
|
required this.cliPath,
|
2026-03-15 08:19:53 +08:00
|
|
|
|
required this.codeAgentRuntimeMode,
|
|
|
|
|
|
required this.codexCliPath,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
required this.defaultModel,
|
|
|
|
|
|
required this.defaultProvider,
|
|
|
|
|
|
required this.gateway,
|
|
|
|
|
|
required this.ollamaLocal,
|
|
|
|
|
|
required this.ollamaCloud,
|
|
|
|
|
|
required this.vault,
|
2026-03-13 15:21:23 +08:00
|
|
|
|
required this.aiGateway,
|
2026-03-22 10:50:29 +08:00
|
|
|
|
required this.webSessionPersistence,
|
2026-03-19 12:33:50 +08:00
|
|
|
|
required this.multiAgent,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
required this.experimentalCanvas,
|
|
|
|
|
|
required this.experimentalBridge,
|
|
|
|
|
|
required this.experimentalDebug,
|
|
|
|
|
|
required this.accountBaseUrl,
|
|
|
|
|
|
required this.accountUsername,
|
|
|
|
|
|
required this.accountWorkspace,
|
|
|
|
|
|
required this.accountLocalMode,
|
2026-03-16 17:58:37 +08:00
|
|
|
|
required this.linuxDesktop,
|
2026-03-11 14:04:29 +08:00
|
|
|
|
required this.assistantExecutionTarget,
|
|
|
|
|
|
required this.assistantPermissionLevel,
|
2026-03-15 10:14:33 +08:00
|
|
|
|
required this.assistantNavigationDestinations,
|
2026-03-19 17:59:20 +08:00
|
|
|
|
required this.assistantCustomTaskTitles,
|
2026-03-19 19:55:36 +08:00
|
|
|
|
required this.assistantArchivedTaskKeys,
|
2026-03-22 11:59:54 +08:00
|
|
|
|
required this.assistantLastSessionKey,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-11 15:15:47 +08:00
|
|
|
|
final AppLanguage appLanguage;
|
2026-03-11 07:32:52 +08:00
|
|
|
|
final bool appActive;
|
|
|
|
|
|
final bool launchAtLogin;
|
|
|
|
|
|
final bool showDockIcon;
|
|
|
|
|
|
final String workspacePath;
|
|
|
|
|
|
final String remoteProjectRoot;
|
|
|
|
|
|
final String cliPath;
|
2026-03-15 08:19:53 +08:00
|
|
|
|
final CodeAgentRuntimeMode codeAgentRuntimeMode;
|
|
|
|
|
|
final String codexCliPath;
|
2026-03-11 07:32:52 +08:00
|
|
|
|
final String defaultModel;
|
|
|
|
|
|
final String defaultProvider;
|
|
|
|
|
|
final GatewayConnectionProfile gateway;
|
|
|
|
|
|
final OllamaLocalConfig ollamaLocal;
|
|
|
|
|
|
final OllamaCloudConfig ollamaCloud;
|
|
|
|
|
|
final VaultConfig vault;
|
2026-03-13 15:21:23 +08:00
|
|
|
|
final AiGatewayProfile aiGateway;
|
2026-03-22 10:50:29 +08:00
|
|
|
|
final WebSessionPersistenceConfig webSessionPersistence;
|
2026-03-19 12:33:50 +08:00
|
|
|
|
final MultiAgentConfig multiAgent;
|
2026-03-11 07:32:52 +08:00
|
|
|
|
final bool experimentalCanvas;
|
|
|
|
|
|
final bool experimentalBridge;
|
|
|
|
|
|
final bool experimentalDebug;
|
|
|
|
|
|
final String accountBaseUrl;
|
|
|
|
|
|
final String accountUsername;
|
|
|
|
|
|
final String accountWorkspace;
|
|
|
|
|
|
final bool accountLocalMode;
|
2026-03-16 17:58:37 +08:00
|
|
|
|
final LinuxDesktopConfig linuxDesktop;
|
2026-03-11 14:04:29 +08:00
|
|
|
|
final AssistantExecutionTarget assistantExecutionTarget;
|
|
|
|
|
|
final AssistantPermissionLevel assistantPermissionLevel;
|
2026-03-15 10:14:33 +08:00
|
|
|
|
final List<WorkspaceDestination> assistantNavigationDestinations;
|
2026-03-19 17:59:20 +08:00
|
|
|
|
final Map<String, String> assistantCustomTaskTitles;
|
2026-03-19 19:55:36 +08:00
|
|
|
|
final List<String> assistantArchivedTaskKeys;
|
2026-03-22 11:59:54 +08:00
|
|
|
|
final String assistantLastSessionKey;
|
2026-03-11 07:32:52 +08:00
|
|
|
|
|
|
|
|
|
|
factory SettingsSnapshot.defaults() {
|
|
|
|
|
|
return SettingsSnapshot(
|
2026-03-11 15:15:47 +08:00
|
|
|
|
appLanguage: AppLanguage.zh,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
appActive: true,
|
|
|
|
|
|
launchAtLogin: false,
|
|
|
|
|
|
showDockIcon: true,
|
|
|
|
|
|
workspacePath: '/opt/data',
|
|
|
|
|
|
remoteProjectRoot: '/opt/data/workspace',
|
|
|
|
|
|
cliPath: 'openclaw',
|
2026-03-15 08:19:53 +08:00
|
|
|
|
codeAgentRuntimeMode: CodeAgentRuntimeMode.externalCli,
|
|
|
|
|
|
codexCliPath: '',
|
2026-03-13 15:21:23 +08:00
|
|
|
|
defaultModel: '',
|
2026-03-11 07:32:52 +08:00
|
|
|
|
defaultProvider: 'gateway',
|
|
|
|
|
|
gateway: GatewayConnectionProfile.defaults(),
|
|
|
|
|
|
ollamaLocal: OllamaLocalConfig.defaults(),
|
|
|
|
|
|
ollamaCloud: OllamaCloudConfig.defaults(),
|
|
|
|
|
|
vault: VaultConfig.defaults(),
|
2026-03-13 15:21:23 +08:00
|
|
|
|
aiGateway: AiGatewayProfile.defaults(),
|
2026-03-22 10:50:29 +08:00
|
|
|
|
webSessionPersistence: WebSessionPersistenceConfig.defaults(),
|
2026-03-19 12:33:50 +08:00
|
|
|
|
multiAgent: MultiAgentConfig.defaults(),
|
2026-03-11 07:32:52 +08:00
|
|
|
|
experimentalCanvas: false,
|
|
|
|
|
|
experimentalBridge: false,
|
|
|
|
|
|
experimentalDebug: false,
|
|
|
|
|
|
accountBaseUrl: 'https://accounts.svc.plus',
|
|
|
|
|
|
accountUsername: '',
|
|
|
|
|
|
accountWorkspace: 'Default Workspace',
|
|
|
|
|
|
accountLocalMode: true,
|
2026-03-16 17:58:37 +08:00
|
|
|
|
linuxDesktop: LinuxDesktopConfig.defaults(),
|
2026-03-11 14:04:29 +08:00
|
|
|
|
assistantExecutionTarget: AssistantExecutionTarget.local,
|
|
|
|
|
|
assistantPermissionLevel: AssistantPermissionLevel.defaultAccess,
|
2026-03-15 10:14:33 +08:00
|
|
|
|
assistantNavigationDestinations: kAssistantNavigationDestinationDefaults,
|
2026-03-19 17:59:20 +08:00
|
|
|
|
assistantCustomTaskTitles: const <String, String>{},
|
2026-03-19 19:55:36 +08:00
|
|
|
|
assistantArchivedTaskKeys: const <String>[],
|
2026-03-22 11:59:54 +08:00
|
|
|
|
assistantLastSessionKey: '',
|
2026-03-11 07:32:52 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SettingsSnapshot copyWith({
|
2026-03-11 15:15:47 +08:00
|
|
|
|
AppLanguage? appLanguage,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
bool? appActive,
|
|
|
|
|
|
bool? launchAtLogin,
|
|
|
|
|
|
bool? showDockIcon,
|
|
|
|
|
|
String? workspacePath,
|
|
|
|
|
|
String? remoteProjectRoot,
|
|
|
|
|
|
String? cliPath,
|
2026-03-15 08:19:53 +08:00
|
|
|
|
CodeAgentRuntimeMode? codeAgentRuntimeMode,
|
|
|
|
|
|
String? codexCliPath,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
String? defaultModel,
|
|
|
|
|
|
String? defaultProvider,
|
|
|
|
|
|
GatewayConnectionProfile? gateway,
|
|
|
|
|
|
OllamaLocalConfig? ollamaLocal,
|
|
|
|
|
|
OllamaCloudConfig? ollamaCloud,
|
|
|
|
|
|
VaultConfig? vault,
|
2026-03-13 15:21:23 +08:00
|
|
|
|
AiGatewayProfile? aiGateway,
|
2026-03-22 10:50:29 +08:00
|
|
|
|
WebSessionPersistenceConfig? webSessionPersistence,
|
2026-03-19 12:33:50 +08:00
|
|
|
|
MultiAgentConfig? multiAgent,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
bool? experimentalCanvas,
|
|
|
|
|
|
bool? experimentalBridge,
|
|
|
|
|
|
bool? experimentalDebug,
|
|
|
|
|
|
String? accountBaseUrl,
|
|
|
|
|
|
String? accountUsername,
|
|
|
|
|
|
String? accountWorkspace,
|
|
|
|
|
|
bool? accountLocalMode,
|
2026-03-16 17:58:37 +08:00
|
|
|
|
LinuxDesktopConfig? linuxDesktop,
|
2026-03-11 14:04:29 +08:00
|
|
|
|
AssistantExecutionTarget? assistantExecutionTarget,
|
|
|
|
|
|
AssistantPermissionLevel? assistantPermissionLevel,
|
2026-03-15 10:14:33 +08:00
|
|
|
|
List<WorkspaceDestination>? assistantNavigationDestinations,
|
2026-03-19 17:59:20 +08:00
|
|
|
|
Map<String, String>? assistantCustomTaskTitles,
|
2026-03-19 19:55:36 +08:00
|
|
|
|
List<String>? assistantArchivedTaskKeys,
|
2026-03-22 11:59:54 +08:00
|
|
|
|
String? assistantLastSessionKey,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
}) {
|
|
|
|
|
|
return SettingsSnapshot(
|
2026-03-11 15:15:47 +08:00
|
|
|
|
appLanguage: appLanguage ?? this.appLanguage,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
appActive: appActive ?? this.appActive,
|
|
|
|
|
|
launchAtLogin: launchAtLogin ?? this.launchAtLogin,
|
|
|
|
|
|
showDockIcon: showDockIcon ?? this.showDockIcon,
|
|
|
|
|
|
workspacePath: workspacePath ?? this.workspacePath,
|
|
|
|
|
|
remoteProjectRoot: remoteProjectRoot ?? this.remoteProjectRoot,
|
|
|
|
|
|
cliPath: cliPath ?? this.cliPath,
|
2026-03-15 08:19:53 +08:00
|
|
|
|
codeAgentRuntimeMode: codeAgentRuntimeMode ?? this.codeAgentRuntimeMode,
|
|
|
|
|
|
codexCliPath: codexCliPath ?? this.codexCliPath,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
defaultModel: defaultModel ?? this.defaultModel,
|
|
|
|
|
|
defaultProvider: defaultProvider ?? this.defaultProvider,
|
|
|
|
|
|
gateway: gateway ?? this.gateway,
|
|
|
|
|
|
ollamaLocal: ollamaLocal ?? this.ollamaLocal,
|
|
|
|
|
|
ollamaCloud: ollamaCloud ?? this.ollamaCloud,
|
|
|
|
|
|
vault: vault ?? this.vault,
|
2026-03-13 15:21:23 +08:00
|
|
|
|
aiGateway: aiGateway ?? this.aiGateway,
|
2026-03-22 10:50:29 +08:00
|
|
|
|
webSessionPersistence:
|
|
|
|
|
|
webSessionPersistence ?? this.webSessionPersistence,
|
2026-03-19 12:33:50 +08:00
|
|
|
|
multiAgent: multiAgent ?? this.multiAgent,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
experimentalCanvas: experimentalCanvas ?? this.experimentalCanvas,
|
|
|
|
|
|
experimentalBridge: experimentalBridge ?? this.experimentalBridge,
|
|
|
|
|
|
experimentalDebug: experimentalDebug ?? this.experimentalDebug,
|
|
|
|
|
|
accountBaseUrl: accountBaseUrl ?? this.accountBaseUrl,
|
|
|
|
|
|
accountUsername: accountUsername ?? this.accountUsername,
|
|
|
|
|
|
accountWorkspace: accountWorkspace ?? this.accountWorkspace,
|
|
|
|
|
|
accountLocalMode: accountLocalMode ?? this.accountLocalMode,
|
2026-03-16 17:58:37 +08:00
|
|
|
|
linuxDesktop: linuxDesktop ?? this.linuxDesktop,
|
2026-03-11 14:04:29 +08:00
|
|
|
|
assistantExecutionTarget:
|
|
|
|
|
|
assistantExecutionTarget ?? this.assistantExecutionTarget,
|
|
|
|
|
|
assistantPermissionLevel:
|
|
|
|
|
|
assistantPermissionLevel ?? this.assistantPermissionLevel,
|
2026-03-15 10:14:33 +08:00
|
|
|
|
assistantNavigationDestinations:
|
|
|
|
|
|
assistantNavigationDestinations ??
|
|
|
|
|
|
this.assistantNavigationDestinations,
|
2026-03-19 17:59:20 +08:00
|
|
|
|
assistantCustomTaskTitles:
|
|
|
|
|
|
assistantCustomTaskTitles ?? this.assistantCustomTaskTitles,
|
2026-03-19 19:55:36 +08:00
|
|
|
|
assistantArchivedTaskKeys:
|
|
|
|
|
|
assistantArchivedTaskKeys ?? this.assistantArchivedTaskKeys,
|
2026-03-22 11:59:54 +08:00
|
|
|
|
assistantLastSessionKey:
|
|
|
|
|
|
assistantLastSessionKey ?? this.assistantLastSessionKey,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
2026-03-11 15:15:47 +08:00
|
|
|
|
'appLanguage': appLanguage.name,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
'appActive': appActive,
|
|
|
|
|
|
'launchAtLogin': launchAtLogin,
|
|
|
|
|
|
'showDockIcon': showDockIcon,
|
|
|
|
|
|
'workspacePath': workspacePath,
|
|
|
|
|
|
'remoteProjectRoot': remoteProjectRoot,
|
|
|
|
|
|
'cliPath': cliPath,
|
2026-03-15 08:19:53 +08:00
|
|
|
|
'codeAgentRuntimeMode': codeAgentRuntimeMode.name,
|
|
|
|
|
|
'codexCliPath': codexCliPath,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
'defaultModel': defaultModel,
|
|
|
|
|
|
'defaultProvider': defaultProvider,
|
|
|
|
|
|
'gateway': gateway.toJson(),
|
|
|
|
|
|
'ollamaLocal': ollamaLocal.toJson(),
|
|
|
|
|
|
'ollamaCloud': ollamaCloud.toJson(),
|
|
|
|
|
|
'vault': vault.toJson(),
|
2026-03-13 15:21:23 +08:00
|
|
|
|
'aiGateway': aiGateway.toJson(),
|
2026-03-22 10:50:29 +08:00
|
|
|
|
'webSessionPersistence': webSessionPersistence.toJson(),
|
2026-03-19 12:33:50 +08:00
|
|
|
|
'multiAgent': multiAgent.toJson(),
|
2026-03-11 07:32:52 +08:00
|
|
|
|
'experimentalCanvas': experimentalCanvas,
|
|
|
|
|
|
'experimentalBridge': experimentalBridge,
|
|
|
|
|
|
'experimentalDebug': experimentalDebug,
|
|
|
|
|
|
'accountBaseUrl': accountBaseUrl,
|
|
|
|
|
|
'accountUsername': accountUsername,
|
|
|
|
|
|
'accountWorkspace': accountWorkspace,
|
|
|
|
|
|
'accountLocalMode': accountLocalMode,
|
2026-03-16 17:58:37 +08:00
|
|
|
|
'linuxDesktop': linuxDesktop.toJson(),
|
2026-03-11 14:04:29 +08:00
|
|
|
|
'assistantExecutionTarget': assistantExecutionTarget.name,
|
|
|
|
|
|
'assistantPermissionLevel': assistantPermissionLevel.name,
|
2026-03-15 10:14:33 +08:00
|
|
|
|
'assistantNavigationDestinations': assistantNavigationDestinations
|
|
|
|
|
|
.map((item) => item.name)
|
|
|
|
|
|
.toList(growable: false),
|
2026-03-19 17:59:20 +08:00
|
|
|
|
'assistantCustomTaskTitles': assistantCustomTaskTitles,
|
2026-03-19 19:55:36 +08:00
|
|
|
|
'assistantArchivedTaskKeys': assistantArchivedTaskKeys,
|
2026-03-22 11:59:54 +08:00
|
|
|
|
'assistantLastSessionKey': assistantLastSessionKey,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory SettingsSnapshot.fromJson(Map<String, dynamic> json) {
|
2026-03-19 17:59:20 +08:00
|
|
|
|
Map<String, String> normalizeTaskTitles(Object? value) {
|
|
|
|
|
|
if (value is! Map) {
|
|
|
|
|
|
return const <String, String>{};
|
|
|
|
|
|
}
|
|
|
|
|
|
final normalized = <String, String>{};
|
|
|
|
|
|
value.forEach((key, title) {
|
|
|
|
|
|
final normalizedKey = key.toString().trim();
|
|
|
|
|
|
final normalizedTitle = title.toString().trim();
|
|
|
|
|
|
if (normalizedKey.isEmpty || normalizedTitle.isEmpty) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
normalized[normalizedKey] = normalizedTitle;
|
|
|
|
|
|
});
|
|
|
|
|
|
return normalized;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 19:55:36 +08:00
|
|
|
|
List<String> normalizeTaskKeys(Object? value) {
|
|
|
|
|
|
if (value is! List) {
|
|
|
|
|
|
return const <String>[];
|
|
|
|
|
|
}
|
|
|
|
|
|
final normalized = <String>[];
|
|
|
|
|
|
final seen = <String>{};
|
|
|
|
|
|
for (final item in value) {
|
|
|
|
|
|
final normalizedKey = item?.toString().trim() ?? '';
|
|
|
|
|
|
if (normalizedKey.isEmpty || !seen.add(normalizedKey)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
normalized.add(normalizedKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
return normalized;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-15 10:14:33 +08:00
|
|
|
|
final rawAssistantNavigationDestinations =
|
|
|
|
|
|
json['assistantNavigationDestinations'];
|
|
|
|
|
|
final assistantNavigationDestinations =
|
|
|
|
|
|
rawAssistantNavigationDestinations is List
|
|
|
|
|
|
? normalizeAssistantNavigationDestinations(
|
|
|
|
|
|
rawAssistantNavigationDestinations
|
|
|
|
|
|
.map(
|
|
|
|
|
|
(item) =>
|
|
|
|
|
|
WorkspaceDestinationCopy.fromJsonValue(item?.toString()),
|
|
|
|
|
|
)
|
|
|
|
|
|
.whereType<WorkspaceDestination>(),
|
|
|
|
|
|
)
|
|
|
|
|
|
: kAssistantNavigationDestinationDefaults;
|
2026-03-11 07:32:52 +08:00
|
|
|
|
return SettingsSnapshot(
|
2026-03-11 15:15:47 +08:00
|
|
|
|
appLanguage: AppLanguageCopy.fromJsonValue(
|
|
|
|
|
|
json['appLanguage'] as String?,
|
|
|
|
|
|
),
|
2026-03-11 07:32:52 +08:00
|
|
|
|
appActive: json['appActive'] as bool? ?? true,
|
|
|
|
|
|
launchAtLogin: json['launchAtLogin'] as bool? ?? false,
|
|
|
|
|
|
showDockIcon: json['showDockIcon'] as bool? ?? true,
|
|
|
|
|
|
workspacePath:
|
2026-03-11 14:04:29 +08:00
|
|
|
|
json['workspacePath'] as String? ??
|
|
|
|
|
|
SettingsSnapshot.defaults().workspacePath,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
remoteProjectRoot:
|
|
|
|
|
|
json['remoteProjectRoot'] as String? ??
|
|
|
|
|
|
SettingsSnapshot.defaults().remoteProjectRoot,
|
2026-03-11 14:04:29 +08:00
|
|
|
|
cliPath:
|
|
|
|
|
|
json['cliPath'] as String? ?? SettingsSnapshot.defaults().cliPath,
|
2026-03-15 08:19:53 +08:00
|
|
|
|
codeAgentRuntimeMode: CodeAgentRuntimeModeCopy.fromJsonValue(
|
|
|
|
|
|
json['codeAgentRuntimeMode'] as String?,
|
|
|
|
|
|
),
|
|
|
|
|
|
codexCliPath:
|
|
|
|
|
|
json['codexCliPath'] as String? ??
|
|
|
|
|
|
SettingsSnapshot.defaults().codexCliPath,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
defaultModel:
|
2026-03-11 14:04:29 +08:00
|
|
|
|
json['defaultModel'] as String? ??
|
|
|
|
|
|
SettingsSnapshot.defaults().defaultModel,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
defaultProvider:
|
|
|
|
|
|
json['defaultProvider'] as String? ??
|
|
|
|
|
|
SettingsSnapshot.defaults().defaultProvider,
|
|
|
|
|
|
gateway: GatewayConnectionProfile.fromJson(
|
|
|
|
|
|
(json['gateway'] as Map?)?.cast<String, dynamic>() ?? const {},
|
|
|
|
|
|
),
|
|
|
|
|
|
ollamaLocal: OllamaLocalConfig.fromJson(
|
|
|
|
|
|
(json['ollamaLocal'] as Map?)?.cast<String, dynamic>() ?? const {},
|
|
|
|
|
|
),
|
|
|
|
|
|
ollamaCloud: OllamaCloudConfig.fromJson(
|
|
|
|
|
|
(json['ollamaCloud'] as Map?)?.cast<String, dynamic>() ?? const {},
|
|
|
|
|
|
),
|
|
|
|
|
|
vault: VaultConfig.fromJson(
|
|
|
|
|
|
(json['vault'] as Map?)?.cast<String, dynamic>() ?? const {},
|
|
|
|
|
|
),
|
2026-03-13 15:21:23 +08:00
|
|
|
|
aiGateway: AiGatewayProfile.fromJson(
|
|
|
|
|
|
(json['aiGateway'] as Map?)?.cast<String, dynamic>() ??
|
|
|
|
|
|
(json['apisix'] as Map?)?.cast<String, dynamic>() ??
|
|
|
|
|
|
const {},
|
2026-03-11 07:32:52 +08:00
|
|
|
|
),
|
2026-03-22 10:50:29 +08:00
|
|
|
|
webSessionPersistence: WebSessionPersistenceConfig.fromJson(
|
|
|
|
|
|
(json['webSessionPersistence'] as Map?)?.cast<String, dynamic>() ??
|
|
|
|
|
|
const {},
|
|
|
|
|
|
),
|
2026-03-19 12:33:50 +08:00
|
|
|
|
multiAgent: MultiAgentConfig.fromJson(
|
|
|
|
|
|
(json['multiAgent'] as Map?)?.cast<String, dynamic>() ?? const {},
|
|
|
|
|
|
),
|
2026-03-11 07:32:52 +08:00
|
|
|
|
experimentalCanvas: json['experimentalCanvas'] as bool? ?? false,
|
|
|
|
|
|
experimentalBridge: json['experimentalBridge'] as bool? ?? false,
|
|
|
|
|
|
experimentalDebug: json['experimentalDebug'] as bool? ?? false,
|
|
|
|
|
|
accountBaseUrl:
|
|
|
|
|
|
json['accountBaseUrl'] as String? ??
|
|
|
|
|
|
SettingsSnapshot.defaults().accountBaseUrl,
|
|
|
|
|
|
accountUsername: json['accountUsername'] as String? ?? '',
|
|
|
|
|
|
accountWorkspace:
|
|
|
|
|
|
json['accountWorkspace'] as String? ??
|
|
|
|
|
|
SettingsSnapshot.defaults().accountWorkspace,
|
|
|
|
|
|
accountLocalMode: json['accountLocalMode'] as bool? ?? true,
|
2026-03-16 17:58:37 +08:00
|
|
|
|
linuxDesktop: LinuxDesktopConfig.fromJson(
|
|
|
|
|
|
(json['linuxDesktop'] as Map?)?.cast<String, dynamic>() ?? const {},
|
|
|
|
|
|
),
|
2026-03-11 14:04:29 +08:00
|
|
|
|
assistantExecutionTarget: AssistantExecutionTargetCopy.fromJsonValue(
|
|
|
|
|
|
json['assistantExecutionTarget'] as String?,
|
|
|
|
|
|
),
|
|
|
|
|
|
assistantPermissionLevel: AssistantPermissionLevelCopy.fromJsonValue(
|
|
|
|
|
|
json['assistantPermissionLevel'] as String?,
|
|
|
|
|
|
),
|
2026-03-15 10:14:33 +08:00
|
|
|
|
assistantNavigationDestinations: assistantNavigationDestinations,
|
2026-03-19 17:59:20 +08:00
|
|
|
|
assistantCustomTaskTitles: normalizeTaskTitles(
|
|
|
|
|
|
json['assistantCustomTaskTitles'],
|
|
|
|
|
|
),
|
2026-03-19 19:55:36 +08:00
|
|
|
|
assistantArchivedTaskKeys: normalizeTaskKeys(
|
|
|
|
|
|
json['assistantArchivedTaskKeys'],
|
|
|
|
|
|
),
|
2026-03-22 11:59:54 +08:00
|
|
|
|
assistantLastSessionKey: json['assistantLastSessionKey'] as String? ?? '',
|
2026-03-11 07:32:52 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static SettingsSnapshot fromJsonString(String? raw) {
|
|
|
|
|
|
if (raw == null || raw.trim().isEmpty) {
|
|
|
|
|
|
return SettingsSnapshot.defaults();
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
final decoded = jsonDecode(raw) as Map<String, dynamic>;
|
|
|
|
|
|
return SettingsSnapshot.fromJson(decoded);
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return SettingsSnapshot.defaults();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String toJsonString() => jsonEncode(toJson());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewayConnectionSnapshot {
|
|
|
|
|
|
const GatewayConnectionSnapshot({
|
|
|
|
|
|
required this.status,
|
|
|
|
|
|
required this.mode,
|
|
|
|
|
|
required this.statusText,
|
|
|
|
|
|
required this.serverName,
|
|
|
|
|
|
required this.remoteAddress,
|
|
|
|
|
|
required this.mainSessionKey,
|
|
|
|
|
|
required this.lastError,
|
2026-03-12 00:54:28 +08:00
|
|
|
|
required this.lastErrorCode,
|
|
|
|
|
|
required this.lastErrorDetailCode,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
required this.lastConnectedAtMs,
|
2026-03-12 00:54:28 +08:00
|
|
|
|
required this.deviceId,
|
|
|
|
|
|
required this.authRole,
|
|
|
|
|
|
required this.authScopes,
|
2026-03-12 01:15:50 +08:00
|
|
|
|
required this.connectAuthMode,
|
|
|
|
|
|
required this.connectAuthFields,
|
|
|
|
|
|
required this.connectAuthSources,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
required this.hasSharedAuth,
|
|
|
|
|
|
required this.hasDeviceToken,
|
|
|
|
|
|
required this.healthPayload,
|
|
|
|
|
|
required this.statusPayload,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final RuntimeConnectionStatus status;
|
|
|
|
|
|
final RuntimeConnectionMode mode;
|
|
|
|
|
|
final String statusText;
|
|
|
|
|
|
final String? serverName;
|
|
|
|
|
|
final String? remoteAddress;
|
|
|
|
|
|
final String? mainSessionKey;
|
|
|
|
|
|
final String? lastError;
|
2026-03-12 00:54:28 +08:00
|
|
|
|
final String? lastErrorCode;
|
|
|
|
|
|
final String? lastErrorDetailCode;
|
2026-03-11 07:32:52 +08:00
|
|
|
|
final int? lastConnectedAtMs;
|
2026-03-12 00:54:28 +08:00
|
|
|
|
final String? deviceId;
|
|
|
|
|
|
final String? authRole;
|
|
|
|
|
|
final List<String> authScopes;
|
2026-03-12 01:15:50 +08:00
|
|
|
|
final String? connectAuthMode;
|
|
|
|
|
|
final List<String> connectAuthFields;
|
|
|
|
|
|
final List<String> connectAuthSources;
|
2026-03-11 07:32:52 +08:00
|
|
|
|
final bool hasSharedAuth;
|
|
|
|
|
|
final bool hasDeviceToken;
|
|
|
|
|
|
final Map<String, dynamic>? healthPayload;
|
|
|
|
|
|
final Map<String, dynamic>? statusPayload;
|
|
|
|
|
|
|
|
|
|
|
|
factory GatewayConnectionSnapshot.initial({
|
|
|
|
|
|
RuntimeConnectionMode mode = RuntimeConnectionMode.unconfigured,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return GatewayConnectionSnapshot(
|
|
|
|
|
|
status: RuntimeConnectionStatus.offline,
|
|
|
|
|
|
mode: mode,
|
|
|
|
|
|
statusText: 'Offline',
|
|
|
|
|
|
serverName: null,
|
|
|
|
|
|
remoteAddress: null,
|
|
|
|
|
|
mainSessionKey: null,
|
|
|
|
|
|
lastError: null,
|
2026-03-12 00:54:28 +08:00
|
|
|
|
lastErrorCode: null,
|
|
|
|
|
|
lastErrorDetailCode: null,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
lastConnectedAtMs: null,
|
2026-03-12 00:54:28 +08:00
|
|
|
|
deviceId: null,
|
|
|
|
|
|
authRole: null,
|
|
|
|
|
|
authScopes: const <String>[],
|
2026-03-12 01:15:50 +08:00
|
|
|
|
connectAuthMode: null,
|
|
|
|
|
|
connectAuthFields: const <String>[],
|
|
|
|
|
|
connectAuthSources: const <String>[],
|
2026-03-11 07:32:52 +08:00
|
|
|
|
hasSharedAuth: false,
|
|
|
|
|
|
hasDeviceToken: false,
|
|
|
|
|
|
healthPayload: null,
|
|
|
|
|
|
statusPayload: null,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GatewayConnectionSnapshot copyWith({
|
|
|
|
|
|
RuntimeConnectionStatus? status,
|
|
|
|
|
|
RuntimeConnectionMode? mode,
|
|
|
|
|
|
String? statusText,
|
|
|
|
|
|
String? serverName,
|
|
|
|
|
|
String? remoteAddress,
|
|
|
|
|
|
String? mainSessionKey,
|
|
|
|
|
|
String? lastError,
|
2026-03-12 00:54:28 +08:00
|
|
|
|
String? lastErrorCode,
|
|
|
|
|
|
String? lastErrorDetailCode,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
int? lastConnectedAtMs,
|
2026-03-12 00:54:28 +08:00
|
|
|
|
String? deviceId,
|
|
|
|
|
|
String? authRole,
|
|
|
|
|
|
List<String>? authScopes,
|
2026-03-12 01:15:50 +08:00
|
|
|
|
String? connectAuthMode,
|
|
|
|
|
|
List<String>? connectAuthFields,
|
|
|
|
|
|
List<String>? connectAuthSources,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
bool? hasSharedAuth,
|
|
|
|
|
|
bool? hasDeviceToken,
|
|
|
|
|
|
Map<String, dynamic>? healthPayload,
|
|
|
|
|
|
Map<String, dynamic>? statusPayload,
|
|
|
|
|
|
bool clearServerName = false,
|
|
|
|
|
|
bool clearRemoteAddress = false,
|
|
|
|
|
|
bool clearMainSessionKey = false,
|
|
|
|
|
|
bool clearLastError = false,
|
2026-03-12 00:54:28 +08:00
|
|
|
|
bool clearLastErrorCode = false,
|
|
|
|
|
|
bool clearLastErrorDetailCode = false,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
}) {
|
|
|
|
|
|
return GatewayConnectionSnapshot(
|
|
|
|
|
|
status: status ?? this.status,
|
|
|
|
|
|
mode: mode ?? this.mode,
|
|
|
|
|
|
statusText: statusText ?? this.statusText,
|
|
|
|
|
|
serverName: clearServerName ? null : (serverName ?? this.serverName),
|
|
|
|
|
|
remoteAddress: clearRemoteAddress
|
|
|
|
|
|
? null
|
|
|
|
|
|
: (remoteAddress ?? this.remoteAddress),
|
|
|
|
|
|
mainSessionKey: clearMainSessionKey
|
|
|
|
|
|
? null
|
|
|
|
|
|
: (mainSessionKey ?? this.mainSessionKey),
|
|
|
|
|
|
lastError: clearLastError ? null : (lastError ?? this.lastError),
|
2026-03-12 00:54:28 +08:00
|
|
|
|
lastErrorCode: clearLastErrorCode
|
|
|
|
|
|
? null
|
|
|
|
|
|
: (lastErrorCode ?? this.lastErrorCode),
|
|
|
|
|
|
lastErrorDetailCode: clearLastErrorDetailCode
|
|
|
|
|
|
? null
|
|
|
|
|
|
: (lastErrorDetailCode ?? this.lastErrorDetailCode),
|
2026-03-11 07:32:52 +08:00
|
|
|
|
lastConnectedAtMs: lastConnectedAtMs ?? this.lastConnectedAtMs,
|
2026-03-12 00:54:28 +08:00
|
|
|
|
deviceId: deviceId ?? this.deviceId,
|
|
|
|
|
|
authRole: authRole ?? this.authRole,
|
|
|
|
|
|
authScopes: authScopes ?? this.authScopes,
|
2026-03-12 01:15:50 +08:00
|
|
|
|
connectAuthMode: connectAuthMode ?? this.connectAuthMode,
|
|
|
|
|
|
connectAuthFields: connectAuthFields ?? this.connectAuthFields,
|
|
|
|
|
|
connectAuthSources: connectAuthSources ?? this.connectAuthSources,
|
2026-03-11 07:32:52 +08:00
|
|
|
|
hasSharedAuth: hasSharedAuth ?? this.hasSharedAuth,
|
|
|
|
|
|
hasDeviceToken: hasDeviceToken ?? this.hasDeviceToken,
|
|
|
|
|
|
healthPayload: healthPayload ?? this.healthPayload,
|
|
|
|
|
|
statusPayload: statusPayload ?? this.statusPayload,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-03-12 00:54:28 +08:00
|
|
|
|
|
|
|
|
|
|
bool get pairingRequired {
|
|
|
|
|
|
final detailCode = lastErrorDetailCode?.trim().toUpperCase();
|
|
|
|
|
|
final errorCode = lastErrorCode?.trim().toUpperCase();
|
|
|
|
|
|
final errorText = lastError?.toLowerCase() ?? '';
|
|
|
|
|
|
return status != RuntimeConnectionStatus.connected &&
|
|
|
|
|
|
(detailCode == 'PAIRING_REQUIRED' ||
|
|
|
|
|
|
errorCode == 'NOT_PAIRED' ||
|
|
|
|
|
|
errorText.contains('pairing required'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool get gatewayTokenMissing {
|
|
|
|
|
|
final detailCode = lastErrorDetailCode?.trim().toUpperCase();
|
|
|
|
|
|
final errorText = lastError?.toLowerCase() ?? '';
|
|
|
|
|
|
return detailCode == 'AUTH_TOKEN_MISSING' ||
|
|
|
|
|
|
errorText.contains('gateway token missing');
|
|
|
|
|
|
}
|
2026-03-12 01:15:50 +08:00
|
|
|
|
|
|
|
|
|
|
String get connectAuthSummary {
|
|
|
|
|
|
final mode = connectAuthMode?.trim() ?? 'none';
|
|
|
|
|
|
final fields = connectAuthFields.isEmpty
|
|
|
|
|
|
? 'none'
|
|
|
|
|
|
: connectAuthFields.join(', ');
|
|
|
|
|
|
final sources = connectAuthSources.isEmpty
|
|
|
|
|
|
? 'none'
|
|
|
|
|
|
: connectAuthSources.join(' · ');
|
|
|
|
|
|
return '$mode | fields: $fields | sources: $sources';
|
|
|
|
|
|
}
|
2026-03-11 07:32:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class RuntimePackageInfo {
|
|
|
|
|
|
const RuntimePackageInfo({
|
|
|
|
|
|
required this.appName,
|
|
|
|
|
|
required this.packageName,
|
|
|
|
|
|
required this.version,
|
|
|
|
|
|
required this.buildNumber,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String appName;
|
|
|
|
|
|
final String packageName;
|
|
|
|
|
|
final String version;
|
|
|
|
|
|
final String buildNumber;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class RuntimeDeviceInfo {
|
|
|
|
|
|
const RuntimeDeviceInfo({
|
|
|
|
|
|
required this.platform,
|
|
|
|
|
|
required this.platformVersion,
|
|
|
|
|
|
required this.deviceFamily,
|
|
|
|
|
|
required this.modelIdentifier,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String platform;
|
|
|
|
|
|
final String platformVersion;
|
|
|
|
|
|
final String deviceFamily;
|
|
|
|
|
|
final String modelIdentifier;
|
|
|
|
|
|
|
|
|
|
|
|
String get platformLabel {
|
|
|
|
|
|
final version = platformVersion.trim();
|
|
|
|
|
|
if (version.isEmpty) {
|
|
|
|
|
|
return platform;
|
|
|
|
|
|
}
|
|
|
|
|
|
return '$platform $version';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 01:15:50 +08:00
|
|
|
|
class RuntimeLogEntry {
|
|
|
|
|
|
const RuntimeLogEntry({
|
|
|
|
|
|
required this.timestampMs,
|
|
|
|
|
|
required this.level,
|
|
|
|
|
|
required this.category,
|
|
|
|
|
|
required this.message,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final int timestampMs;
|
|
|
|
|
|
final String level;
|
|
|
|
|
|
final String category;
|
|
|
|
|
|
final String message;
|
|
|
|
|
|
|
|
|
|
|
|
String get timeLabel {
|
|
|
|
|
|
final date = DateTime.fromMillisecondsSinceEpoch(timestampMs);
|
|
|
|
|
|
return '${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}:${date.second.toString().padLeft(2, '0')}';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String get line => '[$timeLabel] ${level.toUpperCase()} $category $message';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 07:32:52 +08:00
|
|
|
|
class GatewayAgentSummary {
|
|
|
|
|
|
const GatewayAgentSummary({
|
|
|
|
|
|
required this.id,
|
|
|
|
|
|
required this.name,
|
|
|
|
|
|
required this.emoji,
|
|
|
|
|
|
required this.theme,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
final String name;
|
|
|
|
|
|
final String emoji;
|
|
|
|
|
|
final String theme;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewaySessionSummary {
|
|
|
|
|
|
const GatewaySessionSummary({
|
|
|
|
|
|
required this.key,
|
|
|
|
|
|
required this.kind,
|
|
|
|
|
|
required this.displayName,
|
|
|
|
|
|
required this.surface,
|
|
|
|
|
|
required this.subject,
|
|
|
|
|
|
required this.room,
|
|
|
|
|
|
required this.space,
|
|
|
|
|
|
required this.updatedAtMs,
|
|
|
|
|
|
required this.sessionId,
|
|
|
|
|
|
required this.systemSent,
|
|
|
|
|
|
required this.abortedLastRun,
|
|
|
|
|
|
required this.thinkingLevel,
|
|
|
|
|
|
required this.verboseLevel,
|
|
|
|
|
|
required this.inputTokens,
|
|
|
|
|
|
required this.outputTokens,
|
|
|
|
|
|
required this.totalTokens,
|
|
|
|
|
|
required this.model,
|
|
|
|
|
|
required this.contextTokens,
|
|
|
|
|
|
required this.derivedTitle,
|
|
|
|
|
|
required this.lastMessagePreview,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String key;
|
|
|
|
|
|
final String? kind;
|
|
|
|
|
|
final String? displayName;
|
|
|
|
|
|
final String? surface;
|
|
|
|
|
|
final String? subject;
|
|
|
|
|
|
final String? room;
|
|
|
|
|
|
final String? space;
|
|
|
|
|
|
final double? updatedAtMs;
|
|
|
|
|
|
final String? sessionId;
|
|
|
|
|
|
final bool? systemSent;
|
|
|
|
|
|
final bool? abortedLastRun;
|
|
|
|
|
|
final String? thinkingLevel;
|
|
|
|
|
|
final String? verboseLevel;
|
|
|
|
|
|
final int? inputTokens;
|
|
|
|
|
|
final int? outputTokens;
|
|
|
|
|
|
final int? totalTokens;
|
|
|
|
|
|
final String? model;
|
|
|
|
|
|
final int? contextTokens;
|
|
|
|
|
|
final String? derivedTitle;
|
|
|
|
|
|
final String? lastMessagePreview;
|
|
|
|
|
|
|
|
|
|
|
|
String get label {
|
2026-03-11 14:04:29 +08:00
|
|
|
|
final candidates = [derivedTitle, displayName, subject, room, space, key];
|
2026-03-11 07:32:52 +08:00
|
|
|
|
return candidates.firstWhere(
|
|
|
|
|
|
(item) => item != null && item.trim().isNotEmpty,
|
|
|
|
|
|
orElse: () => key,
|
|
|
|
|
|
)!;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewayChatMessage {
|
|
|
|
|
|
const GatewayChatMessage({
|
|
|
|
|
|
required this.id,
|
|
|
|
|
|
required this.role,
|
|
|
|
|
|
required this.text,
|
|
|
|
|
|
required this.timestampMs,
|
|
|
|
|
|
required this.toolCallId,
|
|
|
|
|
|
required this.toolName,
|
|
|
|
|
|
required this.stopReason,
|
|
|
|
|
|
required this.pending,
|
|
|
|
|
|
required this.error,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
final String role;
|
|
|
|
|
|
final String text;
|
|
|
|
|
|
final double? timestampMs;
|
|
|
|
|
|
final String? toolCallId;
|
|
|
|
|
|
final String? toolName;
|
|
|
|
|
|
final String? stopReason;
|
|
|
|
|
|
final bool pending;
|
|
|
|
|
|
final bool error;
|
|
|
|
|
|
|
2026-03-19 19:55:36 +08:00
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': id,
|
|
|
|
|
|
'role': role,
|
|
|
|
|
|
'text': text,
|
|
|
|
|
|
'timestampMs': timestampMs,
|
|
|
|
|
|
'toolCallId': toolCallId,
|
|
|
|
|
|
'toolName': toolName,
|
|
|
|
|
|
'stopReason': stopReason,
|
|
|
|
|
|
'pending': pending,
|
|
|
|
|
|
'error': error,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory GatewayChatMessage.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
double? asDouble(Object? value) {
|
|
|
|
|
|
if (value is num) {
|
|
|
|
|
|
return value.toDouble();
|
|
|
|
|
|
}
|
|
|
|
|
|
return double.tryParse(value?.toString() ?? '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return GatewayChatMessage(
|
|
|
|
|
|
id: json['id']?.toString() ?? '',
|
|
|
|
|
|
role: json['role']?.toString() ?? 'assistant',
|
|
|
|
|
|
text: json['text']?.toString() ?? '',
|
|
|
|
|
|
timestampMs: asDouble(json['timestampMs']),
|
|
|
|
|
|
toolCallId: json['toolCallId']?.toString(),
|
|
|
|
|
|
toolName: json['toolName']?.toString(),
|
|
|
|
|
|
stopReason: json['stopReason']?.toString(),
|
|
|
|
|
|
pending: json['pending'] as bool? ?? false,
|
|
|
|
|
|
error: json['error'] as bool? ?? false,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 07:32:52 +08:00
|
|
|
|
GatewayChatMessage copyWith({
|
|
|
|
|
|
String? id,
|
|
|
|
|
|
String? role,
|
|
|
|
|
|
String? text,
|
|
|
|
|
|
double? timestampMs,
|
|
|
|
|
|
String? toolCallId,
|
|
|
|
|
|
String? toolName,
|
|
|
|
|
|
String? stopReason,
|
|
|
|
|
|
bool? pending,
|
|
|
|
|
|
bool? error,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return GatewayChatMessage(
|
|
|
|
|
|
id: id ?? this.id,
|
|
|
|
|
|
role: role ?? this.role,
|
|
|
|
|
|
text: text ?? this.text,
|
|
|
|
|
|
timestampMs: timestampMs ?? this.timestampMs,
|
|
|
|
|
|
toolCallId: toolCallId ?? this.toolCallId,
|
|
|
|
|
|
toolName: toolName ?? this.toolName,
|
|
|
|
|
|
stopReason: stopReason ?? this.stopReason,
|
|
|
|
|
|
pending: pending ?? this.pending,
|
|
|
|
|
|
error: error ?? this.error,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-22 11:59:54 +08:00
|
|
|
|
class AssistantThreadSkillEntry {
|
|
|
|
|
|
const AssistantThreadSkillEntry({
|
|
|
|
|
|
required this.key,
|
|
|
|
|
|
required this.label,
|
|
|
|
|
|
required this.description,
|
|
|
|
|
|
required this.sourcePath,
|
|
|
|
|
|
required this.sourceLabel,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String key;
|
|
|
|
|
|
final String label;
|
|
|
|
|
|
final String description;
|
|
|
|
|
|
final String sourcePath;
|
|
|
|
|
|
final String sourceLabel;
|
|
|
|
|
|
|
|
|
|
|
|
AssistantThreadSkillEntry copyWith({
|
|
|
|
|
|
String? key,
|
|
|
|
|
|
String? label,
|
|
|
|
|
|
String? description,
|
|
|
|
|
|
String? sourcePath,
|
|
|
|
|
|
String? sourceLabel,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return AssistantThreadSkillEntry(
|
|
|
|
|
|
key: key ?? this.key,
|
|
|
|
|
|
label: label ?? this.label,
|
|
|
|
|
|
description: description ?? this.description,
|
|
|
|
|
|
sourcePath: sourcePath ?? this.sourcePath,
|
|
|
|
|
|
sourceLabel: sourceLabel ?? this.sourceLabel,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'key': key,
|
|
|
|
|
|
'label': label,
|
|
|
|
|
|
'description': description,
|
|
|
|
|
|
'sourcePath': sourcePath,
|
|
|
|
|
|
'sourceLabel': sourceLabel,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory AssistantThreadSkillEntry.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return AssistantThreadSkillEntry(
|
|
|
|
|
|
key: json['key']?.toString() ?? '',
|
|
|
|
|
|
label: json['label']?.toString() ?? '',
|
|
|
|
|
|
description: json['description']?.toString() ?? '',
|
|
|
|
|
|
sourcePath: json['sourcePath']?.toString() ?? '',
|
|
|
|
|
|
sourceLabel: json['sourceLabel']?.toString() ?? '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 19:55:36 +08:00
|
|
|
|
class AssistantThreadRecord {
|
|
|
|
|
|
const AssistantThreadRecord({
|
|
|
|
|
|
required this.sessionKey,
|
|
|
|
|
|
required this.messages,
|
|
|
|
|
|
required this.updatedAtMs,
|
|
|
|
|
|
required this.title,
|
|
|
|
|
|
required this.archived,
|
2026-03-20 12:37:19 +08:00
|
|
|
|
required this.executionTarget,
|
|
|
|
|
|
required this.messageViewMode,
|
2026-03-22 11:59:54 +08:00
|
|
|
|
this.discoveredSkills = const <AssistantThreadSkillEntry>[],
|
|
|
|
|
|
this.importedSkills = const <AssistantThreadSkillEntry>[],
|
|
|
|
|
|
this.selectedSkillKeys = const <String>[],
|
|
|
|
|
|
this.assistantModelId = '',
|
|
|
|
|
|
this.gatewayEntryState,
|
2026-03-19 19:55:36 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String sessionKey;
|
|
|
|
|
|
final List<GatewayChatMessage> messages;
|
|
|
|
|
|
final double? updatedAtMs;
|
|
|
|
|
|
final String title;
|
|
|
|
|
|
final bool archived;
|
2026-03-20 12:37:19 +08:00
|
|
|
|
final AssistantExecutionTarget? executionTarget;
|
|
|
|
|
|
final AssistantMessageViewMode messageViewMode;
|
2026-03-22 11:59:54 +08:00
|
|
|
|
final List<AssistantThreadSkillEntry> discoveredSkills;
|
|
|
|
|
|
final List<AssistantThreadSkillEntry> importedSkills;
|
|
|
|
|
|
final List<String> selectedSkillKeys;
|
|
|
|
|
|
final String assistantModelId;
|
|
|
|
|
|
final String? gatewayEntryState;
|
2026-03-19 19:55:36 +08:00
|
|
|
|
|
|
|
|
|
|
AssistantThreadRecord copyWith({
|
|
|
|
|
|
String? sessionKey,
|
|
|
|
|
|
List<GatewayChatMessage>? messages,
|
|
|
|
|
|
double? updatedAtMs,
|
|
|
|
|
|
String? title,
|
|
|
|
|
|
bool? archived,
|
2026-03-20 12:37:19 +08:00
|
|
|
|
AssistantExecutionTarget? executionTarget,
|
|
|
|
|
|
bool clearExecutionTarget = false,
|
|
|
|
|
|
AssistantMessageViewMode? messageViewMode,
|
2026-03-22 11:59:54 +08:00
|
|
|
|
List<AssistantThreadSkillEntry>? discoveredSkills,
|
|
|
|
|
|
List<AssistantThreadSkillEntry>? importedSkills,
|
|
|
|
|
|
List<String>? selectedSkillKeys,
|
|
|
|
|
|
String? assistantModelId,
|
|
|
|
|
|
String? gatewayEntryState,
|
|
|
|
|
|
bool clearGatewayEntryState = false,
|
2026-03-19 19:55:36 +08:00
|
|
|
|
}) {
|
|
|
|
|
|
return AssistantThreadRecord(
|
|
|
|
|
|
sessionKey: sessionKey ?? this.sessionKey,
|
|
|
|
|
|
messages: messages ?? this.messages,
|
|
|
|
|
|
updatedAtMs: updatedAtMs ?? this.updatedAtMs,
|
|
|
|
|
|
title: title ?? this.title,
|
|
|
|
|
|
archived: archived ?? this.archived,
|
2026-03-20 12:37:19 +08:00
|
|
|
|
executionTarget: clearExecutionTarget
|
|
|
|
|
|
? null
|
|
|
|
|
|
: (executionTarget ?? this.executionTarget),
|
|
|
|
|
|
messageViewMode: messageViewMode ?? this.messageViewMode,
|
2026-03-22 11:59:54 +08:00
|
|
|
|
discoveredSkills: discoveredSkills ?? this.discoveredSkills,
|
|
|
|
|
|
importedSkills: importedSkills ?? this.importedSkills,
|
|
|
|
|
|
selectedSkillKeys: selectedSkillKeys ?? this.selectedSkillKeys,
|
|
|
|
|
|
assistantModelId: assistantModelId ?? this.assistantModelId,
|
|
|
|
|
|
gatewayEntryState: clearGatewayEntryState
|
|
|
|
|
|
? null
|
|
|
|
|
|
: (gatewayEntryState ?? this.gatewayEntryState),
|
2026-03-19 19:55:36 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'sessionKey': sessionKey,
|
|
|
|
|
|
'messages': messages.map((item) => item.toJson()).toList(growable: false),
|
|
|
|
|
|
'updatedAtMs': updatedAtMs,
|
|
|
|
|
|
'title': title,
|
|
|
|
|
|
'archived': archived,
|
2026-03-20 12:37:19 +08:00
|
|
|
|
'executionTarget': executionTarget?.name,
|
|
|
|
|
|
'messageViewMode': messageViewMode.name,
|
2026-03-22 11:59:54 +08:00
|
|
|
|
'discoveredSkills': discoveredSkills
|
|
|
|
|
|
.map((item) => item.toJson())
|
|
|
|
|
|
.toList(growable: false),
|
|
|
|
|
|
'importedSkills': importedSkills
|
|
|
|
|
|
.map((item) => item.toJson())
|
|
|
|
|
|
.toList(growable: false),
|
|
|
|
|
|
'selectedSkillKeys': selectedSkillKeys,
|
|
|
|
|
|
'assistantModelId': assistantModelId,
|
|
|
|
|
|
'gatewayEntryState': gatewayEntryState,
|
2026-03-19 19:55:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory AssistantThreadRecord.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
double? asDouble(Object? value) {
|
|
|
|
|
|
if (value is num) {
|
|
|
|
|
|
return value.toDouble();
|
|
|
|
|
|
}
|
|
|
|
|
|
return double.tryParse(value?.toString() ?? '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final rawMessages = json['messages'];
|
|
|
|
|
|
final messages = rawMessages is List
|
|
|
|
|
|
? rawMessages
|
|
|
|
|
|
.whereType<Map>()
|
|
|
|
|
|
.map(
|
|
|
|
|
|
(item) =>
|
|
|
|
|
|
GatewayChatMessage.fromJson(item.cast<String, dynamic>()),
|
|
|
|
|
|
)
|
|
|
|
|
|
.toList(growable: false)
|
|
|
|
|
|
: const <GatewayChatMessage>[];
|
2026-03-22 11:59:54 +08:00
|
|
|
|
List<AssistantThreadSkillEntry> normalizeSkillEntries(Object? value) {
|
|
|
|
|
|
if (value is! List) {
|
|
|
|
|
|
return const <AssistantThreadSkillEntry>[];
|
|
|
|
|
|
}
|
|
|
|
|
|
final entries = <AssistantThreadSkillEntry>[];
|
|
|
|
|
|
final seen = <String>{};
|
|
|
|
|
|
for (final item in value.whereType<Map>()) {
|
|
|
|
|
|
final entry = AssistantThreadSkillEntry.fromJson(
|
|
|
|
|
|
item.cast<String, dynamic>(),
|
|
|
|
|
|
);
|
|
|
|
|
|
final normalizedKey = entry.key.trim();
|
|
|
|
|
|
if (normalizedKey.isEmpty || !seen.add(normalizedKey)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
entries.add(entry);
|
|
|
|
|
|
}
|
|
|
|
|
|
return entries;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
List<String> normalizeSkillKeys(Object? value) {
|
|
|
|
|
|
if (value is! List) {
|
|
|
|
|
|
return const <String>[];
|
|
|
|
|
|
}
|
|
|
|
|
|
final keys = <String>[];
|
|
|
|
|
|
final seen = <String>{};
|
|
|
|
|
|
for (final item in value) {
|
|
|
|
|
|
final normalized = item?.toString().trim() ?? '';
|
|
|
|
|
|
if (normalized.isEmpty || !seen.add(normalized)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
keys.add(normalized);
|
|
|
|
|
|
}
|
|
|
|
|
|
return keys;
|
|
|
|
|
|
}
|
2026-03-19 19:55:36 +08:00
|
|
|
|
|
|
|
|
|
|
return AssistantThreadRecord(
|
|
|
|
|
|
sessionKey: json['sessionKey']?.toString() ?? '',
|
|
|
|
|
|
messages: messages,
|
|
|
|
|
|
updatedAtMs: asDouble(json['updatedAtMs']),
|
|
|
|
|
|
title: json['title']?.toString() ?? '',
|
|
|
|
|
|
archived: json['archived'] as bool? ?? false,
|
2026-03-20 12:37:19 +08:00
|
|
|
|
executionTarget: json['executionTarget'] == null
|
|
|
|
|
|
? null
|
|
|
|
|
|
: AssistantExecutionTargetCopy.fromJsonValue(
|
|
|
|
|
|
json['executionTarget']?.toString(),
|
|
|
|
|
|
),
|
|
|
|
|
|
messageViewMode: AssistantMessageViewModeCopy.fromJsonValue(
|
|
|
|
|
|
json['messageViewMode']?.toString(),
|
|
|
|
|
|
),
|
2026-03-22 11:59:54 +08:00
|
|
|
|
discoveredSkills: normalizeSkillEntries(json['discoveredSkills']),
|
|
|
|
|
|
importedSkills: normalizeSkillEntries(json['importedSkills']),
|
|
|
|
|
|
selectedSkillKeys: normalizeSkillKeys(json['selectedSkillKeys']),
|
|
|
|
|
|
assistantModelId: json['assistantModelId']?.toString() ?? '',
|
|
|
|
|
|
gatewayEntryState: json['gatewayEntryState']?.toString(),
|
2026-03-19 19:55:36 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 23:01:47 +08:00
|
|
|
|
class GatewayChatAttachmentPayload {
|
|
|
|
|
|
const GatewayChatAttachmentPayload({
|
|
|
|
|
|
required this.type,
|
|
|
|
|
|
required this.mimeType,
|
|
|
|
|
|
required this.fileName,
|
|
|
|
|
|
required this.content,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String type;
|
|
|
|
|
|
final String mimeType;
|
|
|
|
|
|
final String fileName;
|
|
|
|
|
|
final String content;
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'type': type,
|
|
|
|
|
|
'mimeType': mimeType,
|
|
|
|
|
|
'fileName': fileName,
|
|
|
|
|
|
'content': content,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 07:32:52 +08:00
|
|
|
|
class GatewayInstanceSummary {
|
|
|
|
|
|
const GatewayInstanceSummary({
|
|
|
|
|
|
required this.id,
|
|
|
|
|
|
required this.host,
|
|
|
|
|
|
required this.ip,
|
|
|
|
|
|
required this.version,
|
|
|
|
|
|
required this.platform,
|
|
|
|
|
|
required this.deviceFamily,
|
|
|
|
|
|
required this.modelIdentifier,
|
|
|
|
|
|
required this.lastInputSeconds,
|
|
|
|
|
|
required this.mode,
|
|
|
|
|
|
required this.reason,
|
|
|
|
|
|
required this.text,
|
|
|
|
|
|
required this.timestampMs,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
final String? host;
|
|
|
|
|
|
final String? ip;
|
|
|
|
|
|
final String? version;
|
|
|
|
|
|
final String? platform;
|
|
|
|
|
|
final String? deviceFamily;
|
|
|
|
|
|
final String? modelIdentifier;
|
|
|
|
|
|
final int? lastInputSeconds;
|
|
|
|
|
|
final String? mode;
|
|
|
|
|
|
final String? reason;
|
|
|
|
|
|
final String text;
|
|
|
|
|
|
final double timestampMs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewaySkillSummary {
|
|
|
|
|
|
const GatewaySkillSummary({
|
|
|
|
|
|
required this.name,
|
|
|
|
|
|
required this.description,
|
|
|
|
|
|
required this.source,
|
|
|
|
|
|
required this.skillKey,
|
|
|
|
|
|
required this.primaryEnv,
|
|
|
|
|
|
required this.eligible,
|
|
|
|
|
|
required this.disabled,
|
|
|
|
|
|
required this.missingBins,
|
|
|
|
|
|
required this.missingEnv,
|
|
|
|
|
|
required this.missingConfig,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String name;
|
|
|
|
|
|
final String description;
|
|
|
|
|
|
final String source;
|
|
|
|
|
|
final String skillKey;
|
|
|
|
|
|
final String? primaryEnv;
|
|
|
|
|
|
final bool eligible;
|
|
|
|
|
|
final bool disabled;
|
|
|
|
|
|
final List<String> missingBins;
|
|
|
|
|
|
final List<String> missingEnv;
|
|
|
|
|
|
final List<String> missingConfig;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 23:01:47 +08:00
|
|
|
|
class GatewayConnectorSummary {
|
|
|
|
|
|
const GatewayConnectorSummary({
|
|
|
|
|
|
required this.id,
|
|
|
|
|
|
required this.label,
|
|
|
|
|
|
required this.detailLabel,
|
|
|
|
|
|
required this.accountName,
|
|
|
|
|
|
required this.configured,
|
|
|
|
|
|
required this.enabled,
|
|
|
|
|
|
required this.running,
|
|
|
|
|
|
required this.connected,
|
|
|
|
|
|
required this.status,
|
|
|
|
|
|
required this.lastError,
|
|
|
|
|
|
required this.meta,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
final String label;
|
|
|
|
|
|
final String detailLabel;
|
|
|
|
|
|
final String? accountName;
|
|
|
|
|
|
final bool configured;
|
|
|
|
|
|
final bool enabled;
|
|
|
|
|
|
final bool running;
|
|
|
|
|
|
final bool connected;
|
|
|
|
|
|
final String status;
|
|
|
|
|
|
final String? lastError;
|
|
|
|
|
|
final List<String> meta;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewayModelSummary {
|
|
|
|
|
|
const GatewayModelSummary({
|
|
|
|
|
|
required this.id,
|
|
|
|
|
|
required this.name,
|
|
|
|
|
|
required this.provider,
|
|
|
|
|
|
required this.contextWindow,
|
|
|
|
|
|
required this.maxOutputTokens,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
final String name;
|
|
|
|
|
|
final String provider;
|
|
|
|
|
|
final int? contextWindow;
|
|
|
|
|
|
final int? maxOutputTokens;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewayCronJobSummary {
|
|
|
|
|
|
const GatewayCronJobSummary({
|
|
|
|
|
|
required this.id,
|
|
|
|
|
|
required this.name,
|
|
|
|
|
|
required this.description,
|
|
|
|
|
|
required this.enabled,
|
|
|
|
|
|
required this.agentId,
|
|
|
|
|
|
required this.scheduleLabel,
|
|
|
|
|
|
required this.nextRunAtMs,
|
|
|
|
|
|
required this.lastRunAtMs,
|
|
|
|
|
|
required this.lastStatus,
|
|
|
|
|
|
required this.lastError,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
final String name;
|
|
|
|
|
|
final String? description;
|
|
|
|
|
|
final bool enabled;
|
|
|
|
|
|
final String? agentId;
|
|
|
|
|
|
final String scheduleLabel;
|
|
|
|
|
|
final int? nextRunAtMs;
|
|
|
|
|
|
final int? lastRunAtMs;
|
|
|
|
|
|
final String? lastStatus;
|
|
|
|
|
|
final String? lastError;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 00:54:28 +08:00
|
|
|
|
class GatewayDevicePairingList {
|
|
|
|
|
|
const GatewayDevicePairingList({required this.pending, required this.paired});
|
|
|
|
|
|
|
|
|
|
|
|
final List<GatewayPendingDevice> pending;
|
|
|
|
|
|
final List<GatewayPairedDevice> paired;
|
|
|
|
|
|
|
|
|
|
|
|
const GatewayDevicePairingList.empty()
|
|
|
|
|
|
: pending = const <GatewayPendingDevice>[],
|
|
|
|
|
|
paired = const <GatewayPairedDevice>[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewayPendingDevice {
|
|
|
|
|
|
const GatewayPendingDevice({
|
|
|
|
|
|
required this.requestId,
|
|
|
|
|
|
required this.deviceId,
|
|
|
|
|
|
required this.displayName,
|
|
|
|
|
|
required this.role,
|
|
|
|
|
|
required this.scopes,
|
|
|
|
|
|
required this.remoteIp,
|
|
|
|
|
|
required this.isRepair,
|
|
|
|
|
|
required this.requestedAtMs,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String requestId;
|
|
|
|
|
|
final String deviceId;
|
|
|
|
|
|
final String? displayName;
|
|
|
|
|
|
final String? role;
|
|
|
|
|
|
final List<String> scopes;
|
|
|
|
|
|
final String? remoteIp;
|
|
|
|
|
|
final bool isRepair;
|
|
|
|
|
|
final int? requestedAtMs;
|
|
|
|
|
|
|
|
|
|
|
|
String get label {
|
|
|
|
|
|
final display = displayName?.trim() ?? '';
|
|
|
|
|
|
return display.isEmpty ? deviceId : display;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewayPairedDevice {
|
|
|
|
|
|
const GatewayPairedDevice({
|
|
|
|
|
|
required this.deviceId,
|
|
|
|
|
|
required this.displayName,
|
|
|
|
|
|
required this.roles,
|
|
|
|
|
|
required this.scopes,
|
|
|
|
|
|
required this.remoteIp,
|
|
|
|
|
|
required this.tokens,
|
|
|
|
|
|
required this.createdAtMs,
|
|
|
|
|
|
required this.approvedAtMs,
|
|
|
|
|
|
required this.currentDevice,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String deviceId;
|
|
|
|
|
|
final String? displayName;
|
|
|
|
|
|
final List<String> roles;
|
|
|
|
|
|
final List<String> scopes;
|
|
|
|
|
|
final String? remoteIp;
|
|
|
|
|
|
final List<GatewayDeviceTokenSummary> tokens;
|
|
|
|
|
|
final int? createdAtMs;
|
|
|
|
|
|
final int? approvedAtMs;
|
|
|
|
|
|
final bool currentDevice;
|
|
|
|
|
|
|
|
|
|
|
|
String get label {
|
|
|
|
|
|
final display = displayName?.trim() ?? '';
|
|
|
|
|
|
return display.isEmpty ? deviceId : display;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class GatewayDeviceTokenSummary {
|
|
|
|
|
|
const GatewayDeviceTokenSummary({
|
|
|
|
|
|
required this.role,
|
|
|
|
|
|
required this.scopes,
|
|
|
|
|
|
required this.createdAtMs,
|
|
|
|
|
|
required this.rotatedAtMs,
|
|
|
|
|
|
required this.revokedAtMs,
|
|
|
|
|
|
required this.lastUsedAtMs,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String role;
|
|
|
|
|
|
final List<String> scopes;
|
|
|
|
|
|
final int? createdAtMs;
|
|
|
|
|
|
final int? rotatedAtMs;
|
|
|
|
|
|
final int? revokedAtMs;
|
|
|
|
|
|
final int? lastUsedAtMs;
|
|
|
|
|
|
|
|
|
|
|
|
bool get revoked => revokedAtMs != null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-11 07:32:52 +08:00
|
|
|
|
class SecretReferenceEntry {
|
|
|
|
|
|
const SecretReferenceEntry({
|
|
|
|
|
|
required this.name,
|
|
|
|
|
|
required this.provider,
|
|
|
|
|
|
required this.module,
|
|
|
|
|
|
required this.maskedValue,
|
|
|
|
|
|
required this.status,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String name;
|
|
|
|
|
|
final String provider;
|
|
|
|
|
|
final String module;
|
|
|
|
|
|
final String maskedValue;
|
|
|
|
|
|
final String status;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class SecretAuditEntry {
|
|
|
|
|
|
const SecretAuditEntry({
|
|
|
|
|
|
required this.timeLabel,
|
|
|
|
|
|
required this.action,
|
|
|
|
|
|
required this.provider,
|
|
|
|
|
|
required this.target,
|
|
|
|
|
|
required this.module,
|
|
|
|
|
|
required this.status,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String timeLabel;
|
|
|
|
|
|
final String action;
|
|
|
|
|
|
final String provider;
|
|
|
|
|
|
final String target;
|
|
|
|
|
|
final String module;
|
|
|
|
|
|
final String status;
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'timeLabel': timeLabel,
|
|
|
|
|
|
'action': action,
|
|
|
|
|
|
'provider': provider,
|
|
|
|
|
|
'target': target,
|
|
|
|
|
|
'module': module,
|
|
|
|
|
|
'status': status,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory SecretAuditEntry.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return SecretAuditEntry(
|
|
|
|
|
|
timeLabel: json['timeLabel'] as String? ?? '',
|
|
|
|
|
|
action: json['action'] as String? ?? '',
|
|
|
|
|
|
provider: json['provider'] as String? ?? '',
|
|
|
|
|
|
target: json['target'] as String? ?? '',
|
|
|
|
|
|
module: json['module'] as String? ?? '',
|
|
|
|
|
|
status: json['status'] as String? ?? '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class DerivedTaskItem {
|
|
|
|
|
|
const DerivedTaskItem({
|
|
|
|
|
|
required this.id,
|
|
|
|
|
|
required this.title,
|
|
|
|
|
|
required this.owner,
|
|
|
|
|
|
required this.status,
|
|
|
|
|
|
required this.surface,
|
|
|
|
|
|
required this.startedAtLabel,
|
|
|
|
|
|
required this.durationLabel,
|
|
|
|
|
|
required this.summary,
|
|
|
|
|
|
required this.sessionKey,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
final String title;
|
|
|
|
|
|
final String owner;
|
|
|
|
|
|
final String status;
|
|
|
|
|
|
final String surface;
|
|
|
|
|
|
final String startedAtLabel;
|
|
|
|
|
|
final String durationLabel;
|
|
|
|
|
|
final String summary;
|
|
|
|
|
|
final String sessionKey;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class LocalDeviceIdentity {
|
|
|
|
|
|
const LocalDeviceIdentity({
|
|
|
|
|
|
required this.deviceId,
|
|
|
|
|
|
required this.publicKeyBase64Url,
|
|
|
|
|
|
required this.privateKeyBase64Url,
|
|
|
|
|
|
required this.createdAtMs,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String deviceId;
|
|
|
|
|
|
final String publicKeyBase64Url;
|
|
|
|
|
|
final String privateKeyBase64Url;
|
|
|
|
|
|
final int createdAtMs;
|
2026-03-12 09:55:17 +08:00
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return <String, dynamic>{
|
|
|
|
|
|
'deviceId': deviceId,
|
|
|
|
|
|
'publicKeyBase64Url': publicKeyBase64Url,
|
|
|
|
|
|
'privateKeyBase64Url': privateKeyBase64Url,
|
|
|
|
|
|
'createdAtMs': createdAtMs,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory LocalDeviceIdentity.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return LocalDeviceIdentity(
|
|
|
|
|
|
deviceId: json['deviceId'] as String? ?? '',
|
|
|
|
|
|
publicKeyBase64Url: json['publicKeyBase64Url'] as String? ?? '',
|
|
|
|
|
|
privateKeyBase64Url: json['privateKeyBase64Url'] as String? ?? '',
|
|
|
|
|
|
createdAtMs: (json['createdAtMs'] as num?)?.toInt() ?? 0,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2026-03-11 07:32:52 +08:00
|
|
|
|
}
|
2026-03-19 12:33:50 +08:00
|
|
|
|
|
|
|
|
|
|
/// 多 Agent 协作角色
|
|
|
|
|
|
enum MultiAgentRole {
|
2026-03-20 16:52:20 +08:00
|
|
|
|
architect, // 调度/文档:需求收口、接受标准、工作流设计
|
|
|
|
|
|
engineer, // 主程:关键实现、重构、集成
|
|
|
|
|
|
testerDoc, // worker/review:并行切片、复审、回归建议
|
2026-03-19 12:33:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-20 08:17:34 +08:00
|
|
|
|
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,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 12:33:50 +08:00
|
|
|
|
extension MultiAgentRoleCopy on MultiAgentRole {
|
|
|
|
|
|
String get label => switch (this) {
|
2026-03-20 16:52:20 +08:00
|
|
|
|
MultiAgentRole.architect => 'Architect(调度/文档)',
|
|
|
|
|
|
MultiAgentRole.engineer => 'Lead Engineer(主程)',
|
|
|
|
|
|
MultiAgentRole.testerDoc => 'Worker/Review(Worker 池)',
|
2026-03-19 12:33:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
String get description => switch (this) {
|
2026-03-20 16:52:20 +08:00
|
|
|
|
MultiAgentRole.architect => '负责需求收口、接受标准、文档与协作调度',
|
|
|
|
|
|
MultiAgentRole.engineer => '负责主实现、关键改动、集成收口',
|
|
|
|
|
|
MultiAgentRole.testerDoc => '负责并行 worker、复审、回归和补充说明',
|
2026-03-19 12:33:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2026-03-20 16:52:20 +08:00
|
|
|
|
final String cliTool; // e.g. 'claude' | 'codex' | 'opencode' | 'gemini'
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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,
|
|
|
|
|
|
required this.label,
|
|
|
|
|
|
required this.source,
|
|
|
|
|
|
required this.selected,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String key;
|
|
|
|
|
|
final String label;
|
|
|
|
|
|
final String source;
|
|
|
|
|
|
final bool selected;
|
|
|
|
|
|
|
|
|
|
|
|
ManagedSkillEntry copyWith({
|
|
|
|
|
|
String? key,
|
|
|
|
|
|
String? label,
|
|
|
|
|
|
String? source,
|
|
|
|
|
|
bool? selected,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return ManagedSkillEntry(
|
|
|
|
|
|
key: key ?? this.key,
|
|
|
|
|
|
label: label ?? this.label,
|
|
|
|
|
|
source: source ?? this.source,
|
|
|
|
|
|
selected: selected ?? this.selected,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {'key': key, 'label': label, 'source': source, 'selected': selected};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory ManagedSkillEntry.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return ManagedSkillEntry(
|
|
|
|
|
|
key: json['key'] as String? ?? '',
|
|
|
|
|
|
label: json['label'] as String? ?? '',
|
|
|
|
|
|
source: json['source'] as String? ?? '',
|
|
|
|
|
|
selected: json['selected'] as bool? ?? false,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class ManagedMcpServerEntry {
|
|
|
|
|
|
const ManagedMcpServerEntry({
|
|
|
|
|
|
required this.id,
|
|
|
|
|
|
required this.name,
|
|
|
|
|
|
required this.transport,
|
|
|
|
|
|
required this.command,
|
|
|
|
|
|
required this.url,
|
|
|
|
|
|
required this.args,
|
|
|
|
|
|
required this.envKeys,
|
|
|
|
|
|
required this.enabled,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String id;
|
|
|
|
|
|
final String name;
|
|
|
|
|
|
final String transport;
|
|
|
|
|
|
final String command;
|
|
|
|
|
|
final String url;
|
|
|
|
|
|
final List<String> args;
|
|
|
|
|
|
final List<String> envKeys;
|
|
|
|
|
|
final bool enabled;
|
|
|
|
|
|
|
|
|
|
|
|
ManagedMcpServerEntry copyWith({
|
|
|
|
|
|
String? id,
|
|
|
|
|
|
String? name,
|
|
|
|
|
|
String? transport,
|
|
|
|
|
|
String? command,
|
|
|
|
|
|
String? url,
|
|
|
|
|
|
List<String>? args,
|
|
|
|
|
|
List<String>? envKeys,
|
|
|
|
|
|
bool? enabled,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return ManagedMcpServerEntry(
|
|
|
|
|
|
id: id ?? this.id,
|
|
|
|
|
|
name: name ?? this.name,
|
|
|
|
|
|
transport: transport ?? this.transport,
|
|
|
|
|
|
command: command ?? this.command,
|
|
|
|
|
|
url: url ?? this.url,
|
|
|
|
|
|
args: args ?? this.args,
|
|
|
|
|
|
envKeys: envKeys ?? this.envKeys,
|
|
|
|
|
|
enabled: enabled ?? this.enabled,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': id,
|
|
|
|
|
|
'name': name,
|
|
|
|
|
|
'transport': transport,
|
|
|
|
|
|
'command': command,
|
|
|
|
|
|
'url': url,
|
|
|
|
|
|
'args': args,
|
|
|
|
|
|
'envKeys': envKeys,
|
|
|
|
|
|
'enabled': enabled,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory ManagedMcpServerEntry.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
final rawArgs = json['args'];
|
|
|
|
|
|
final rawEnvKeys = json['envKeys'];
|
|
|
|
|
|
return ManagedMcpServerEntry(
|
|
|
|
|
|
id: json['id'] as String? ?? '',
|
|
|
|
|
|
name: json['name'] as String? ?? '',
|
|
|
|
|
|
transport: json['transport'] as String? ?? 'stdio',
|
|
|
|
|
|
command: json['command'] as String? ?? '',
|
|
|
|
|
|
url: json['url'] as String? ?? '',
|
|
|
|
|
|
args: rawArgs is List
|
|
|
|
|
|
? rawArgs.map((item) => item.toString()).toList(growable: false)
|
|
|
|
|
|
: const <String>[],
|
|
|
|
|
|
envKeys: rawEnvKeys is List
|
|
|
|
|
|
? rawEnvKeys.map((item) => item.toString()).toList(growable: false)
|
|
|
|
|
|
: const <String>[],
|
|
|
|
|
|
enabled: json['enabled'] as bool? ?? true,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class ManagedMountTargetState {
|
|
|
|
|
|
const ManagedMountTargetState({
|
|
|
|
|
|
required this.targetId,
|
|
|
|
|
|
required this.label,
|
|
|
|
|
|
required this.available,
|
|
|
|
|
|
required this.supportsSkills,
|
|
|
|
|
|
required this.supportsMcp,
|
|
|
|
|
|
required this.supportsAiGatewayInjection,
|
|
|
|
|
|
required this.discoveryState,
|
|
|
|
|
|
required this.syncState,
|
|
|
|
|
|
required this.discoveredSkillCount,
|
|
|
|
|
|
required this.discoveredMcpCount,
|
|
|
|
|
|
required this.managedMcpCount,
|
|
|
|
|
|
required this.detail,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
final String targetId;
|
|
|
|
|
|
final String label;
|
|
|
|
|
|
final bool available;
|
|
|
|
|
|
final bool supportsSkills;
|
|
|
|
|
|
final bool supportsMcp;
|
|
|
|
|
|
final bool supportsAiGatewayInjection;
|
|
|
|
|
|
final String discoveryState;
|
|
|
|
|
|
final String syncState;
|
|
|
|
|
|
final int discoveredSkillCount;
|
|
|
|
|
|
final int discoveredMcpCount;
|
|
|
|
|
|
final int managedMcpCount;
|
|
|
|
|
|
final String detail;
|
|
|
|
|
|
|
|
|
|
|
|
ManagedMountTargetState copyWith({
|
|
|
|
|
|
String? targetId,
|
|
|
|
|
|
String? label,
|
|
|
|
|
|
bool? available,
|
|
|
|
|
|
bool? supportsSkills,
|
|
|
|
|
|
bool? supportsMcp,
|
|
|
|
|
|
bool? supportsAiGatewayInjection,
|
|
|
|
|
|
String? discoveryState,
|
|
|
|
|
|
String? syncState,
|
|
|
|
|
|
int? discoveredSkillCount,
|
|
|
|
|
|
int? discoveredMcpCount,
|
|
|
|
|
|
int? managedMcpCount,
|
|
|
|
|
|
String? detail,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return ManagedMountTargetState(
|
|
|
|
|
|
targetId: targetId ?? this.targetId,
|
|
|
|
|
|
label: label ?? this.label,
|
|
|
|
|
|
available: available ?? this.available,
|
|
|
|
|
|
supportsSkills: supportsSkills ?? this.supportsSkills,
|
|
|
|
|
|
supportsMcp: supportsMcp ?? this.supportsMcp,
|
|
|
|
|
|
supportsAiGatewayInjection:
|
|
|
|
|
|
supportsAiGatewayInjection ?? this.supportsAiGatewayInjection,
|
|
|
|
|
|
discoveryState: discoveryState ?? this.discoveryState,
|
|
|
|
|
|
syncState: syncState ?? this.syncState,
|
|
|
|
|
|
discoveredSkillCount: discoveredSkillCount ?? this.discoveredSkillCount,
|
|
|
|
|
|
discoveredMcpCount: discoveredMcpCount ?? this.discoveredMcpCount,
|
|
|
|
|
|
managedMcpCount: managedMcpCount ?? this.managedMcpCount,
|
|
|
|
|
|
detail: detail ?? this.detail,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
'targetId': targetId,
|
|
|
|
|
|
'label': label,
|
|
|
|
|
|
'available': available,
|
|
|
|
|
|
'supportsSkills': supportsSkills,
|
|
|
|
|
|
'supportsMcp': supportsMcp,
|
|
|
|
|
|
'supportsAiGatewayInjection': supportsAiGatewayInjection,
|
|
|
|
|
|
'discoveryState': discoveryState,
|
|
|
|
|
|
'syncState': syncState,
|
|
|
|
|
|
'discoveredSkillCount': discoveredSkillCount,
|
|
|
|
|
|
'discoveredMcpCount': discoveredMcpCount,
|
|
|
|
|
|
'managedMcpCount': managedMcpCount,
|
|
|
|
|
|
'detail': detail,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory ManagedMountTargetState.fromJson(Map<String, dynamic> json) {
|
|
|
|
|
|
return ManagedMountTargetState(
|
|
|
|
|
|
targetId: json['targetId'] as String? ?? '',
|
|
|
|
|
|
label: json['label'] as String? ?? '',
|
|
|
|
|
|
available: json['available'] as bool? ?? false,
|
|
|
|
|
|
supportsSkills: json['supportsSkills'] as bool? ?? false,
|
|
|
|
|
|
supportsMcp: json['supportsMcp'] as bool? ?? false,
|
|
|
|
|
|
supportsAiGatewayInjection:
|
|
|
|
|
|
json['supportsAiGatewayInjection'] as bool? ?? false,
|
|
|
|
|
|
discoveryState: json['discoveryState'] as String? ?? 'idle',
|
|
|
|
|
|
syncState: json['syncState'] as String? ?? 'idle',
|
|
|
|
|
|
discoveredSkillCount: json['discoveredSkillCount'] as int? ?? 0,
|
|
|
|
|
|
discoveredMcpCount: json['discoveredMcpCount'] as int? ?? 0,
|
|
|
|
|
|
managedMcpCount: json['managedMcpCount'] as int? ?? 0,
|
|
|
|
|
|
detail: json['detail'] as String? ?? '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
factory ManagedMountTargetState.placeholder({
|
|
|
|
|
|
required String targetId,
|
|
|
|
|
|
required String label,
|
|
|
|
|
|
required bool supportsSkills,
|
|
|
|
|
|
required bool supportsMcp,
|
|
|
|
|
|
required bool supportsAiGatewayInjection,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return ManagedMountTargetState(
|
|
|
|
|
|
targetId: targetId,
|
|
|
|
|
|
label: label,
|
|
|
|
|
|
available: false,
|
|
|
|
|
|
supportsSkills: supportsSkills,
|
|
|
|
|
|
supportsMcp: supportsMcp,
|
|
|
|
|
|
supportsAiGatewayInjection: supportsAiGatewayInjection,
|
|
|
|
|
|
discoveryState: 'idle',
|
|
|
|
|
|
syncState: 'idle',
|
|
|
|
|
|
discoveredSkillCount: 0,
|
|
|
|
|
|
discoveredMcpCount: 0,
|
|
|
|
|
|
managedMcpCount: 0,
|
|
|
|
|
|
detail: '',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static List<ManagedMountTargetState> defaults() {
|
|
|
|
|
|
return const <ManagedMountTargetState>[
|
2026-03-20 08:17:34 +08:00
|
|
|
|
ManagedMountTargetState(
|
|
|
|
|
|
targetId: 'aris',
|
|
|
|
|
|
label: 'ARIS',
|
|
|
|
|
|
available: false,
|
|
|
|
|
|
supportsSkills: true,
|
|
|
|
|
|
supportsMcp: true,
|
|
|
|
|
|
supportsAiGatewayInjection: false,
|
|
|
|
|
|
discoveryState: 'idle',
|
|
|
|
|
|
syncState: 'idle',
|
|
|
|
|
|
discoveredSkillCount: 0,
|
|
|
|
|
|
discoveredMcpCount: 0,
|
|
|
|
|
|
managedMcpCount: 0,
|
|
|
|
|
|
detail: '',
|
|
|
|
|
|
),
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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: '',
|
|
|
|
|
|
),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 多 Agent 协作配置
|
|
|
|
|
|
class MultiAgentConfig {
|
|
|
|
|
|
const MultiAgentConfig({
|
|
|
|
|
|
required this.enabled,
|
|
|
|
|
|
required this.autoSync,
|
2026-03-20 08:17:34 +08:00
|
|
|
|
required this.framework,
|
|
|
|
|
|
required this.arisEnabled,
|
|
|
|
|
|
required this.arisMode,
|
|
|
|
|
|
required this.arisBundleVersion,
|
|
|
|
|
|
required this.arisCompatStatus,
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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;
|
2026-03-20 08:17:34 +08:00
|
|
|
|
final MultiAgentFramework framework;
|
|
|
|
|
|
final bool arisEnabled;
|
|
|
|
|
|
final String arisMode;
|
|
|
|
|
|
final String arisBundleVersion;
|
|
|
|
|
|
final String arisCompatStatus;
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-03-20 08:17:34 +08:00
|
|
|
|
bool get usesAris => arisEnabled || framework == MultiAgentFramework.aris;
|
|
|
|
|
|
|
2026-03-19 12:33:50 +08:00
|
|
|
|
factory MultiAgentConfig.defaults() {
|
|
|
|
|
|
return MultiAgentConfig(
|
|
|
|
|
|
enabled: false,
|
|
|
|
|
|
autoSync: true,
|
2026-03-20 08:17:34 +08:00
|
|
|
|
framework: MultiAgentFramework.native,
|
|
|
|
|
|
arisEnabled: false,
|
|
|
|
|
|
arisMode: 'full',
|
|
|
|
|
|
arisBundleVersion: '',
|
|
|
|
|
|
arisCompatStatus: 'idle',
|
2026-03-19 12:33:50 +08:00
|
|
|
|
architect: const AgentWorkerConfig(
|
|
|
|
|
|
role: MultiAgentRole.architect,
|
2026-03-20 16:52:20 +08:00
|
|
|
|
cliTool: 'claude',
|
|
|
|
|
|
model: 'kimi-k2.5:cloud',
|
2026-03-19 12:33:50 +08:00
|
|
|
|
enabled: true,
|
|
|
|
|
|
),
|
|
|
|
|
|
engineer: const AgentWorkerConfig(
|
|
|
|
|
|
role: MultiAgentRole.engineer,
|
2026-03-20 16:52:20 +08:00
|
|
|
|
cliTool: 'codex',
|
|
|
|
|
|
model: 'minimax-m2.7:cloud',
|
2026-03-19 12:33:50 +08:00
|
|
|
|
enabled: true,
|
|
|
|
|
|
),
|
|
|
|
|
|
tester: const AgentWorkerConfig(
|
|
|
|
|
|
role: MultiAgentRole.testerDoc,
|
2026-03-20 16:52:20 +08:00
|
|
|
|
cliTool: 'opencode',
|
|
|
|
|
|
model: 'glm-5:cloud',
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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>[
|
2026-03-20 08:17:34 +08:00
|
|
|
|
ManagedMountTargetState(
|
|
|
|
|
|
targetId: 'aris',
|
|
|
|
|
|
label: 'ARIS',
|
|
|
|
|
|
available: false,
|
|
|
|
|
|
supportsSkills: true,
|
|
|
|
|
|
supportsMcp: true,
|
|
|
|
|
|
supportsAiGatewayInjection: false,
|
|
|
|
|
|
discoveryState: 'idle',
|
|
|
|
|
|
syncState: 'idle',
|
|
|
|
|
|
discoveredSkillCount: 0,
|
|
|
|
|
|
discoveredMcpCount: 0,
|
|
|
|
|
|
managedMcpCount: 0,
|
|
|
|
|
|
detail: '',
|
|
|
|
|
|
),
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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,
|
2026-03-20 08:17:34 +08:00
|
|
|
|
MultiAgentFramework? framework,
|
|
|
|
|
|
bool? arisEnabled,
|
|
|
|
|
|
String? arisMode,
|
|
|
|
|
|
String? arisBundleVersion,
|
|
|
|
|
|
String? arisCompatStatus,
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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,
|
2026-03-20 08:17:34 +08:00
|
|
|
|
framework: framework ?? this.framework,
|
|
|
|
|
|
arisEnabled: arisEnabled ?? this.arisEnabled,
|
|
|
|
|
|
arisMode: arisMode ?? this.arisMode,
|
|
|
|
|
|
arisBundleVersion: arisBundleVersion ?? this.arisBundleVersion,
|
|
|
|
|
|
arisCompatStatus: arisCompatStatus ?? this.arisCompatStatus,
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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,
|
2026-03-20 08:17:34 +08:00
|
|
|
|
'framework': framework.name,
|
|
|
|
|
|
'arisEnabled': arisEnabled,
|
|
|
|
|
|
'arisMode': arisMode,
|
|
|
|
|
|
'arisBundleVersion': arisBundleVersion,
|
|
|
|
|
|
'arisCompatStatus': arisCompatStatus,
|
2026-03-19 12:33:50 +08:00
|
|
|
|
'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,
|
2026-03-20 08:17:34 +08:00
|
|
|
|
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,
|
2026-03-20 16:52:20 +08:00
|
|
|
|
architect: parseWorker(
|
|
|
|
|
|
architectJson,
|
|
|
|
|
|
MultiAgentRole.architect,
|
|
|
|
|
|
defaults.architect.cliTool,
|
|
|
|
|
|
),
|
|
|
|
|
|
engineer: parseWorker(
|
|
|
|
|
|
engineerJson,
|
|
|
|
|
|
MultiAgentRole.engineer,
|
|
|
|
|
|
defaults.engineer.cliTool,
|
|
|
|
|
|
),
|
|
|
|
|
|
tester: parseWorker(
|
|
|
|
|
|
testerJson,
|
|
|
|
|
|
MultiAgentRole.testerDoc,
|
|
|
|
|
|
defaults.tester.cliTool,
|
|
|
|
|
|
),
|
2026-03-19 12:33:50 +08:00
|
|
|
|
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>{},
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|