Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8fe599d
Rewrite app around agent sessions
KSemenenko Mar 14, 2026
454dde9
agentees
KSemenenko Mar 14, 2026
b3e67a5
Persist local agent history and grain state
KSemenenko Mar 14, 2026
842ad57
Fix chat composer shortcuts and UI lag
KSemenenko Mar 14, 2026
7aa916b
fixes
KSemenenko Mar 14, 2026
4922523
Refine MVUX screens and repair agent builder
KSemenenko Mar 14, 2026
384e30d
Move app config into DotPilot app project
KSemenenko Mar 15, 2026
c90d04e
ui updates
KSemenenko Mar 15, 2026
ba8320c
more work
KSemenenko Mar 15, 2026
de5bea1
refactoring
KSemenenko Mar 15, 2026
c1f86ef
new structure
KSemenenko Mar 15, 2026
cb20a2a
remove trash
KSemenenko Mar 15, 2026
275c782
cleaning
KSemenenko Mar 15, 2026
780a468
fix buils
KSemenenko Mar 15, 2026
cdc2090
clean up
KSemenenko Mar 15, 2026
c2dc2b1
tests and fixes
KSemenenko Mar 15, 2026
3d06f1a
more fixes
KSemenenko Mar 15, 2026
4aa3442
Fix agent shell startup and chat flows
KSemenenko Mar 16, 2026
0e46ae0
Add real agent profile editing
KSemenenko Mar 16, 2026
d726e4a
Prevent desktop sleep during live sessions
KSemenenko Mar 16, 2026
f288004
Add fleet board live session monitor
KSemenenko Mar 16, 2026
e3faff0
code and page
KSemenenko Mar 16, 2026
4736732
fix: address PR 84 review comments
KSemenenko Mar 16, 2026
7760167
fix: address remaining PR 84 review threads
KSemenenko Mar 16, 2026
abfc231
fix: refresh fleet provider snapshot on reload
KSemenenko Mar 16, 2026
71498c5
fix: stabilize linux snap release packaging
KSemenenko Mar 16, 2026
86f0a89
fix: stabilize provider metadata and hydration startup
KSemenenko Mar 16, 2026
6bd5f26
fix: remove duplicate provider probes in tests
KSemenenko Mar 16, 2026
1be0ed4
fix: harden windows sqlite test cleanup
KSemenenko Mar 16, 2026
341e466
fix: clear sqlite pools before test cleanup
KSemenenko Mar 16, 2026
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
Prev Previous commit
Next Next commit
fix: address remaining PR 84 review threads
  • Loading branch information
KSemenenko committed Mar 16, 2026
commit 77601674f16c1e42e6ec25592651ea79d2217e18
28 changes: 28 additions & 0 deletions DotPilot.Tests/Chat/ViewModels/ChatModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,34 @@ public async Task RefreshIgnoresCancellationDuringWorkspaceProbe()
(await model.FeedbackMessage).Should().BeEmpty();
}

[Test]
public async Task RefreshInvalidatesTheFleetProviderSnapshotAfterProviderChanges()
{
using var commandScope = CodexCliTestScope.Create(nameof(ChatModelTests));
commandScope.WriteVersionCommand("codex", "codex version 1.0.0");
commandScope.WriteCodexMetadata("gpt-5.4", "gpt-5.4");
await using var fixture = await CreateFixtureAsync();
var model = ActivatorUtilities.CreateInstance<ChatModel>(fixture.Provider);

var initialBoard = await model.FleetBoard;
initialBoard.Should().NotBeNull();
initialBoard!.Providers.Should().Contain(provider =>
provider.DisplayName == "Codex" &&
provider.StatusLabel == "Disabled");

(await fixture.WorkspaceState.UpdateProviderAsync(
new UpdateProviderPreferenceCommand(AgentProviderKind.Codex, true),
CancellationToken.None)).ShouldSucceed();

await model.Refresh(CancellationToken.None);

var refreshedBoard = await model.FleetBoard;
refreshedBoard.Should().NotBeNull();
refreshedBoard!.Providers.Should().Contain(provider =>
provider.DisplayName == "Codex" &&
provider.StatusLabel == "Ready");
}

