fix(watcher): fix TickCount wrap and resume retry on API failure

- Use 32-bit Environment.TickCount with unchecked uint arithmetic so
  GetIdleTime stays correct after the ~49.7 day uint wrap boundary
- Only clear _pausedTaskId after successful resume, not before the API call
- Add retry path: if resume failed, retry on next poll cycle while user
  is still active

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 00:18:22 -05:00
parent 74cd0f0018
commit 5db92d5127
2 changed files with 16 additions and 5 deletions

View File

@@ -29,6 +29,7 @@ internal static partial class NativeMethods
var info = new LASTINPUTINFO { cbSize = (uint)Marshal.SizeOf<LASTINPUTINFO>() };
if (!GetLastInputInfo(ref info))
return TimeSpan.Zero;
return TimeSpan.FromMilliseconds(Environment.TickCount64 - info.dwTime);
// Use 32-bit TickCount so both values wrap at the same boundary as dwTime (uint)
return TimeSpan.FromMilliseconds(unchecked((uint)Environment.TickCount - info.dwTime));
}
}

View File

@@ -73,7 +73,12 @@ public class Worker(
// Idle detection
var idleTime = NativeMethods.GetIdleTime();
if (!_isIdle && idleTime.TotalMilliseconds >= config.IdleTimeoutMs)
if (!_isIdle && _pausedTaskId is not null && idleTime.TotalMilliseconds < config.IdleTimeoutMs)
{
// Retry resume from a previous failed attempt
await ResumeIdlePausedTaskAsync(ct: stoppingToken);
}
else if (!_isIdle && idleTime.TotalMilliseconds >= config.IdleTimeoutMs)
{
_isIdle = true;
logger.LogInformation("User idle for {IdleTime}, pausing active task", idleTime);
@@ -167,7 +172,6 @@ public class Worker(
return;
var taskId = _pausedTaskId.Value;
_pausedTaskId = null;
try
{
@@ -180,6 +184,7 @@ public class Worker(
if (response?.Data is null || response.Data.Status != "Paused")
{
logger.LogDebug("Task {TaskId} is no longer paused, skipping auto-resume", taskId);
_pausedTaskId = null;
return;
}
@@ -189,13 +194,18 @@ public class Worker(
new { note = "Auto-resumed: user returned" }, ct);
if (resumeResponse.IsSuccessStatusCode)
{
logger.LogInformation("Auto-resumed task {TaskId}", taskId);
_pausedTaskId = null;
}
else
logger.LogWarning("Failed to resume task {TaskId}: {Status}", taskId, resumeResponse.StatusCode);
{
logger.LogWarning("Failed to resume task {TaskId}: {Status}, will retry", taskId, resumeResponse.StatusCode);
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to resume task {TaskId} after idle", taskId);
logger.LogWarning(ex, "Failed to resume task {TaskId} after idle, will retry", taskId);
}
}
}