Files
nexus/backend/Services/OpenClawRunEventReconciler.cs
T
AzuTear f5552218bc
CI - Build & Test / Backend (.NET) (push) Successful in 42s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m46s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Successful in 56s
feat: ship agent-first mission control v0.2.57
2026-07-31 22:39:47 +02:00

72 lines
2.8 KiB
C#

namespace Nexus.Api.Services;
/// <summary>
/// Continuously folds bounded Gateway chat/run events into the durable Nexus
/// run projection. Re-processing is safe because per-run sequence cursors and
/// persisted terminal/gap event ids reject duplicates.
/// </summary>
public sealed class OpenClawRunEventReconciler(
IGatewayConnector connector,
IServiceScopeFactory scopeFactory,
ILogger<OpenClawRunEventReconciler> logger) : BackgroundService
{
private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(750);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
string? cursor = null;
while (!stoppingToken.IsCancellationRequested)
{
try
{
var snapshot = connector.GetRecentEvents(500)
.OrderBy(item => item.ReceivedAt)
.ThenBy(item => item.Sequence)
.ThenBy(OpenClawEventIdentity.Create, StringComparer.Ordinal)
.ToList();
var startIndex = cursor is null
? 0
: snapshot.FindIndex(item =>
string.Equals(
OpenClawEventIdentity.Create(item),
cursor,
StringComparison.Ordinal)) + 1;
if (startIndex <= 0 && cursor is not null)
{
// The bounded buffer rolled over. Re-process what remains;
// run-level sequence cursors and persisted event ids dedupe it.
startIndex = 0;
}
if (startIndex < snapshot.Count)
{
await using var scope = scopeFactory.CreateAsyncScope();
var runs = scope.ServiceProvider.GetRequiredService<IOpenClawRunService>();
foreach (var gatewayEvent in snapshot.Skip(startIndex))
{
await runs.ReconcileAsync(gatewayEvent, stoppingToken);
cursor = OpenClawEventIdentity.Create(gatewayEvent);
}
}
else if (snapshot.Count > 0)
{
cursor = OpenClawEventIdentity.Create(snapshot[^1]);
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
break;
}
catch (Exception exception)
{
logger.LogWarning(
exception,
"Could not reconcile the latest OpenClaw run events; retrying without dropping the durable projection.");
}
await Task.Delay(PollInterval, stoppingToken);
}
}
}