Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions src/AgentOs.Domain/Preview/PreviewRecipe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,11 @@ public sealed record PreviewRecipe(string Command, Uri Url, int ReadyTimeoutSeco
if (!IsLocal(url)) { error = "The url must be on this computer (localhost)."; return null; }

var timeout = DefaultReadyTimeoutSeconds;
if (root.TryGetProperty("readyTimeoutSeconds", out var t))
if (root.TryGetProperty("readyTimeoutSeconds", out var t)
&& (t.ValueKind != JsonValueKind.Number || !t.TryGetInt32(out timeout) || timeout is < 10 or > 3600))
{
if (t.ValueKind != JsonValueKind.Number || !t.TryGetInt32(out timeout) || timeout is < 10 or > 3600)
{
error = "\"readyTimeoutSeconds\" must be a whole number from 10 to 3600.";
return null;
}
error = "\"readyTimeoutSeconds\" must be a whole number from 10 to 3600.";
return null;
}
return new PreviewRecipe(command, url, timeout);
}
Expand Down
5 changes: 3 additions & 2 deletions src/AgentOs.Domain/Workspaces/TicketDependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,12 @@ public static IReadOnlyList<int> CreationOrder(int count, Func<int, IReadOnlyLis
placed[i] = true;
foreach (var j in edges[i])
{
if (--indegree[j] == 0) { ready.Enqueue(j, j); }
indegree[j]--;
if (indegree[j] == 0) { ready.Enqueue(j, j); }
}
}
// Whatever is left sits on a cycle: append in index order (their cyclic edges are then ignored).
for (var i = 0; i < count; i++) { if (!placed[i]) { order.Add(i); } }
order.AddRange(Enumerable.Range(0, count).Where(i => !placed[i]));
return order;
}

Expand Down
11 changes: 10 additions & 1 deletion src/AgentOs.RemoteAgent/PreviewHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public static void KillLeftovers(string root)
}
finally
{
try { File.Delete(pidFile); } catch (IOException) { } catch (UnauthorizedAccessException) { }
TryDeleteFile(pidFile);
}
}
}
Expand Down Expand Up @@ -313,6 +313,15 @@ private static async Task<bool> ConnectsAsync(System.Net.IPAddress address, int
}
}

private static void TryDeleteFile(string path)
{
try { File.Delete(path); }
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
_ = ex; // still held by a process — the next sweep retries
}
}

private static void WritePidFile(string dir, Process process)
{
try
Expand Down
5 changes: 5 additions & 0 deletions src/AgentOs.Web/Services/Orchestration/AutopilotPlanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public static class AutopilotPlanner
/// (seen on the lab: the second one found the ports taken and could not check its end-to-end criteria).</summary>
public const int MaxParallelTests = 1;

/// <summary>What Autopilot does next on a board, given its tickets and tasks — a pure decision, executed by the
/// orchestrator.</summary>
/// <param name="mode">Off, Build, or BuildAndMerge.</param>
/// <param name="tickets">The board's tickets.</param>
/// <param name="tasks">The board's open tasks and their proposed changes.</param>
/// <param name="canTest">A computer of the member is connected, so the Testing agent can run. Without one nothing
/// is tested — and nothing untested is merged.</param>
public static IReadOnlyList<AutopilotAction> Decide(AutopilotMode mode, IReadOnlyList<PlanTicket> tickets, IReadOnlyList<PlanTask> tasks,
Expand Down
5 changes: 3 additions & 2 deletions src/AgentOs.Web/Services/Orchestration/ProjectOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
}

/// <summary>One orchestration pass over a board.</summary>
public sealed partial class ProjectOrchestrator(

Check warning on line 107 in src/AgentOs.Web/Services/Orchestration/ProjectOrchestrator.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Type 'ProjectOrchestrator' owns disposable field(s) '_oneAtATime' but is not disposable (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1001)
IServiceScopeFactory scopes,
BoardTaskService tasks,
TaskStopRegistry stops,
Expand Down Expand Up @@ -355,13 +355,14 @@

private async Task<IReadOnlyList<int>> DependenciesAsync(ISourceProvider? provider, WorkspaceEntity board, string tenant, string pat, BoardTicket t, CancellationToken ct)
{
var key = (board.Id, t.Number!.Value);
if (t.Number is not { } number) { return []; }
var key = (board.Id, number);
if (_deps.TryGetValue(key, out var cached) && DateTimeOffset.UtcNow - cached.At < TimeSpan.FromMinutes(10)) { return cached.Deps; }
if (provider is null) { return []; }
string? body;
try
{
body = await provider.ReadTicketBodyAsync(BoardTaskService.RepoDescriptor(board, tenant, t.RepoOwner!, t.RepoName!, "main", pat), t.Number.Value, ct).ConfigureAwait(false);
body = await provider.ReadTicketBodyAsync(BoardTaskService.RepoDescriptor(board, tenant, t.RepoOwner!, t.RepoName!, "main", pat), number, ct).ConfigureAwait(false);
}
catch (Exception ex) when (ex is System.Net.Http.HttpRequestException or Octokit.ApiException or InvalidOperationException or TaskCanceledException)
{
Expand Down
22 changes: 12 additions & 10 deletions tests/AgentOs.Tests/Sessions/KeepAwakeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ public class KeepAwakeTests
public void Hold_CountsWorkInFlight_AndReleasesOnce()
{
using var awake = new KeepAwake(enabled: true);
var first = awake.Hold();
var second = awake.Hold();
awake.Active.ShouldBe(2);

first.Dispose();
first.Dispose(); // a double release must not under-count
awake.Active.ShouldBe(1);

second.Dispose();
awake.Active.ShouldBe(0);
using (var first = awake.Hold())
{
using (awake.Hold())
{
awake.Active.ShouldBe(2);
first.Dispose();
first.Dispose(); // a double release must not under-count
awake.Active.ShouldBe(1);
}
awake.Active.ShouldBe(0);
}
awake.Active.ShouldBe(0); // the outer using's second release is a no-op too
}
}
4 changes: 2 additions & 2 deletions tests/AgentOs.Tests/Sessions/TryTheAppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public void StopPreview_PushesStopToTheRunningConnection()
public void RunnerDisconnect_ReportsItsPreviewsStopped()
{
var broker = new InProcessRemoteAgentBroker();
var registration = broker.RegisterRunner("conn-a", new RunnerConnection(Guid.NewGuid(), "t1", "u1"));
using var registration = broker.RegisterRunner("conn-a", new RunnerConnection(Guid.NewGuid(), "t1", "u1"));
var reports = new List<PreviewStatus>();
broker.PreviewReported += reports.Add;
broker.StartPreview(Start(), new RunnerTarget("t1", "u1"));
Expand Down Expand Up @@ -218,7 +218,7 @@ public void ShellFor_RunsTheLineInTheCheckout()
[Fact]
public async Task IsListening_TrueOnlyWhileSomethingListens()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
using var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
try
Expand Down
Loading