feat: ship agent-first mission control v0.2.57
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using Nexus.Api.Repositories;
|
||||
|
||||
namespace Nexus.Api.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Re-establishes the official OpenClaw session subscriptions after every
|
||||
/// Gateway reconnect and subscribes active durable Nexus runs to sanitized
|
||||
/// message/tool/approval events.
|
||||
/// </summary>
|
||||
public sealed class OpenClawEventSubscriptionCoordinator(
|
||||
IGatewayConnector connector,
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ILogger<OpenClawEventSubscriptionCoordinator> logger) : BackgroundService
|
||||
{
|
||||
private static readonly TimeSpan SyncInterval = TimeSpan.FromSeconds(2);
|
||||
private readonly Dictionary<string, OpenClawRunSubscription> _messageSubscriptions =
|
||||
new(StringComparer.Ordinal);
|
||||
private DateTimeOffset? _connectionEpoch;
|
||||
private bool _sessionCatalogSubscribed;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SynchronizeOnceAsync(stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogWarning(
|
||||
exception,
|
||||
"Could not synchronize OpenClaw event subscriptions; retrying.");
|
||||
}
|
||||
|
||||
await Task.Delay(SyncInterval, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SynchronizeOnceAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (connector.ConnectionState != GatewayConnectionState.Connected)
|
||||
{
|
||||
ResetConnectionState();
|
||||
return;
|
||||
}
|
||||
|
||||
var connectionEpoch = connector.LastConnectedAt;
|
||||
if (_connectionEpoch != connectionEpoch)
|
||||
{
|
||||
ResetConnectionState();
|
||||
_connectionEpoch = connectionEpoch;
|
||||
}
|
||||
|
||||
if (!_sessionCatalogSubscribed && connector.Supports("sessions.subscribe"))
|
||||
{
|
||||
await connector.InvokeAsync(
|
||||
"sessions.subscribe",
|
||||
new JsonObject(),
|
||||
cancellationToken: cancellationToken);
|
||||
_sessionCatalogSubscribed = true;
|
||||
}
|
||||
|
||||
if (!connector.Supports("sessions.messages.subscribe"))
|
||||
return;
|
||||
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var repository = scope.ServiceProvider.GetRequiredService<IOpenClawRunRepository>();
|
||||
var activeRuns = await repository.GetActiveSubscriptionsAsync(cancellationToken);
|
||||
var desiredSubscriptions = activeRuns.ToDictionary(
|
||||
BuildSubscriptionKey,
|
||||
subscription => subscription,
|
||||
StringComparer.Ordinal);
|
||||
var includeApprovals = connector.GrantedScopes.Contains("operator.admin")
|
||||
|| connector.GrantedScopes.Contains("operator.approvals");
|
||||
|
||||
foreach (var activeRun in activeRuns)
|
||||
{
|
||||
var subscriptionKey = BuildSubscriptionKey(activeRun);
|
||||
if (_messageSubscriptions.ContainsKey(subscriptionKey))
|
||||
continue;
|
||||
|
||||
var parameters = new JsonObject
|
||||
{
|
||||
["key"] = activeRun.SessionKey,
|
||||
["agentId"] = activeRun.AgentId
|
||||
};
|
||||
if (includeApprovals)
|
||||
parameters["includeApprovals"] = true;
|
||||
|
||||
await connector.InvokeAsync(
|
||||
"sessions.messages.subscribe",
|
||||
parameters,
|
||||
cancellationToken: cancellationToken);
|
||||
_messageSubscriptions[subscriptionKey] = activeRun;
|
||||
}
|
||||
|
||||
if (connector.Supports("sessions.messages.unsubscribe"))
|
||||
{
|
||||
foreach (var obsolete in _messageSubscriptions
|
||||
.Where(item => !desiredSubscriptions.ContainsKey(item.Key))
|
||||
.ToList())
|
||||
{
|
||||
await connector.InvokeAsync(
|
||||
"sessions.messages.unsubscribe",
|
||||
new JsonObject
|
||||
{
|
||||
["key"] = obsolete.Value.SessionKey,
|
||||
["agentId"] = obsolete.Value.AgentId
|
||||
},
|
||||
cancellationToken: cancellationToken);
|
||||
_messageSubscriptions.Remove(obsolete.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetConnectionState()
|
||||
{
|
||||
_connectionEpoch = null;
|
||||
_sessionCatalogSubscribed = false;
|
||||
_messageSubscriptions.Clear();
|
||||
}
|
||||
|
||||
private static string BuildSubscriptionKey(OpenClawRunSubscription subscription)
|
||||
=> $"{subscription.SessionKey}\u001f{subscription.AgentId}";
|
||||
}
|
||||
Reference in New Issue
Block a user