using System.Text.Json.Nodes; using Nexus.Api.Models; namespace Nexus.Api.Services; /// /// Maintains a persistent WebSocket connection to the OpenClaw Gateway /// for real-time event streaming and health monitoring. /// public interface IGatewayConnector { /// /// Current WebSocket connection state. /// GatewayConnectionState ConnectionState { get; } /// /// Gateway version reported at last connection. /// string? GatewayVersion { get; } /// /// Required version from configuration, falling back to Nexus' verified /// OpenClaw release pin when the setting is absent or blank. /// string? RequiredVersion { get; } /// /// Timestamp of the last successful connection attempt. /// DateTimeOffset? LastConnectedAt { get; } /// /// Number of consecutive failed connection attempts. /// int ReconnectAttempts { get; } /// /// Detailed status message (e.g. error or version info). /// string? StatusMessage { get; } /// /// Stable Nexus backend device id used for non-loopback Gateway pairing. /// The private key and device token are never exposed through this contract. /// string? DeviceId { get; } /// /// Whether a paired backend device token is available in protected server-side storage. /// bool DeviceTokenConfigured { get; } /// /// Whether the Gateway is waiting for an operator to approve the current device request. /// bool PairingRequired { get; } /// /// Exact pending OpenClaw pairing request id, when supplied by the Gateway. /// string? PairingRequestId { get; } /// /// Negotiated Gateway protocol version from the latest hello-ok frame. /// int? ProtocolVersion { get; } /// /// RPC methods advertised by the connected Gateway. /// IReadOnlySet AdvertisedMethods { get; } /// /// Event families advertised by the connected Gateway. /// IReadOnlySet AdvertisedEvents { get; } /// /// Operator scopes granted by the Gateway during the handshake. /// IReadOnlySet GrantedScopes { get; } /// /// Normalized endpoint and TLS pin currently used by the running connector. /// These values never include credentials. /// string? ActiveEndpoint => null; string? ActiveTlsFingerprint => null; /// /// Timestamp of the latest event frame received from the Gateway. /// DateTimeOffset? LastEventAt { get; } /// /// Returns whether the current Gateway explicitly advertises an RPC method. /// bool Supports(string method); /// /// Invokes a Gateway RPC over the authenticated protocol-v4 connection. /// Task InvokeAsync( string method, object? parameters = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null); /// /// Returns a bounded newest-first snapshot of recently received Gateway events. /// IReadOnlyList GetRecentEvents(int limit = 100); /// /// Requests a new explicit operator-scope set for the next Gateway /// handshake. The production connector closes the current socket so /// OpenClaw can start its normal pairing or scope-upgrade flow. /// Test connectors may keep the default no-op implementation. /// Task RequestOperatorScopesAsync( IReadOnlyCollection scopes, CancellationToken cancellationToken = default) => Task.CompletedTask; /// /// Re-targets the single connector after Setup has validated the endpoint. /// The optional bootstrap token remains process-memory-only until OpenClaw /// issues a bound device token. /// Task ConfigureEndpointAsync( string endpoint, string? tlsFingerprint, string? bootstrapToken, CancellationToken cancellationToken = default) => Task.CompletedTask; /// /// Closes the active socket after a local detach. The background connector /// may return to an unauthenticated discovery/pairing state, but the /// adopted profile and protected device token are no longer active. /// Task DisconnectAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; } public sealed record GatewayEventEnvelope( string Event, JsonNode? Payload, long? Sequence, long? StateVersion, DateTimeOffset ReceivedAt); /// /// Correlation metadata for one logical Nexus-to-OpenClaw invocation. /// Only fields defined by the OpenClaw wire schema are sent to the Gateway: /// correlation is reflected in the request id, W3C traceparent is attached to /// the request frame, and idempotencyKey is added to params only when /// is explicitly enabled for a /// schema-confirmed method. Actor and the complete context remain in Nexus' /// local audit boundary. /// public sealed record OpenClawInvocationContext( string IdempotencyKey, string CorrelationId, string Actor, string TraceParent, bool IncludeIdempotencyParameter = false) { public static OpenClawInvocationContext Create( string? actor = null, string? idempotencyKey = null, string? correlationId = null, string? traceParent = null, bool includeIdempotencyParameter = false) => OpenClawInvocationContextFactory.Create( actor, idempotencyKey, correlationId, traceParent, includeIdempotencyParameter); } public sealed class OpenClawGatewayRpcException : Exception { public OpenClawGatewayRpcException( string code, string message, JsonNode? details = null, bool retryable = false, int? retryAfterMs = null) : base(message) { Code = code; Details = details; Retryable = retryable; RetryAfterMs = retryAfterMs; } public string Code { get; } public JsonNode? Details { get; } public bool Retryable { get; } public int? RetryAfterMs { get; } } /// /// Represents the current connection state of the GatewayConnector. /// public enum GatewayConnectionState { /// Not yet attempted or initializing. Initializing, /// Connected and healthy. Connected, /// Disconnected, waiting for reconnect. Disconnected, /// Recoverable error, retrying. Reconnecting, /// Failed after maximum retries. Failed }