feat(ui): Throttle-Stufen im Ausfuehrung-Tab sichtbar machen

Die vier UsageThrottle-Prozente waren nur per Gauge-Drag im Usage-Monitor
erreichbar und wurden im Settings-Modal unsichtbar durchgeschleift. Jetzt
read-only angezeigt plus Button in den Usage-Monitor. Der Button schliesst die
Settings absichtlich: Save() schreibt die beim Oeffnen gelesenen Throttle-Werte
zurueck und wuerde ein zwischenzeitliches Draggen ueberschreiben.
This commit is contained in:
mika kuns
2026-08-27 10:39:08 +02:00
parent 4464dc6ff1
commit 31d53b0033
5 changed files with 71 additions and 0 deletions
+11
View File
@@ -40,6 +40,10 @@
"usageGateSevenDayPct": "7-Tage-Fenster (%)", "usageGateSevenDayPct": "7-Tage-Fenster (%)",
"autoContinueOnUsageLimit": "Nach Limit-Reset fortsetzen", "autoContinueOnUsageLimit": "Nach Limit-Reset fortsetzen",
"autoContinueOnUsageLimitHint": "Ist ein Run am Nutzungslimit abgebrochen, wird er nach Ablauf des 5-Stunden-Fensters automatisch fortgesetzt. Weckt außerdem die pausierte Queue genau zum Reset-Zeitpunkt statt erst beim nächsten Backstop-Check.", "autoContinueOnUsageLimitHint": "Ist ein Run am Nutzungslimit abgebrochen, wird er nach Ablauf des 5-Stunden-Fensters automatisch fortgesetzt. Weckt außerdem die pausierte Queue genau zum Reset-Zeitpunkt statt erst beim nächsten Backstop-Check.",
"throttleStagesHeading": "Throttle-Stufen",
"throttleStagesHint": "Die Parallelität wird vor dem harten Gate begrenzt: soft auf 2 Slots, hard auf 1. Eingestellt wird das durch Ziehen der Marker an den Gauges im Usage-Monitor. Der Monitor schließt diese Einstellungen — vorher speichern, wenn du etwas geändert hast.",
"throttleStagesValues": "5h: soft {0}% / hard {1}% · 7d: soft {2}% / hard {3}%",
"openUsageMonitor": "Usage-Monitor öffnen...",
"reportExcludedPaths": "Bericht: ausgeschlossene Pfade (einer pro Zeile)", "reportExcludedPaths": "Bericht: ausgeschlossene Pfade (einer pro Zeile)",
"standupWeekday": "Standup-Wochentag", "standupWeekday": "Standup-Wochentag",
"weekdaySunday": "Sonntag", "weekdaySunday": "Sonntag",
@@ -66,6 +70,10 @@
"confirmRemoveAll": "ALLE Worktrees entfernen? Nicht committete Arbeit geht verloren." "confirmRemoveAll": "ALLE Worktrees entfernen? Nicht committete Arbeit geht verloren."
}, },
"files": { "files": {
"cliSection": "CLAUDE CLI",
"claudeBinHint": "Befehl oder vollständiger Pfad, den der Worker startet. Leer setzt auf \"claude\" zurück. Gilt ab dem nächsten Task-Run — kein Neustart nötig.",
"claudeBinResolved": "Aufgelöst zu: {0}",
"claudeBinUnresolved": "Nicht im PATH gefunden — Task-Runs schlagen fehl, solange das so bleibt.",
"agentsSection": "AGENTEN", "agentsSection": "AGENTEN",
"agentsHint": "Mitgelieferte Standard-Agenten wiederherstellen. Vorhandene Dateien werden nicht überschrieben.", "agentsHint": "Mitgelieferte Standard-Agenten wiederherstellen. Vorhandene Dateien werden nicht überschrieben.",
"restoreDefaultAgents": "Standard-Agenten wiederherstellen", "restoreDefaultAgents": "Standard-Agenten wiederherstellen",
@@ -762,6 +770,9 @@
"forceRemoving": "Worktree wird zwangsweise entfernt…", "forceRemoving": "Worktree wird zwangsweise entfernt…",
"batchMerging": "Merge {0}/{1}…" "batchMerging": "Merge {0}/{1}…"
}, },
"claudeBin": {
"saving": "claude_bin wird gespeichert…"
},
"skills": { "skills": {
"installing": "Skill wird geklont…", "installing": "Skill wird geklont…",
"updating": "Skill wird aktualisiert…", "updating": "Skill wird aktualisiert…",
+11
View File
@@ -40,6 +40,10 @@
"usageGateSevenDayPct": "7-day window (%)", "usageGateSevenDayPct": "7-day window (%)",
"autoContinueOnUsageLimit": "Continue on session limit reset", "autoContinueOnUsageLimit": "Continue on session limit reset",
"autoContinueOnUsageLimitHint": "When a run stops because it hit the usage limit, automatically continue it once the 5-hour window resets. Also wakes the paused queue exactly at reset time instead of waiting for the next backstop check.", "autoContinueOnUsageLimitHint": "When a run stops because it hit the usage limit, automatically continue it once the 5-hour window resets. Also wakes the paused queue exactly at reset time instead of waiting for the next backstop check.",
"throttleStagesHeading": "Throttle stages",
"throttleStagesHint": "Parallelism is capped before the hard gate: soft caps at 2 slots, hard at 1. Edited by dragging the markers on the usage-monitor gauges. Opening the monitor closes these settings — save first if you changed anything.",
"throttleStagesValues": "5h: soft {0}% / hard {1}% · 7d: soft {2}% / hard {3}%",
"openUsageMonitor": "Open usage monitor...",
"reportExcludedPaths": "Report: excluded paths (one per line)", "reportExcludedPaths": "Report: excluded paths (one per line)",
"standupWeekday": "Standup weekday", "standupWeekday": "Standup weekday",
"weekdaySunday": "Sunday", "weekdaySunday": "Sunday",
@@ -66,6 +70,10 @@
"confirmRemoveAll": "Remove ALL worktrees? Uncommitted work will be lost." "confirmRemoveAll": "Remove ALL worktrees? Uncommitted work will be lost."
}, },
"files": { "files": {
"cliSection": "CLAUDE CLI",
"claudeBinHint": "Command or full path the worker starts. Blank resets to \"claude\". Applies to the next task run — no restart needed.",
"claudeBinResolved": "Resolves to: {0}",
"claudeBinUnresolved": "Not found on PATH — task runs will fail until this resolves.",
"agentsSection": "AGENTS", "agentsSection": "AGENTS",
"agentsHint": "Restore bundled default agents. Existing files are not overwritten.", "agentsHint": "Restore bundled default agents. Existing files are not overwritten.",
"restoreDefaultAgents": "Restore default agents", "restoreDefaultAgents": "Restore default agents",
@@ -762,6 +770,9 @@
"forceRemoving": "Force-removing worktree…", "forceRemoving": "Force-removing worktree…",
"batchMerging": "Merging {0}/{1}…" "batchMerging": "Merging {0}/{1}…"
}, },
"claudeBin": {
"saving": "Saving claude_bin…"
},
"skills": { "skills": {
"installing": "Cloning skill…", "installing": "Cloning skill…",
"updating": "Updating skill…", "updating": "Updating skill…",
@@ -66,6 +66,18 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
[RelayCommand] [RelayCommand]
private void OpenRepoImport() => RequestRepoImport?.Invoke(); private void OpenRepoImport() => RequestRepoImport?.Invoke();
// Same deal for the usage monitor, which owns the throttle-stage editor. It must *close* this
// modal rather than stack on top: Save() writes the throttle values it read at open, so a drag
// made while Settings is still open would be overwritten on save.
public Action? RequestUsageMonitor { get; set; }
[RelayCommand]
private void OpenUsageMonitor() => RequestUsageMonitor?.Invoke();
/// Read-only echo of the four throttle percentages, which are edited by dragging the
/// usage-monitor gauges — without this the Execution tab silently carries them through.
[ObservableProperty] private string _throttleSummary = "";
[ObservableProperty] private string _validationError = ""; [ObservableProperty] private string _validationError = "";
[ObservableProperty] private bool _isBusy; [ObservableProperty] private bool _isBusy;
[ObservableProperty] private string _statusMessage = ""; [ObservableProperty] private string _statusMessage = "";
@@ -135,6 +147,9 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
_throttleStages = ( _throttleStages = (
dto.UsageThrottleFiveHourSoftPct, dto.UsageThrottleFiveHourHardPct, dto.UsageThrottleFiveHourSoftPct, dto.UsageThrottleFiveHourHardPct,
dto.UsageThrottleSevenDaySoftPct, dto.UsageThrottleSevenDayHardPct); dto.UsageThrottleSevenDaySoftPct, dto.UsageThrottleSevenDayHardPct);
ThrottleSummary = Loc.T("settings.general.throttleStagesValues",
_throttleStages.FiveSoft, _throttleStages.FiveHard,
_throttleStages.SevenSoft, _throttleStages.SevenHard);
Worktrees.WorktreeStrategy = dto.WorktreeStrategy ?? "sibling"; Worktrees.WorktreeStrategy = dto.WorktreeStrategy ?? "sibling";
Worktrees.CentralWorktreeRoot = dto.CentralWorktreeRoot; Worktrees.CentralWorktreeRoot = dto.CentralWorktreeRoot;
Worktrees.WorktreeAutoCleanupEnabled = dto.WorktreeAutoCleanupEnabled; Worktrees.WorktreeAutoCleanupEnabled = dto.WorktreeAutoCleanupEnabled;
@@ -148,6 +163,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
} }
else StatusMessage = Loc.T("vm.settingsModal.workerOffline"); else StatusMessage = Loc.T("vm.settingsModal.workerOffline");
await Files.LoadClaudeBinAsync();
await Prime.LoadAsync(); await Prime.LoadAsync();
await OnlineInbox.LoadAsync(); await OnlineInbox.LoadAsync();
await SessionSkills.LoadAsync(dto?.SessionSkills); await SessionSkills.LoadAsync(dto?.SessionSkills);
@@ -252,6 +252,18 @@
<TextBlock Text="{loc:Tr settings.general.autoContinueOnUsageLimitHint}" <TextBlock Text="{loc:Tr settings.general.autoContinueOnUsageLimitHint}"
Opacity="0.6" FontSize="12" TextWrapping="Wrap"/> Opacity="0.6" FontSize="12" TextWrapping="Wrap"/>
</StackPanel> </StackPanel>
<!-- Throttle stages live on the usage-monitor gauges (drag + spinner per stage);
echoed read-only here so they aren't invisible on the tab that owns the gate. -->
<StackPanel Spacing="4">
<TextBlock Classes="field-label" Text="{loc:Tr settings.general.throttleStagesHeading}"/>
<TextBlock Text="{Binding ThrottleSummary}" Classes="meta"/>
<TextBlock Text="{loc:Tr settings.general.throttleStagesHint}"
Opacity="0.6" FontSize="12" TextWrapping="Wrap"/>
<Button Classes="btn" HorizontalAlignment="Left"
Content="{loc:Tr settings.general.openUsageMonitor}"
Command="{Binding OpenUsageMonitorCommand}"/>
</StackPanel>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</TabItem> </TabItem>
@@ -394,6 +406,22 @@
<TabItem Header="{loc:Tr settings.sidebar.categoryFiles}"> <TabItem Header="{loc:Tr settings.sidebar.categoryFiles}">
<ScrollViewer> <ScrollViewer>
<StackPanel Spacing="12" Margin="0,8,0,0"> <StackPanel Spacing="12" Margin="0,8,0,0">
<!-- claude_bin from worker.config.json — saved on its own button, not the modal's
Save, because the worker owns that file rather than app_settings. -->
<StackPanel Spacing="6">
<TextBlock Classes="section-label" Text="{loc:Tr settings.files.cliSection}"/>
<TextBlock Classes="meta" Text="{loc:Tr settings.files.claudeBinHint}" TextWrapping="Wrap"/>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="8">
<TextBox Grid.Column="0" Text="{Binding Files.ClaudeBin, Mode=TwoWay}"
PlaceholderText="claude"/>
<Button Grid.Column="1" Classes="btn" Content="{loc:Tr settings.save}"
Command="{Binding Files.SaveClaudeBinCommand}"/>
</Grid>
<ctl:OperationIndicator Status="{Binding Files.ClaudeBinOp}"/>
<TextBlock Classes="path-mono" TextWrapping="Wrap"
Text="{Binding Files.ClaudeBinResolvedHint}"
IsVisible="{Binding Files.ClaudeBinResolvedHint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
<StackPanel Spacing="6"> <StackPanel Spacing="6">
<TextBlock Classes="section-label" Text="{loc:Tr settings.files.agentsSection}"/> <TextBlock Classes="section-label" Text="{loc:Tr settings.files.agentsSection}"/>
<TextBlock Classes="meta" Text="{loc:Tr settings.files.agentsHint}" <TextBlock Classes="meta" Text="{loc:Tr settings.files.agentsHint}"
@@ -77,6 +77,11 @@ public sealed class WindowDialogService : IDialogService
dlg.Close(); dlg.Close();
if (Shell is not null) _ = Shell.OpenRepoImportCommand.ExecuteAsync(null); if (Shell is not null) _ = Shell.OpenRepoImportCommand.ExecuteAsync(null);
}; };
vm.RequestUsageMonitor = () =>
{
dlg.Close();
if (Shell is not null) _ = Shell.OpenUsageMonitorCommand.ExecuteAsync(null);
};
await dlg.ShowDialog(ActiveOwner()); await dlg.ShowDialog(ActiveOwner());
} }