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
}