chore(claude-do): UsageGate: Parallelitaet stufenweise drosseln statt erst bei

## Kontext: Limits sind Fenster, nicht Summen

Die Runs laufen ueber das Claude-Abo. Limits greifen pro 5h-Fenster und pro 7 Tage. Nicht die Wochensumme tut weh, sondern dass ein Agent-Burst ein Fenster leerraeumt, in dem Mika selbst interaktiv arbeiten will.

## Messgrundlage (alle Transcripts unter ~/.claude/projects)

Agent-Runs sind ueber die ganze Historie nur **18,4 %** des Account-Verbrauch

ClaudeDo-Task: 87105f5e-c4f4-4af4-ae60-89cd2e153e3c
This commit is contained in:
mika kuns
2026-08-05 15:53:15 +02:00
parent 83ea429b8a
commit a201d3f43d
24 changed files with 1466 additions and 19 deletions
@@ -28,6 +28,8 @@ public sealed partial class UsageMonitorModalViewModel : ViewModelBase
[NotifyPropertyChangedFor(nameof(StaleStampText))]
[NotifyPropertyChangedFor(nameof(StaleBandText))]
[NotifyPropertyChangedFor(nameof(GateBandText))]
[NotifyPropertyChangedFor(nameof(IsThrottled))]
[NotifyPropertyChangedFor(nameof(ThrottleBandText))]
private UsageSnapshotDto? _snapshot;
[ObservableProperty] private DateTime? _startDate;
@@ -60,6 +62,18 @@ public sealed partial class UsageMonitorModalViewModel : ViewModelBase
public string StaleBandText => Loc.T("modals.usageMonitor.staleFormat", StaleStampText);
public string GateBandText => GateReason is null ? "" : Loc.T("modals.usageMonitor.gateBlockedFormat", GateReason);
public bool IsThrottled => Snapshot is { } s && !IsGateBlocked && s.EffectiveSlots < s.ConfiguredSlots;
public string ThrottleBandText => Snapshot is not { } s || !IsThrottled
? ""
: Loc.T("modals.usageMonitor.throttleFormat", s.EffectiveSlots, s.ConfiguredSlots, BucketLabel(s.ThrottleBucket));
private static string BucketLabel(string? bucket) => bucket switch
{
"five_hour" => Loc.T("usage.pill.fiveHourLabel"),
"seven_day" => Loc.T("usage.pill.sevenDayLabel"),
_ => "",
};
[RelayCommand]
private void Close()
{
@@ -40,6 +40,7 @@ public sealed partial class UsagePillViewModel : ViewModelBase
OnPropertyChanged(nameof(IsWarn));
OnPropertyChanged(nameof(IsBlocked));
OnPropertyChanged(nameof(IsStale));
OnPropertyChanged(nameof(IsThrottled));
OnPropertyChanged(nameof(Tooltip));
OnPropertyChanged(nameof(ShowNormalDot));
OnPropertyChanged(nameof(ShowWarnDot));
@@ -52,6 +53,8 @@ public sealed partial class UsagePillViewModel : ViewModelBase
public bool IsStale => Snapshot?.IsStale == true;
public bool IsThrottled => Snapshot is { } s && !IsBlocked && s.EffectiveSlots < s.ConfiguredSlots;
public bool IsWarn => Snapshot is { } s &&
((s.FiveHourPercent is { } five && five >= s.FiveHourThresholdPct - 10) ||
(s.SevenDayPercent is { } seven && seven >= s.SevenDayThresholdPct - 10));
@@ -95,6 +98,8 @@ public sealed partial class UsagePillViewModel : ViewModelBase
lines.Add(Loc.T("usage.pill.resetIn", Loc.T("usage.pill.sevenDayLabel"), FormatRemaining(sevenReset)));
if (s.IsGateBlocked && !string.IsNullOrEmpty(s.GateReason))
lines.Add(Loc.T("usage.pill.blockedReason", s.GateReason));
else if (s.EffectiveSlots < s.ConfiguredSlots)
lines.Add(Loc.T("usage.pill.throttled", s.EffectiveSlots, s.ConfiguredSlots, BucketLabel(s.ThrottleBucket)));
if (s.IsStale)
{
var stamp = s.FetchedAtUtc?.ToLocalTime().ToString("HH:mm") ?? "?";
@@ -106,6 +111,13 @@ public sealed partial class UsagePillViewModel : ViewModelBase
return lines.Count > 0 ? string.Join(Environment.NewLine, lines) : Loc.T("usage.pill.empty");
}
private static string BucketLabel(string? bucket) => bucket switch
{
"five_hour" => Loc.T("usage.pill.fiveHourLabel"),
"seven_day" => Loc.T("usage.pill.sevenDayLabel"),
_ => "",
};
private static string FormatRemaining(DateTimeOffset resetsAt)
{
var remaining = resetsAt - DateTimeOffset.UtcNow;