// Agent registry for OpenClaw Gateway integration. // // This module handles agent registration and discovery through the Gateway. import 'dart:async'; import 'package:flutter/foundation.dart'; import 'gateway_runtime.dart'; /// Agent capability description. class AgentCapability { final String name; final String description; final Map? parameters; const AgentCapability({ required this.name, required this.description, this.parameters, }); factory AgentCapability.fromJson(Map json) { return AgentCapability( name: json['name'] as String, description: json['description'] as String, parameters: json['parameters'] as Map?, ); } Map toJson() => { 'name': name, 'description': description, if (parameters != null) 'parameters': parameters, }; } /// Agent registration information. class AgentRegistration { final String agentId; final String agentType; final String name; final String version; final String token; final DateTime registeredAt; final DateTime? expiresAt; final List capabilities; const AgentRegistration({ required this.agentId, required this.agentType, required this.name, required this.version, required this.token, required this.registeredAt, this.expiresAt, this.capabilities = const [], }); factory AgentRegistration.fromJson(Map json) { return AgentRegistration( agentId: json['agentId'] as String, agentType: json['agentType'] as String, name: json['name'] as String, version: json['version'] as String, token: json['token'] as String, registeredAt: DateTime.parse(json['registeredAt'] as String), expiresAt: json['expiresAt'] != null ? DateTime.tryParse(json['expiresAt'] as String) : null, capabilities: (json['capabilities'] as List?) ?.map((e) => AgentCapability.fromJson(e as Map)) .toList() ?? [], ); } Map toJson() => { 'agentId': agentId, 'agentType': agentType, 'name': name, 'version': version, 'token': token, 'registeredAt': registeredAt.toIso8601String(), if (expiresAt != null) 'expiresAt': expiresAt!.toIso8601String(), 'capabilities': capabilities.map((c) => c.toJson()).toList(), }; } /// Agent information from registry. class AgentInfo { final String agentId; final String agentType; final String name; final String status; final List capabilities; final bool isOnline; final DateTime? lastSeen; const AgentInfo({ required this.agentId, required this.agentType, required this.name, required this.status, this.capabilities = const [], this.isOnline = false, this.lastSeen, }); factory AgentInfo.fromJson(Map json) { return AgentInfo( agentId: json['agentId'] as String, agentType: json['agentType'] as String, name: json['name'] as String, status: json['status'] as String, capabilities: (json['capabilities'] as List?)?.map((e) => e as String).toList() ?? [], isOnline: json['isOnline'] as bool? ?? false, lastSeen: json['lastSeen'] != null ? DateTime.tryParse(json['lastSeen'] as String) : null, ); } } /// Agent response from invoke. class AgentResponse { final String content; final String? threadId; final String? turnId; final Map? metadata; const AgentResponse({ required this.content, this.threadId, this.turnId, this.metadata, }); factory AgentResponse.fromJson(Map json) { return AgentResponse( content: json['content'] as String? ?? '', threadId: json['threadId'] as String?, turnId: json['turnId'] as String?, metadata: json['metadata'] as Map?, ); } } /// Exception for agent operations. class AgentException implements Exception { final String message; final String? code; const AgentException(this.message, {this.code}); @override String toString() => code != null ? 'AgentException($code): $message' : message; } /// Agent registry for managing agent registration and discovery. class AgentRegistry with ChangeNotifier { final GatewayRuntime _gateway; AgentRegistration? _registration; List _agents = []; String? _lastError; bool _isRegistering = false; AgentRegistry(this._gateway); AgentRegistration? get registration => _registration; List get agents => List.unmodifiable(_agents); String? get lastError => _lastError; bool get isRegistered => _registration != null; bool get isRegistering => _isRegistering; /// Register this agent with the Gateway. Future register({ required String agentType, required String name, required String version, required List capabilities, String transport = 'in-process', Map? metadata, }) async { if (!_gateway.isConnected) { throw AgentException('Gateway not connected', code: 'NOT_CONNECTED'); } _isRegistering = true; _lastError = null; notifyListeners(); try { final response = await _gateway.request( 'agent/register', params: { 'agentType': agentType, 'name': name, 'version': version, 'capabilities': capabilities.map((c) => c.toJson()).toList(), ...?metadata == null ? null : {'metadata': metadata}, 'transport': transport, }, ); _registration = AgentRegistration.fromJson( response as Map, ); notifyListeners(); return _registration!; } catch (e) { _lastError = e.toString(); notifyListeners(); throw AgentException( 'Failed to register agent: $e', code: 'REGISTRATION_FAILED', ); } finally { _isRegistering = false; notifyListeners(); } } /// Unregister this agent from the Gateway. Future unregister() async { if (_registration == null) { return; } if (!_gateway.isConnected) { throw AgentException('Gateway not connected', code: 'NOT_CONNECTED'); } try { await _gateway.request( 'agent/unregister', params: {'agentId': _registration!.agentId}, ); _registration = null; notifyListeners(); } catch (e) { _lastError = e.toString(); notifyListeners(); throw AgentException( 'Failed to unregister agent: $e', code: 'UNREGISTRATION_FAILED', ); } } /// Clear local registration state without calling the Gateway. void clearRegistration() { _registration = null; notifyListeners(); } /// List all registered agents. Future> listAgents({String? agentType}) async { if (!_gateway.isConnected) { throw AgentException('Gateway not connected', code: 'NOT_CONNECTED'); } try { final response = await _gateway.request( 'agent/list', params: { ...?agentType == null ? null : {'agentType': agentType}, }, ); final agentsJson = response['agents'] as List? ?? []; _agents = agentsJson .map((a) => AgentInfo.fromJson(a as Map)) .toList(); notifyListeners(); return _agents; } catch (e) { _lastError = e.toString(); notifyListeners(); throw AgentException('Failed to list agents: $e', code: 'LIST_FAILED'); } } /// Invoke a remote agent. Future invokeAgent({ required String agentId, required String prompt, Map? context, String? threadId, }) async { if (!_gateway.isConnected) { throw AgentException('Gateway not connected', code: 'NOT_CONNECTED'); } try { final response = await _gateway.request( 'agent/invoke', params: { 'agentId': agentId, 'prompt': prompt, ...?context == null ? null : {'context': context}, ...?threadId == null ? null : {'threadId': threadId}, }, ); return AgentResponse.fromJson(response as Map); } catch (e) { _lastError = e.toString(); notifyListeners(); throw AgentException('Failed to invoke agent: $e', code: 'INVOKE_FAILED'); } } /// Update agent status. Future updateStatus({ required String status, List? capabilities, }) async { if (_registration == null) { throw AgentException('Agent not registered', code: 'NOT_REGISTERED'); } if (!_gateway.isConnected) { throw AgentException('Gateway not connected', code: 'NOT_CONNECTED'); } try { await _gateway.request( 'agent/updateStatus', params: { 'agentId': _registration!.agentId, 'status': status, ...?capabilities == null ? null : {'capabilities': capabilities}, }, ); } catch (e) { _lastError = e.toString(); notifyListeners(); throw AgentException( 'Failed to update status: $e', code: 'UPDATE_FAILED', ); } } /// Sync memory with cloud. Future> syncMemory({ required String direction, String? sinceVersion, }) async { if (!_gateway.isConnected) { throw AgentException('Gateway not connected', code: 'NOT_CONNECTED'); } try { final response = await _gateway.request( 'memory/sync', params: { 'direction': direction, // 'pull', 'push', 'both' ...?sinceVersion == null ? null : {'sinceVersion': sinceVersion}, }, ); return response as Map; } catch (e) { _lastError = e.toString(); notifyListeners(); throw AgentException('Failed to sync memory: $e', code: 'SYNC_FAILED'); } } /// Clear last error. void clearError() { _lastError = null; notifyListeners(); } }