203 lines
7.3 KiB
C#
203 lines
7.3 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Nexus.Api.Data;
|
|
using Nexus.Api.Models;
|
|
using System.Text.Json;
|
|
|
|
namespace Nexus.Api.Repositories;
|
|
|
|
public sealed class OpenClawRunRepository(NexusDbContext db) : IOpenClawRunRepository
|
|
{
|
|
public async Task<IReadOnlyList<OpenClawRun>> GetAsync(
|
|
OpenClawRunQuery query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var runs = db.OpenClawRuns.AsNoTracking().AsQueryable();
|
|
|
|
if (!string.IsNullOrWhiteSpace(query.Status))
|
|
runs = runs.Where(run => run.Status == query.Status);
|
|
if (query.TaskId.HasValue)
|
|
runs = runs.Where(run => run.TaskId == query.TaskId);
|
|
if (query.ProjectId.HasValue)
|
|
runs = runs.Where(run => run.ProjectId == query.ProjectId);
|
|
if (!string.IsNullOrWhiteSpace(query.SessionKey))
|
|
runs = runs.Where(run => run.SessionKey == query.SessionKey);
|
|
if (OpenClawRunCursorCodec.TryDecode(query.Cursor, out var cursor))
|
|
{
|
|
var createdBefore = cursor.CreatedAt;
|
|
if (cursor.Id.HasValue)
|
|
{
|
|
var idBefore = cursor.Id.Value;
|
|
runs = runs.Where(run =>
|
|
run.CreatedAt < createdBefore
|
|
|| (run.CreatedAt == createdBefore
|
|
&& run.Id.CompareTo(idBefore) < 0));
|
|
}
|
|
else
|
|
{
|
|
runs = runs.Where(run => run.CreatedAt < createdBefore);
|
|
}
|
|
}
|
|
|
|
return await runs
|
|
.OrderByDescending(run => run.CreatedAt)
|
|
.ThenByDescending(run => run.Id)
|
|
.Take(Math.Clamp(query.Limit, 1, 201))
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public Task<OpenClawRun?> GetByIdAsync(
|
|
Guid id,
|
|
bool tracking = false,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var query = tracking
|
|
? db.OpenClawRuns.AsQueryable()
|
|
: db.OpenClawRuns.AsNoTracking();
|
|
return query.FirstOrDefaultAsync(run => run.Id == id, cancellationToken);
|
|
}
|
|
|
|
public Task<OpenClawRun?> GetByStartIdempotencyKeyAsync(
|
|
string idempotencyKey,
|
|
CancellationToken cancellationToken = default)
|
|
=> db.OpenClawRuns
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(run => run.StartIdempotencyKey == idempotencyKey, cancellationToken);
|
|
|
|
public Task<OpenClawRun?> GetByOpenClawRunIdAsync(
|
|
string openClawRunId,
|
|
CancellationToken cancellationToken = default)
|
|
=> db.OpenClawRuns
|
|
.FirstOrDefaultAsync(run => run.OpenClawRunId == openClawRunId, cancellationToken);
|
|
|
|
public async Task<IReadOnlyList<OpenClawRunHistory>> GetHistoryAsync(
|
|
Guid runId,
|
|
CancellationToken cancellationToken = default)
|
|
=> await db.OpenClawRunHistory
|
|
.AsNoTracking()
|
|
.Where(item => item.RunId == runId)
|
|
.OrderBy(item => item.OccurredAt)
|
|
.ThenBy(item => item.Id)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
public Task<OpenClawRunHistory?> GetInvocationAsync(
|
|
Guid runId,
|
|
string action,
|
|
string idempotencyKey,
|
|
CancellationToken cancellationToken = default)
|
|
=> db.OpenClawRunHistory
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(
|
|
item => item.RunId == runId
|
|
&& item.Action == action
|
|
&& item.IdempotencyKey == idempotencyKey,
|
|
cancellationToken);
|
|
|
|
public Task<bool> HasGatewayEventAsync(
|
|
string gatewayEventId,
|
|
CancellationToken cancellationToken = default)
|
|
=> db.OpenClawRunHistory
|
|
.AsNoTracking()
|
|
.AnyAsync(item => item.GatewayEventId == gatewayEventId, cancellationToken);
|
|
|
|
public Task<bool> TaskExistsAsync(Guid taskId, CancellationToken cancellationToken = default)
|
|
=> db.Tasks.AsNoTracking().AnyAsync(task => task.Id == taskId, cancellationToken);
|
|
|
|
public Task<bool> ProjectExistsAsync(Guid projectId, CancellationToken cancellationToken = default)
|
|
=> db.Projects.AsNoTracking().AnyAsync(project => project.Id == projectId, cancellationToken);
|
|
|
|
public async Task<IReadOnlyList<OpenClawRunSubscription>> GetActiveSubscriptionsAsync(
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var subscriptions = await db.OpenClawRuns
|
|
.AsNoTracking()
|
|
.Where(run => run.Status == OpenClawRunStates.Dispatching
|
|
|| run.Status == OpenClawRunStates.Running
|
|
|| run.Status == OpenClawRunStates.Stopping)
|
|
.Select(run => new { run.SessionKey, run.AgentId })
|
|
.Distinct()
|
|
.ToListAsync(cancellationToken);
|
|
return subscriptions
|
|
.Select(item => new OpenClawRunSubscription(item.SessionKey, item.AgentId))
|
|
.ToList();
|
|
}
|
|
|
|
public async Task AddAsync(
|
|
OpenClawRun run,
|
|
OpenClawRunHistory history,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
db.OpenClawRuns.Add(run);
|
|
db.OpenClawRunHistory.Add(history);
|
|
db.OutboxEvents.Add(CreateRunEvent("run.created", run));
|
|
await db.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task AddRetryAsync(
|
|
OpenClawRun source,
|
|
OpenClawRun retry,
|
|
OpenClawRunHistory sourceHistory,
|
|
OpenClawRunHistory retryHistory,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
source.UpdatedAt = DateTimeOffset.UtcNow;
|
|
source.Revision++;
|
|
db.OpenClawRuns.Update(source);
|
|
db.OpenClawRuns.Add(retry);
|
|
db.OpenClawRunHistory.AddRange(sourceHistory, retryHistory);
|
|
db.OutboxEvents.Add(CreateRunEvent("run.updated", source));
|
|
db.OutboxEvents.Add(CreateRunEvent(
|
|
"run.created",
|
|
retry,
|
|
source.Id));
|
|
await db.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task UpdateAsync(
|
|
OpenClawRun run,
|
|
OpenClawRunHistory history,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
run.UpdatedAt = DateTimeOffset.UtcNow;
|
|
run.Revision++;
|
|
db.OpenClawRuns.Update(run);
|
|
db.OpenClawRunHistory.Add(history);
|
|
db.OutboxEvents.Add(CreateRunEvent("run.updated", run));
|
|
await db.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task UpdateProjectionAsync(
|
|
OpenClawRun run,
|
|
OpenClawRunHistory? history = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
run.UpdatedAt = DateTimeOffset.UtcNow;
|
|
run.Revision++;
|
|
db.OpenClawRuns.Update(run);
|
|
if (history is not null)
|
|
db.OpenClawRunHistory.Add(history);
|
|
db.OutboxEvents.Add(CreateRunEvent("run.updated", run));
|
|
await db.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
private static OutboxEvent CreateRunEvent(
|
|
string type,
|
|
OpenClawRun run,
|
|
Guid? sourceRunId = null)
|
|
=> new()
|
|
{
|
|
Type = type,
|
|
AggregateType = "run",
|
|
AggregateId = run.Id.ToString(),
|
|
AggregateRevision = run.Revision,
|
|
PayloadJson = JsonSerializer.Serialize(new
|
|
{
|
|
status = run.Status,
|
|
taskId = run.TaskId,
|
|
projectId = run.ProjectId,
|
|
agentId = run.AgentId,
|
|
sourceRunId
|
|
})
|
|
};
|
|
|
|
}
|