xworkmate-app/lib/runtime/runtime_controllers_entities.dart
Cowork 3P 92f81eb27a refactor: eliminate dead codex_runtime methods, add anti-fallback policy
codex_runtime.dart (-290 lines):
- Remove 17 dead methods behind UnsupportedError guard
  (findCodexBinary, startStdio, request, startThread, resumeThread,
   sendMessage, interrupt, getAccount, listModels, listSkills, stop,
   dispose, _resolveLaunchConfiguration + 3 @visibleForTesting wrappers)
- Remove 10 dead fields (_process, _state, _pendingRequests, _events, etc.)
- Remove ChangeNotifier mixin (nothing to notify)
- Keep only model types, enums, and standalone helper functions

AGENTS.md (+21 lines):
- Add Fallback and Dead Code Elimination Policy section
- Forbidden: cascading fallbacks, lingering DEPRECATED code,
  dead code behind guards, silent catch blocks, redundant indirection,
  excessive JSON key probing
- Required: inline WHY comments on every retained fallback chain

Additional cleanup:
- gateway_acp_client.dart: remove unused _GatewayAcpSessionUpdate class
- runtime_controllers_entities.dart: replace _canRefreshThroughRuntime
  with runtimeInternal.isConnected
- runtime_models_gateway_entities.dart: relocate CollaborationAttachment
2026-06-04 07:13:29 +00:00

329 lines
8.9 KiB
Dart

// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'gateway_runtime.dart';
import 'runtime_models.dart';
import 'secure_config_store.dart';
import 'runtime_controllers_settings.dart';
import 'runtime_controllers_gateway.dart';
import 'runtime_controllers_derived_tasks.dart';
class SkillsController extends ChangeNotifier {
SkillsController(this.runtimeInternal) {
_runtimeListener = () {
if (!runtimeInternal.isConnected) {
// Reset auto-refresh flag on disconnect so a subsequent reconnect
// will trigger a fresh load.
_hasAutoRefreshed = false;
return;
}
// Auto-refresh on first gateway connect only when skills are empty,
// not already loading, and haven't auto-refreshed this session.
if (loadingInternal || itemsInternal.isNotEmpty || _hasAutoRefreshed) {
return;
}
_hasAutoRefreshed = true;
refresh();
};
runtimeInternal.addListener(_runtimeListener!);
}
final GatewayRuntime runtimeInternal;
List<GatewaySkillSummary> itemsInternal = const <GatewaySkillSummary>[];
bool loadingInternal = false;
String? errorInternal;
int _retryCount = 0;
static const int _maxRetries = 2;
VoidCallback? _runtimeListener;
bool _hasAutoRefreshed = false;
List<GatewaySkillSummary> get items => itemsInternal;
bool get loading => loadingInternal;
String? get error => errorInternal;
/// Whether the user can manually retry (non-empty error + not loading).
bool get canRetry => (errorInternal?.isNotEmpty ?? false) && !loadingInternal;
bool get _canRefreshThroughRuntime =>
runtimeInternal.isConnected || runtimeInternal.canConnectBridgeSession;
Future<void> refresh({String? agentId}) async {
if (!_canRefreshThroughRuntime) {
errorInternal = 'Gateway 未连接,无法加载技能列表。';
notifyListeners();
return;
}
loadingInternal = true;
errorInternal = null;
_retryCount = 0;
notifyListeners();
await _doRefresh(agentId: agentId);
}
Future<void> _doRefresh({String? agentId}) async {
try {
itemsInternal = await runtimeInternal.listSkills(agentId: agentId);
errorInternal = null;
_retryCount = 0;
} catch (error) {
if (_retryCount < _maxRetries && runtimeInternal.isConnected) {
_retryCount++;
final delay = Duration(seconds: _retryCount * 2);
await Future<void>.delayed(delay);
if (runtimeInternal.isConnected) {
await _doRefresh(agentId: agentId);
return;
}
}
errorInternal = error.toString();
} finally {
loadingInternal = false;
notifyListeners();
}
}
@override
void dispose() {
if (_runtimeListener != null) {
runtimeInternal.removeListener(_runtimeListener!);
_runtimeListener = null;
}
super.dispose();
}
}
class ModelsController extends ChangeNotifier {
ModelsController(this.runtimeInternal, this.settingsControllerInternal);
final GatewayRuntime runtimeInternal;
final SettingsController settingsControllerInternal;
List<GatewayModelSummary> itemsInternal = const <GatewayModelSummary>[];
bool loadingInternal = false;
String? errorInternal;
List<GatewayModelSummary> get items => itemsInternal;
bool get loading => loadingInternal;
String? get error => errorInternal;
void restoreFromSettings(AiGatewayProfile profile) {
final models = modelsFromProfileInternal(profile);
if (models.length == itemsInternal.length &&
models.every(
(item) => itemsInternal.any((current) => current.id == item.id),
)) {
return;
}
itemsInternal = models;
notifyListeners();
}
Future<void> refresh() async {
loadingInternal = true;
errorInternal = null;
notifyListeners();
try {
final profile = settingsControllerInternal.snapshot.aiGateway;
if (profile.baseUrl.trim().isNotEmpty) {
final synced = await settingsControllerInternal.syncAiGatewayCatalog(
profile,
);
itemsInternal = modelsFromProfileInternal(synced);
} else if (runtimeInternal.isConnected) {
itemsInternal = await runtimeInternal.listModels();
} else {
itemsInternal = modelsFromProfileInternal(profile);
}
} catch (error) {
errorInternal = error.toString();
} finally {
loadingInternal = false;
notifyListeners();
}
}
List<GatewayModelSummary> modelsFromProfileInternal(
AiGatewayProfile profile,
) {
final selected = profile.selectedModels
.where(profile.availableModels.contains)
.toList(growable: false);
final candidates = selected.isNotEmpty
? selected
: profile.availableModels.take(5).toList(growable: false);
return candidates
.map(
(item) => GatewayModelSummary(
id: item,
name: item,
provider: 'LLM API',
contextWindow: null,
maxOutputTokens: null,
),
)
.toList(growable: false);
}
}
class CronJobsController extends ChangeNotifier {
CronJobsController(this.runtimeInternal);
final GatewayRuntime runtimeInternal;
List<GatewayCronJobSummary> itemsInternal = const <GatewayCronJobSummary>[];
bool loadingInternal = false;
String? errorInternal;
List<GatewayCronJobSummary> get items => itemsInternal;
bool get loading => loadingInternal;
String? get error => errorInternal;
Future<void> refresh() async {
if (!runtimeInternal.isConnected) {
itemsInternal = const <GatewayCronJobSummary>[];
errorInternal = null;
notifyListeners();
return;
}
loadingInternal = true;
errorInternal = null;
notifyListeners();
try {
itemsInternal = await runtimeInternal.listCronJobs();
} catch (error) {
errorInternal = error.toString();
} finally {
loadingInternal = false;
notifyListeners();
}
}
}
class DevicesController extends ChangeNotifier {
DevicesController(this.runtimeInternal);
final GatewayRuntime runtimeInternal;
GatewayDevicePairingList itemsInternal =
const GatewayDevicePairingList.empty();
bool loadingInternal = false;
String? errorInternal;
GatewayDevicePairingList get items => itemsInternal;
bool get loading => loadingInternal;
String? get error => errorInternal;
Future<void> refresh({bool quiet = false}) async {
if (!runtimeInternal.isConnected) {
itemsInternal = const GatewayDevicePairingList.empty();
if (!quiet) {
errorInternal = null;
}
notifyListeners();
return;
}
if (loadingInternal) {
return;
}
loadingInternal = true;
if (!quiet) {
errorInternal = null;
}
notifyListeners();
try {
itemsInternal = await runtimeInternal.listDevicePairing();
} catch (error) {
if (!quiet) {
errorInternal = error.toString();
}
} finally {
loadingInternal = false;
notifyListeners();
}
}
Future<void> approve(String requestId) async {
errorInternal = null;
notifyListeners();
try {
await runtimeInternal.approveDevicePairing(requestId);
await refresh(quiet: true);
} catch (error) {
errorInternal = error.toString();
notifyListeners();
}
}
Future<void> reject(String requestId) async {
errorInternal = null;
notifyListeners();
try {
await runtimeInternal.rejectDevicePairing(requestId);
await refresh(quiet: true);
} catch (error) {
errorInternal = error.toString();
notifyListeners();
}
}
Future<void> remove(String deviceId) async {
errorInternal = null;
notifyListeners();
try {
await runtimeInternal.removePairedDevice(deviceId);
await refresh(quiet: true);
} catch (error) {
errorInternal = error.toString();
notifyListeners();
}
}
Future<String?> rotateToken({
required String deviceId,
required String role,
List<String> scopes = const <String>[],
}) async {
errorInternal = null;
notifyListeners();
try {
final token = await runtimeInternal.rotateDeviceToken(
deviceId: deviceId,
role: role,
scopes: scopes,
);
await refresh(quiet: true);
return token;
} catch (error) {
errorInternal = error.toString();
notifyListeners();
return null;
}
}
Future<void> revokeToken({
required String deviceId,
required String role,
}) async {
errorInternal = null;
notifyListeners();
try {
await runtimeInternal.revokeDeviceToken(deviceId: deviceId, role: role);
await refresh(quiet: true);
} catch (error) {
errorInternal = error.toString();
notifyListeners();
}
}
void clear() {
itemsInternal = const GatewayDevicePairingList.empty();
errorInternal = null;
loadingInternal = false;
notifyListeners();
}
}