feat(usage): PowerUse-Zeitfenster + Handoff-Schwellwert als Settings
Drei neue app_settings-Spalten (Migration AddPowerUseWindow): power_use_window_start/end (HH:mm, lokal, nullable) und power_use_handoff_pct (NOT NULL DEFAULT 95, geclampt 50-99). Die Fensterlogik liegt als reine Funktion in PowerUseWindow (Contains/Normalize/IsOpen): beide Grenzen leer oder nur eine gesetzt = rund um die Uhr, Ende vor Start wrappt ueber Mitternacht, Grenzen halboffen. Geprueft wird in ComposeHandlerPromptAsync, also pro Phase zum Zeitpunkt des Prompt-Baus - die Uhr ist per Func<DateTime> injizierbar. Bedienung im Usage-Monitor neben dem PowerUse-Toggle (Commit auf Blur/Enter, ein Write fuer alle vier Felder); in Settings->General unsichtbar, aber load->save durchgereicht. Nebenbefund, gleicher Pfad: AppSettingsRepository.UpdateAsync kopiert Feld fuer Feld und hat ModelPresets, PowerUseEnabled und ListHandlerMaxHandoffs nie geschrieben - jeder Save dieser drei war still wirkungslos. Mit ergaenzt und per Round-trip-Test gepinnt.
This commit is contained in:
@@ -45,6 +45,9 @@ public sealed class InteractiveLaunchSpecService
|
||||
private readonly string _claudePath;
|
||||
private readonly HandoffRoundTracker _handoffRounds;
|
||||
|
||||
// Local wall clock, injectable so the PowerUse time window is testable without sleeping.
|
||||
private readonly Func<DateTime> _now;
|
||||
|
||||
public InteractiveLaunchSpecService(
|
||||
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
||||
ISessionSkillSeeder skillSeeder,
|
||||
@@ -52,8 +55,10 @@ public sealed class InteractiveLaunchSpecService
|
||||
WorktreeManager wtManager,
|
||||
GitService git,
|
||||
WorkerConfig cfg,
|
||||
HandoffRoundTracker handoffRounds)
|
||||
HandoffRoundTracker handoffRounds,
|
||||
Func<DateTime>? now = null)
|
||||
{
|
||||
_now = now ?? (() => DateTime.Now);
|
||||
_dbFactory = dbFactory;
|
||||
_skillSeeder = skillSeeder;
|
||||
_skillRegistry = skillRegistry;
|
||||
@@ -580,7 +585,11 @@ public sealed class InteractiveLaunchSpecService
|
||||
// Appending rather than substituting a second prompt set keeps the shared 95% in one place --
|
||||
// the addendum is written to read as an override of the base prompt's interactive rules. A run
|
||||
// that is not in PowerUse gets a byte-identical prompt to what it got before PowerUse existed.
|
||||
private static async Task<string> ComposeHandlerPromptAsync(
|
||||
//
|
||||
// The optional time window is checked HERE, i.e. per phase at prompt-build time: a chain that
|
||||
// hands off out of the window drops the addendum from that point on instead of staying
|
||||
// unattended because the first phase happened to start inside it.
|
||||
private async Task<string> ComposeHandlerPromptAsync(
|
||||
ClaudeDoDbContext ctx, string listId, PromptKind kind, CancellationToken ct)
|
||||
{
|
||||
var basePrompt = PromptFiles.ReadOrDefault(kind);
|
||||
@@ -588,6 +597,9 @@ public sealed class InteractiveLaunchSpecService
|
||||
var settings = await new AppSettingsRepository(ctx).GetAsync(ct);
|
||||
if (!settings.PowerUseEnabled) return basePrompt;
|
||||
|
||||
if (!PowerUseWindow.IsOpen(_now(), settings.PowerUseWindowStart, settings.PowerUseWindowEnd))
|
||||
return basePrompt;
|
||||
|
||||
var listConfig = await new ListRepository(ctx).GetConfigAsync(listId, ct);
|
||||
if (listConfig?.PowerUseAllowed != true) return basePrompt;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user