feat: ship agent-first mission control v0.2.57
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

This commit is contained in:
AzuTear
2026-07-31 22:39:47 +02:00
parent 3bc7622977
commit f5552218bc
535 changed files with 95242 additions and 8791 deletions
@@ -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
})
};
}