feat(worker): add Prime scheduler abstractions + runner
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
src/ClaudeDo.Worker/Prime/IPrimeClock.cs
Normal file
2
src/ClaudeDo.Worker/Prime/IPrimeClock.cs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
public interface IPrimeClock { DateTimeOffset Now { get; } }
|
||||||
8
src/ClaudeDo.Worker/Prime/IPrimeRunner.cs
Normal file
8
src/ClaudeDo.Worker/Prime/IPrimeRunner.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
public interface IPrimeRunner
|
||||||
|
{
|
||||||
|
Task<PrimeRunOutcome> FireAsync(PrimeScheduleDto schedule, CancellationToken ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record PrimeRunOutcome(bool Success, string Message);
|
||||||
6
src/ClaudeDo.Worker/Prime/IPrimeScheduleSignal.cs
Normal file
6
src/ClaudeDo.Worker/Prime/IPrimeScheduleSignal.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
public interface IPrimeScheduleSignal
|
||||||
|
{
|
||||||
|
void Signal();
|
||||||
|
CancellationToken CurrentToken { get; }
|
||||||
|
}
|
||||||
5
src/ClaudeDo.Worker/Prime/PrimeClock.cs
Normal file
5
src/ClaudeDo.Worker/Prime/PrimeClock.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
public sealed class PrimeClock : IPrimeClock
|
||||||
|
{
|
||||||
|
public DateTimeOffset Now => DateTimeOffset.Now;
|
||||||
|
}
|
||||||
53
src/ClaudeDo.Worker/Prime/PrimeRunner.cs
Normal file
53
src/ClaudeDo.Worker/Prime/PrimeRunner.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using ClaudeDo.Data;
|
||||||
|
using ClaudeDo.Worker.Runner;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
public sealed class PrimeRunner : IPrimeRunner
|
||||||
|
{
|
||||||
|
private static readonly TimeSpan FireTimeout = TimeSpan.FromSeconds(60);
|
||||||
|
private readonly IClaudeProcess _claude;
|
||||||
|
private readonly ILogger<PrimeRunner> _logger;
|
||||||
|
|
||||||
|
public PrimeRunner(IClaudeProcess claude, ILogger<PrimeRunner> logger)
|
||||||
|
{
|
||||||
|
_claude = claude;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PrimeRunOutcome> FireAsync(PrimeScheduleDto schedule, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var cwd = Paths.AppDataRoot();
|
||||||
|
Directory.CreateDirectory(cwd);
|
||||||
|
|
||||||
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
timeoutCts.CancelAfter(FireTimeout);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var prompt = schedule.PromptOverride ?? "ping";
|
||||||
|
var result = await _claude.RunAsync(
|
||||||
|
arguments: "-p --max-turns 1",
|
||||||
|
prompt: prompt,
|
||||||
|
workingDirectory: cwd,
|
||||||
|
onStdoutLine: _ => Task.CompletedTask,
|
||||||
|
ct: timeoutCts.Token);
|
||||||
|
|
||||||
|
if (IsSuccess(result))
|
||||||
|
return new PrimeRunOutcome(true, "Primed Claude");
|
||||||
|
return new PrimeRunOutcome(false, FailureMessage(result));
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested && !ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return new PrimeRunOutcome(false, $"timed out after {FireTimeout.TotalSeconds:0}s");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Prime fire failed");
|
||||||
|
return new PrimeRunOutcome(false, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSuccess(RunResult result) => result.IsSuccess;
|
||||||
|
private static string FailureMessage(RunResult result) => $"exit code {result.ExitCode}";
|
||||||
|
}
|
||||||
29
src/ClaudeDo.Worker/Prime/PrimeScheduleSignal.cs
Normal file
29
src/ClaudeDo.Worker/Prime/PrimeScheduleSignal.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
public sealed class PrimeScheduleSignal : IPrimeScheduleSignal, IDisposable
|
||||||
|
{
|
||||||
|
private CancellationTokenSource _cts = new();
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
public CancellationToken CurrentToken
|
||||||
|
{
|
||||||
|
get { lock (_lock) return _cts.Token; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Signal()
|
||||||
|
{
|
||||||
|
CancellationTokenSource old;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
old = _cts;
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
}
|
||||||
|
try { old.Cancel(); } catch { /* already cancelled */ }
|
||||||
|
old.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (_lock) _cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/ClaudeDo.Worker/Prime/PrimeSchedulerOptions.cs
Normal file
7
src/ClaudeDo.Worker/Prime/PrimeSchedulerOptions.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace ClaudeDo.Worker.Prime;
|
||||||
|
|
||||||
|
public sealed record PrimeSchedulerOptions(TimeSpan CatchUpWindow)
|
||||||
|
{
|
||||||
|
public static PrimeSchedulerOptions Default { get; } =
|
||||||
|
new(TimeSpan.FromMinutes(30));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user