[Test]
public async Task FleetBoardShowsTheActiveSessionWhileStreamingAndClearsAfterCompletion()
{
Expand Down
99 changes: 99 additions & 0 deletions DotPilot.Tests/Host/Power/DesktopSleepPreventionServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Diagnostics;
using DotPilot.Core.ChatSessions;
using DotPilot.Core.ControlPlaneDomain;
using Microsoft.Extensions.DependencyInjection;

namespace DotPilot.Tests.Host.Power;

public sealed class DesktopSleepPreventionServiceTests
{
[Test]
public async Task ServiceTracksSessionActivityLifecycle()
{
if (OperatingSystem.IsLinux() && !CommandExists("systemd-inhibit"))
{
Assert.Ignore("systemd-inhibit is not available on this machine.");
}

if (OperatingSystem.IsMacOS() && !CommandExists("caffeinate"))
{
Assert.Ignore("caffeinate is not available on this machine.");
}

var services = new ServiceCollection();
services.AddLogging();
services.AddSingleton(TimeProvider.System);
services.AddAgentSessions(new AgentSessionStorageOptions
{
UseInMemoryDatabase = true,
InMemoryDatabaseName = Guid.NewGuid().ToString("N"),
});
services.AddSingleton<DesktopSleepPreventionService>();

await using var provider = services.BuildServiceProvider();
var monitor = provider.GetRequiredService<ISessionActivityMonitor>();
var sleepPrevention = provider.GetRequiredService<DesktopSleepPreventionService>();

using var lease = monitor.BeginActivity(
new SessionActivityDescriptor(
SessionId.New(),
"Sleep prevention session",
AgentProfileId.New(),
"Sleep agent",
"Debug Provider"));

await WaitForAsync(static service => service.IsSleepPreventionActive, sleepPrevention);

lease.Dispose();

await WaitForAsync(static service => !service.IsSleepPreventionActive, sleepPrevention);
}

private static async Task WaitForAsync(
Func<DesktopSleepPreventionService, bool> predicate,
DesktopSleepPreventionService service)
{
var timeoutAt = DateTimeOffset.UtcNow.AddSeconds(5);
while (DateTimeOffset.UtcNow < timeoutAt)
{
if (predicate(service))
{
return;
}

await Task.Delay(50);
}

predicate(service).Should().BeTrue();
}

private static bool CommandExists(string commandName)
{
try
{
var startInfo = new ProcessStartInfo
{
FileName = "sh",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
startInfo.ArgumentList.Add("-c");
startInfo.ArgumentList.Add($"command -v {commandName}");

using var process = Process.Start(startInfo);
if (process is null)
{
return false;
}

process.WaitForExit(2000);
return process.ExitCode == 0;
}
catch
{
return false;
}
}
}
96 changes: 77 additions & 19 deletions DotPilot/Host/Power/DesktopSleepPreventionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public sealed class DesktopSleepPreventionService : IDisposable
private readonly Lock gate = new();
private Process? inhibitorProcess;
private bool isSleepPreventionActive;
private bool isSleepPreventionPending;
private long stateVersion;

public DesktopSleepPreventionService(
ISessionActivityMonitor sessionActivityMonitor,
Expand Down Expand Up @@ -71,62 +73,68 @@ private void ApplySessionActivityState()

private void AcquireSleepPrevention()
{
lock (gate)
var acquisition = TryBeginAcquisition();
if (!acquisition.ShouldAcquire)
{
if (isSleepPreventionActive)
{
return;
}
return;
}

try
{
if (OperatingSystem.IsWindows())
{
AcquireWindowsSleepPrevention();
SetActiveState("SetThreadExecutionState");
CompleteWindowsAcquisition(acquisition.Version, "SetThreadExecutionState");
return;
}

if (OperatingSystem.IsMacOS())
{
SetProcessState(StartMacOsInhibitorProcess());
CompleteProcessAcquisition(acquisition.Version, StartMacOsInhibitorProcess());
return;
}

if (OperatingSystem.IsLinux())
{
SetProcessState(StartLinuxInhibitorProcess());
CompleteProcessAcquisition(acquisition.Version, StartLinuxInhibitorProcess());
return;
}

CancelPendingAcquisition(acquisition.Version);
}
catch (Exception exception)
{
ShellSleepPreventionLog.AcquireFailed(logger, exception);
CancelPendingAcquisition(acquisition.Version);
ReleaseSleepPrevention();
}
}

private void ReleaseSleepPrevention()
{
Process? processToStop;
bool shouldReleaseWindows;
bool wasActive;

lock (gate)
{
if (!isSleepPreventionActive && inhibitorProcess is null)
stateVersion++;
if (!isSleepPreventionActive && !isSleepPreventionPending && inhibitorProcess is null)
{
return;
}

if (OperatingSystem.IsWindows() && isSleepPreventionActive)
{
ReleaseWindowsSleepPrevention();
}

shouldReleaseWindows = OperatingSystem.IsWindows() && isSleepPreventionActive;
processToStop = inhibitorProcess;
inhibitorProcess = null;
wasActive = isSleepPreventionActive;
isSleepPreventionActive = false;
isSleepPreventionPending = false;
}

if (shouldReleaseWindows)
{
ReleaseWindowsSleepPrevention();
}

StopProcess(processToStop);
Expand All @@ -139,29 +147,79 @@ private void ReleaseSleepPrevention()
StateChanged?.Invoke(this, EventArgs.Empty);
}

private void SetProcessState(Process process)
private (bool ShouldAcquire, long Version) TryBeginAcquisition()
{
lock (gate)
{
if (isSleepPreventionActive || isSleepPreventionPending)
{
return (false, stateVersion);
}

stateVersion++;
isSleepPreventionPending = true;
return (true, stateVersion);
}
}

private void CompleteProcessAcquisition(long version, Process process)
{
var acquired = false;
lock (gate)
{
inhibitorProcess = process;
isSleepPreventionActive = true;
if (isSleepPreventionPending && !isSleepPreventionActive && version == stateVersion)
{
inhibitorProcess = process;
isSleepPreventionActive = true;
isSleepPreventionPending = false;
acquired = true;
}
}

if (!acquired)
{
StopProcess(process);
return;
}

ShellSleepPreventionLog.Acquired(logger, process.ProcessName);
StateChanged?.Invoke(this, EventArgs.Empty);
}

private void SetActiveState(string mechanism)
private void CompleteWindowsAcquisition(long version, string mechanism)
{
var acquired = false;
lock (gate)
{
isSleepPreventionActive = true;
if (isSleepPreventionPending && !isSleepPreventionActive && version == stateVersion)
{
isSleepPreventionActive = true;
isSleepPreventionPending = false;
acquired = true;
}
}

if (!acquired)
{
ReleaseWindowsSleepPrevention();
return;
}

ShellSleepPreventionLog.Acquired(logger, mechanism);
StateChanged?.Invoke(this, EventArgs.Empty);
}

private void CancelPendingAcquisition(long version)
{
lock (gate)
{
if (isSleepPreventionPending && !isSleepPreventionActive && version == stateVersion)
{
isSleepPreventionPending = false;
}
}
}

private static void AcquireWindowsSleepPrevention()
{
var result = SetThreadExecutionState(EsContinuous | EsSystemRequired);
Expand Down
1 change: 1 addition & 0 deletions DotPilot/Presentation/Chat/ViewModels/ChatModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public async ValueTask Refresh(CancellationToken cancellationToken)
return;
}

fleetProviderSnapshotStale = true;
_workspaceRefresh.Raise();
_sessionRefresh.Raise();
Comment thread
KSemenenko marked this conversation as resolved.
await EnsureSelectedChatAsync(cancellationToken);
Expand Down
Loading