feat: ship agent-first mission control v0.2.57
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Nexus.Api.Controllers;
|
||||
using Nexus.Api.Data;
|
||||
using Nexus.Api.Models;
|
||||
using Nexus.Api.Repositories;
|
||||
using Nexus.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Nexus.Api.Tests;
|
||||
|
||||
public sealed class OpenClawRunServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ListCursor_DoesNotSkipOrDuplicateRunsWithTheSameCreatedAt()
|
||||
{
|
||||
await using var harness = await RunHarness.CreateAsync();
|
||||
var timestamp = new DateTimeOffset(
|
||||
2026,
|
||||
7,
|
||||
31,
|
||||
12,
|
||||
0,
|
||||
0,
|
||||
TimeSpan.Zero);
|
||||
var runs = Enumerable.Range(0, 3)
|
||||
.Select(index => new OpenClawRun
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Title = $"Run {index}",
|
||||
Prompt = $"Inspect {index}",
|
||||
AgentId = "iris",
|
||||
SessionKey = "agent:iris:main",
|
||||
Status = OpenClawRunStates.Completed,
|
||||
StartIdempotencyKey = $"list-cursor-{index}",
|
||||
CorrelationId = $"correlation-{index}",
|
||||
Actor = "owner",
|
||||
CreatedAt = timestamp,
|
||||
UpdatedAt = timestamp
|
||||
})
|
||||
.ToList();
|
||||
harness.Db.OpenClawRuns.AddRange(runs);
|
||||
await harness.Db.SaveChangesAsync();
|
||||
harness.Db.ChangeTracker.Clear();
|
||||
|
||||
var first = await harness.Service.GetAsync(new OpenClawRunQuery(Limit: 2));
|
||||
var second = await harness.Service.GetAsync(
|
||||
new OpenClawRunQuery(Limit: 2, Cursor: first.NextCursor));
|
||||
|
||||
Assert.Equal(2, first.Items.Count);
|
||||
Assert.NotNull(first.NextCursor);
|
||||
Assert.Matches("^[A-Za-z0-9_-]+$", first.NextCursor);
|
||||
Assert.True(
|
||||
OpenClawRunCursorCodec.TryDecode(
|
||||
first.NextCursor,
|
||||
out var cursorPosition));
|
||||
Assert.Equal(first.Items[^1].CreatedAt, cursorPosition.CreatedAt);
|
||||
Assert.Equal(first.Items[^1].Id, cursorPosition.Id);
|
||||
Assert.Single(second.Items);
|
||||
Assert.Null(second.NextCursor);
|
||||
Assert.Equal(
|
||||
3,
|
||||
first.Items
|
||||
.Concat(second.Items)
|
||||
.Select(item => item.Id)
|
||||
.Distinct()
|
||||
.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListCursor_AcceptsLegacyUtcTicksCursor()
|
||||
{
|
||||
await using var harness = await RunHarness.CreateAsync();
|
||||
var boundary = new DateTimeOffset(
|
||||
2026,
|
||||
7,
|
||||
31,
|
||||
12,
|
||||
0,
|
||||
0,
|
||||
TimeSpan.Zero);
|
||||
harness.Db.OpenClawRuns.AddRange(
|
||||
CreateListedRun("newer", boundary.AddMinutes(1)),
|
||||
CreateListedRun("older", boundary.AddMinutes(-1)));
|
||||
await harness.Db.SaveChangesAsync();
|
||||
harness.Db.ChangeTracker.Clear();
|
||||
|
||||
var page = await harness.Service.GetAsync(
|
||||
new OpenClawRunQuery(
|
||||
Limit: 10,
|
||||
Cursor: boundary.UtcTicks.ToString(
|
||||
System.Globalization.CultureInfo.InvariantCulture)));
|
||||
|
||||
var run = Assert.Single(page.Items);
|
||||
Assert.Equal("older", run.Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Start_PersistsCorrelations_AndReplaysIdempotently()
|
||||
{
|
||||
await using var harness = await RunHarness.CreateAsync();
|
||||
var project = new Project { Name = "Nexus" };
|
||||
var task = new WorkTask { Title = "Connect OpenClaw", ProjectId = project.Id };
|
||||
harness.Db.Projects.Add(project);
|
||||
harness.Db.Tasks.Add(task);
|
||||
await harness.Db.SaveChangesAsync();
|
||||
harness.Gateway.StartResults.Enqueue(new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
OpenClawRunStates.Running,
|
||||
"started",
|
||||
"oc-run-1"));
|
||||
var request = new StartOpenClawRunRequest(
|
||||
"Inspect the deployment",
|
||||
"iris",
|
||||
"agent:iris:main",
|
||||
"Deployment inspection",
|
||||
task.Id,
|
||||
project.Id);
|
||||
var invocation = Invocation("start-key", "corr-1", "owner-1");
|
||||
|
||||
var first = await harness.Service.StartAsync(request, invocation);
|
||||
var replay = await harness.Service.StartAsync(request, invocation);
|
||||
|
||||
Assert.True(first.Ok);
|
||||
Assert.Equal("oc-run-1", first.Run.OpenClawRunId);
|
||||
Assert.Equal(task.Id, first.Run.TaskId);
|
||||
Assert.Equal(project.Id, first.Run.ProjectId);
|
||||
Assert.Equal("corr-1", first.Run.CorrelationId);
|
||||
Assert.Equal("owner-1", first.Run.Actor);
|
||||
Assert.Equal("idempotent_replay", replay.State);
|
||||
Assert.Single(harness.Gateway.StartCalls);
|
||||
|
||||
var persisted = await harness.Db.OpenClawRuns.SingleAsync();
|
||||
Assert.Equal(OpenClawRunStates.Running, persisted.Status);
|
||||
Assert.Equal(2, await harness.Db.OpenClawRunHistory.CountAsync());
|
||||
Assert.All(
|
||||
await harness.Db.OpenClawRunHistory.ToListAsync(),
|
||||
item => Assert.Equal("corr-1", item.CorrelationId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Stop_UsesExactRunAndDoesNotRepeatGatewayMutation()
|
||||
{
|
||||
await using var harness = await RunHarness.CreateAsync();
|
||||
harness.Gateway.StartResults.Enqueue(new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
OpenClawRunStates.Running,
|
||||
"started",
|
||||
"oc-run-stop"));
|
||||
var started = await harness.Service.StartAsync(
|
||||
Request(),
|
||||
Invocation("start-stop", "corr-stop", "owner"));
|
||||
harness.Gateway.StopResults.Enqueue(new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
OpenClawRunStates.Stopped,
|
||||
"stopped",
|
||||
"oc-run-stop"));
|
||||
var stopInvocation = Invocation("stop-key", "corr-stop-2", "owner");
|
||||
|
||||
var stopped = await harness.Service.StopAsync(
|
||||
started.Run.Id,
|
||||
"operator request",
|
||||
stopInvocation);
|
||||
var replay = await harness.Service.StopAsync(
|
||||
started.Run.Id,
|
||||
"operator request",
|
||||
stopInvocation);
|
||||
|
||||
Assert.NotNull(stopped);
|
||||
Assert.True(stopped!.Ok);
|
||||
Assert.Equal(OpenClawRunStates.Stopped, stopped.Run.Status);
|
||||
Assert.Equal("idempotent_replay", replay?.State);
|
||||
Assert.Single(harness.Gateway.StopCalls);
|
||||
Assert.Contains(
|
||||
await harness.Db.OpenClawRunHistory.ToListAsync(),
|
||||
item => item.Action == "stop_requested"
|
||||
&& item.IdempotencyKey == "stop-key"
|
||||
&& item.Actor == "owner");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Retry_CreatesCorrelatedChild_WhileResumeIsTruthfullyUnsupported()
|
||||
{
|
||||
await using var harness = await RunHarness.CreateAsync();
|
||||
harness.Gateway.StartResults.Enqueue(new OpenClawRunGatewayResult(
|
||||
true,
|
||||
false,
|
||||
OpenClawRunStates.Failed,
|
||||
"provider failed"));
|
||||
var source = await harness.Service.StartAsync(
|
||||
Request(),
|
||||
Invocation("start-failed", "corr-source", "owner"));
|
||||
harness.Gateway.StartResults.Enqueue(new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
OpenClawRunStates.Running,
|
||||
"retry started",
|
||||
"oc-run-retry"));
|
||||
|
||||
var resume = await harness.Service.ResumeAsync(
|
||||
source.Run.Id,
|
||||
null,
|
||||
Invocation("resume-key", "corr-resume", "owner"));
|
||||
var retry = await harness.Service.RetryAsync(
|
||||
source.Run.Id,
|
||||
"retry after provider recovery",
|
||||
Invocation("retry-key", "corr-retry", "owner"));
|
||||
|
||||
Assert.NotNull(resume);
|
||||
Assert.False(resume!.Ok);
|
||||
Assert.Equal(OpenClawRunStates.Unsupported, resume.State);
|
||||
Assert.False(resume.Run.CanResume);
|
||||
Assert.NotNull(retry?.ResultRun);
|
||||
Assert.True(retry!.Ok);
|
||||
Assert.Equal(source.Run.Id, retry.ResultRun!.RetriedFromRunId);
|
||||
Assert.Equal(source.Run.SessionKey, retry.ResultRun.SessionKey);
|
||||
Assert.Equal("corr-retry", retry.ResultRun.CorrelationId);
|
||||
Assert.Equal(2, harness.Gateway.StartCalls.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Reconcile_ProjectsTerminalState_AndPerRunSequenceGap()
|
||||
{
|
||||
await using var harness = await RunHarness.CreateAsync();
|
||||
harness.Gateway.StartResults.Enqueue(new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
OpenClawRunStates.Running,
|
||||
"started",
|
||||
"oc-run-event"));
|
||||
var started = await harness.Service.StartAsync(
|
||||
Request(),
|
||||
Invocation("start-event", "corr-event", "owner"));
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
await harness.Service.ReconcileAsync(ChatEvent("oc-run-event", "delta", 1, now));
|
||||
await harness.Service.ReconcileAsync(ChatEvent("oc-run-event", "final", 3, now.AddSeconds(1)));
|
||||
await harness.Service.ReconcileAsync(ChatEvent("oc-run-event", "final", 3, now.AddSeconds(1)));
|
||||
|
||||
harness.Db.ChangeTracker.Clear();
|
||||
var run = await harness.Db.OpenClawRuns.SingleAsync(item => item.Id == started.Run.Id);
|
||||
Assert.Equal(OpenClawRunStates.Completed, run.Status);
|
||||
Assert.Equal(3, run.LastGatewaySequence);
|
||||
Assert.True(run.SequenceGapDetected);
|
||||
Assert.NotNull(run.FinishedAt);
|
||||
var gatewayTransitions = await harness.Db.OpenClawRunHistory
|
||||
.Where(item => item.Action == "gateway_event")
|
||||
.ToListAsync();
|
||||
var gatewayTransition = Assert.Single(gatewayTransitions);
|
||||
Assert.True(gatewayTransition.SequenceGapDetected);
|
||||
Assert.Equal(OpenClawRunStates.Completed, gatewayTransition.ToStatus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Start_RejectsUnknownTaskCorrelationBeforeGatewayCall()
|
||||
{
|
||||
await using var harness = await RunHarness.CreateAsync();
|
||||
var request = Request() with { TaskId = Guid.NewGuid() };
|
||||
|
||||
var exception = await Assert.ThrowsAsync<OpenClawRunValidationException>(
|
||||
() => harness.Service.StartAsync(
|
||||
request,
|
||||
Invocation("unknown-task", "corr", "owner")));
|
||||
|
||||
Assert.Equal("taskId", exception.Field);
|
||||
Assert.Empty(harness.Gateway.StartCalls);
|
||||
Assert.Empty(await harness.Db.OpenClawRuns.ToListAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Start_ReplaysUnacknowledgedDispatchWithTheSameIdempotencyKey()
|
||||
{
|
||||
await using var harness = await RunHarness.CreateAsync();
|
||||
var request = Request();
|
||||
var pending = new OpenClawRun
|
||||
{
|
||||
Title = request.Title!,
|
||||
Prompt = request.Prompt,
|
||||
AgentId = request.AgentId,
|
||||
SessionKey = request.SessionKey,
|
||||
Status = OpenClawRunStates.Dispatching,
|
||||
StartIdempotencyKey = "recover-key",
|
||||
CorrelationId = "original-correlation",
|
||||
Actor = "owner"
|
||||
};
|
||||
harness.Db.OpenClawRuns.Add(pending);
|
||||
await harness.Db.SaveChangesAsync();
|
||||
harness.Db.ChangeTracker.Clear();
|
||||
harness.Gateway.StartResults.Enqueue(new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
OpenClawRunStates.Running,
|
||||
"in flight",
|
||||
"oc-recovered"));
|
||||
|
||||
var result = await harness.Service.StartAsync(
|
||||
request,
|
||||
Invocation("recover-key", "recovery-correlation", "owner"));
|
||||
|
||||
Assert.True(result.Ok);
|
||||
Assert.Equal("oc-recovered", result.Run.OpenClawRunId);
|
||||
Assert.Single(harness.Gateway.StartCalls);
|
||||
var recovered = await harness.Db.OpenClawRuns.SingleAsync();
|
||||
Assert.Equal(OpenClawRunStates.Running, recovered.Status);
|
||||
Assert.Contains(
|
||||
await harness.Db.OpenClawRunHistory.ToListAsync(),
|
||||
item => item.Action == "start_recovery"
|
||||
&& item.CorrelationId == "recovery-correlation");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Controller_RequiresIdempotencyHeader_AndDerivesActorFromPrincipal()
|
||||
{
|
||||
var service = new CapturingRunService();
|
||||
var controller = new OpenClawRunsController(service);
|
||||
var context = new DefaultHttpContext();
|
||||
context.TraceIdentifier = "trace-http";
|
||||
context.User = new ClaimsPrincipal(
|
||||
new ClaimsIdentity(
|
||||
[new Claim("sub", "owner-subject"), new Claim(ClaimTypes.Role, "owner")],
|
||||
"test"));
|
||||
controller.ControllerContext = new ControllerContext { HttpContext = context };
|
||||
|
||||
var missingHeader = await controller.Start(Request(), CancellationToken.None);
|
||||
Assert.IsType<BadRequestObjectResult>(missingHeader.Result);
|
||||
|
||||
context.Request.Headers["Idempotency-Key"] = "controller-key";
|
||||
context.Request.Headers["X-Correlation-ID"] = "controller-correlation";
|
||||
var accepted = await controller.Start(Request(), CancellationToken.None);
|
||||
|
||||
Assert.IsType<CreatedAtRouteResult>(accepted.Result);
|
||||
Assert.Equal("owner-subject", service.Invocation?.Actor);
|
||||
Assert.Equal("controller-key", service.Invocation?.IdempotencyKey);
|
||||
Assert.Equal("controller-correlation", service.Invocation?.CorrelationId);
|
||||
|
||||
context.Request.Headers["traceparent"] = "not-a-w3c-traceparent";
|
||||
var invalidTrace = await controller.Start(Request(), CancellationToken.None);
|
||||
Assert.IsType<BadRequestObjectResult>(invalidTrace.Result);
|
||||
|
||||
context.Request.Headers.Remove("traceparent");
|
||||
service.StartOk = false;
|
||||
var durablyBlocked = await controller.Start(Request(), CancellationToken.None);
|
||||
Assert.IsType<AcceptedAtRouteResult>(durablyBlocked.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Controller_MutationsRequireOwnerRole()
|
||||
{
|
||||
var classAuthorization = typeof(OpenClawRunsController)
|
||||
.GetCustomAttributes(typeof(AuthorizeAttribute), inherit: true)
|
||||
.Cast<AuthorizeAttribute>()
|
||||
.Single(attribute => string.IsNullOrWhiteSpace(attribute.Roles));
|
||||
var startAuthorization = typeof(OpenClawRunsController)
|
||||
.GetMethod(nameof(OpenClawRunsController.Start))!
|
||||
.GetCustomAttributes(typeof(AuthorizeAttribute), inherit: true)
|
||||
.Cast<AuthorizeAttribute>()
|
||||
.Single();
|
||||
|
||||
Assert.NotNull(classAuthorization);
|
||||
Assert.Equal("owner", startAuthorization.Roles);
|
||||
}
|
||||
|
||||
private static StartOpenClawRunRequest Request()
|
||||
=> new(
|
||||
"Inspect and report the current state.",
|
||||
"iris",
|
||||
"agent:iris:main",
|
||||
"Inspect state");
|
||||
|
||||
private static OpenClawRun CreateListedRun(
|
||||
string title,
|
||||
DateTimeOffset createdAt)
|
||||
=> new()
|
||||
{
|
||||
Title = title,
|
||||
Prompt = $"Inspect {title}",
|
||||
AgentId = "iris",
|
||||
SessionKey = "agent:iris:main",
|
||||
Status = OpenClawRunStates.Completed,
|
||||
StartIdempotencyKey = $"list-{title}",
|
||||
CorrelationId = $"correlation-{title}",
|
||||
Actor = "owner",
|
||||
CreatedAt = createdAt,
|
||||
UpdatedAt = createdAt
|
||||
};
|
||||
|
||||
private static OpenClawInvocationMetadata Invocation(
|
||||
string idempotencyKey,
|
||||
string correlationId,
|
||||
string actor)
|
||||
=> new(
|
||||
idempotencyKey,
|
||||
correlationId,
|
||||
actor,
|
||||
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
|
||||
|
||||
private static GatewayEventEnvelope ChatEvent(
|
||||
string runId,
|
||||
string state,
|
||||
long sequence,
|
||||
DateTimeOffset receivedAt)
|
||||
=> new(
|
||||
"chat",
|
||||
new JsonObject
|
||||
{
|
||||
["runId"] = runId,
|
||||
["sessionKey"] = "agent:iris:main",
|
||||
["state"] = state,
|
||||
["seq"] = sequence
|
||||
},
|
||||
sequence,
|
||||
sequence,
|
||||
receivedAt);
|
||||
|
||||
private sealed class RunHarness : IAsyncDisposable
|
||||
{
|
||||
private RunHarness(
|
||||
NexusDbContext db,
|
||||
FakeRunGateway gateway,
|
||||
OpenClawRunService service)
|
||||
{
|
||||
Db = db;
|
||||
Gateway = gateway;
|
||||
Service = service;
|
||||
}
|
||||
|
||||
public NexusDbContext Db { get; }
|
||||
public FakeRunGateway Gateway { get; }
|
||||
public OpenClawRunService Service { get; }
|
||||
|
||||
public static async Task<RunHarness> CreateAsync()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<NexusDbContext>()
|
||||
.UseInMemoryDatabase($"openclaw-runs-{Guid.NewGuid():N}")
|
||||
.Options;
|
||||
var db = new NexusDbContext(options);
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
var gateway = new FakeRunGateway();
|
||||
var repository = new OpenClawRunRepository(db);
|
||||
return new RunHarness(db, gateway, new OpenClawRunService(repository, gateway));
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() => Db.DisposeAsync();
|
||||
}
|
||||
|
||||
private sealed class FakeRunGateway : IOpenClawRunGateway
|
||||
{
|
||||
public Queue<OpenClawRunGatewayResult> StartResults { get; } = new();
|
||||
public Queue<OpenClawRunGatewayResult> StopResults { get; } = new();
|
||||
public List<(Guid RunId, OpenClawInvocationMetadata Invocation)> StartCalls { get; } = [];
|
||||
public List<(Guid RunId, OpenClawInvocationMetadata Invocation)> StopCalls { get; } = [];
|
||||
|
||||
public Task<OpenClawRunGatewayResult> StartAsync(
|
||||
OpenClawRun run,
|
||||
OpenClawInvocationMetadata invocation,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
StartCalls.Add((run.Id, invocation));
|
||||
return Task.FromResult(StartResults.Count > 0
|
||||
? StartResults.Dequeue()
|
||||
: new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
OpenClawRunStates.Running,
|
||||
"started",
|
||||
$"oc-{run.Id:N}"));
|
||||
}
|
||||
|
||||
public Task<OpenClawRunGatewayResult> StopAsync(
|
||||
OpenClawRun run,
|
||||
OpenClawInvocationMetadata invocation,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
StopCalls.Add((run.Id, invocation));
|
||||
return Task.FromResult(StopResults.Count > 0
|
||||
? StopResults.Dequeue()
|
||||
: new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
OpenClawRunStates.Stopped,
|
||||
"stopped",
|
||||
run.OpenClawRunId));
|
||||
}
|
||||
|
||||
public Task<OpenClawRunGatewayResult> GetHistoryAsync(
|
||||
OpenClawRun run,
|
||||
int limit,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new OpenClawRunGatewayResult(
|
||||
true,
|
||||
true,
|
||||
"available",
|
||||
"history",
|
||||
run.OpenClawRunId,
|
||||
new JsonObject { ["messages"] = new JsonArray() }));
|
||||
}
|
||||
|
||||
private sealed class CapturingRunService : IOpenClawRunService
|
||||
{
|
||||
public OpenClawInvocationMetadata? Invocation { get; private set; }
|
||||
public bool StartOk { get; set; } = true;
|
||||
|
||||
public Task<OpenClawRunOperationDto> StartAsync(
|
||||
StartOpenClawRunRequest request,
|
||||
OpenClawInvocationMetadata invocation,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Invocation = invocation;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var run = new OpenClawRunDto(
|
||||
Guid.NewGuid(),
|
||||
request.Title ?? "Run",
|
||||
request.Prompt,
|
||||
request.AgentId,
|
||||
request.SessionKey,
|
||||
StartOk ? OpenClawRunStates.Running : OpenClawRunStates.Blocked,
|
||||
request.TaskId,
|
||||
request.ProjectId,
|
||||
"oc-run",
|
||||
null,
|
||||
invocation.CorrelationId,
|
||||
invocation.Actor,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
StartOk,
|
||||
!StartOk,
|
||||
false,
|
||||
"unsupported",
|
||||
now,
|
||||
now,
|
||||
now,
|
||||
null);
|
||||
return Task.FromResult(new OpenClawRunOperationDto(
|
||||
StartOk,
|
||||
StartOk ? OpenClawRunStates.Running : OpenClawRunStates.Blocked,
|
||||
StartOk ? "started" : "gateway disconnected",
|
||||
run,
|
||||
null,
|
||||
now));
|
||||
}
|
||||
|
||||
public Task<OpenClawRunCollectionDto> GetAsync(
|
||||
OpenClawRunQuery query,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> throw new NotSupportedException();
|
||||
public Task<OpenClawRunDto?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
=> throw new NotSupportedException();
|
||||
public Task<OpenClawRunOperationDto?> StopAsync(
|
||||
Guid id,
|
||||
string? reason,
|
||||
OpenClawInvocationMetadata invocation,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> throw new NotSupportedException();
|
||||
public Task<OpenClawRunOperationDto?> ResumeAsync(
|
||||
Guid id,
|
||||
string? reason,
|
||||
OpenClawInvocationMetadata invocation,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> throw new NotSupportedException();
|
||||
public Task<OpenClawRunOperationDto?> RetryAsync(
|
||||
Guid id,
|
||||
string? reason,
|
||||
OpenClawInvocationMetadata invocation,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> throw new NotSupportedException();
|
||||
public Task<OpenClawRunHistoryResponse?> GetHistoryAsync(
|
||||
Guid id,
|
||||
int gatewayLimit = 200,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> throw new NotSupportedException();
|
||||
public Task ReconcileAsync(
|
||||
GatewayEventEnvelope gatewayEvent,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user