feat: ship agent-first mission control v0.2.57
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
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
|
||||
})
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user