maxpool 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Multi-account Claude Code proxy with adaptive, rate-aware load balancing across Claude accounts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -2053,9 +2053,11 @@ export class AccountManager {
2053
2053
  next.peakWindows = peakWindows;
2054
2054
  }
2055
2055
  if (peakCap !== undefined) {
2056
- const c = Number(peakCap);
2057
- if (!Number.isFinite(c) || c < 0 || c > 1) return false;
2058
- next.peakCap = c;
2056
+ // The SAME coercion trap the read path documents: Number(null)/Number('')/
2057
+ // Number(false) are all 0 = the HARD BAR. A persisted 0 is then unreachable
2058
+ // by the read-path guard (typeof 0 === 'number'). Reject non-numbers here.
2059
+ if (typeof peakCap !== 'number' || !Number.isFinite(peakCap) || peakCap < 0 || peakCap > 1) return false;
2060
+ next.peakCap = peakCap;
2059
2061
  }
2060
2062
  if (peakDepreference !== undefined) next.peakDepreference = Boolean(peakDepreference);
2061
2063
  if (peakTimezone !== undefined) {
package/src/tui.js CHANGED
@@ -828,6 +828,14 @@ export class TUI {
828
828
  // dialog (unlike restart/delete). `f` is kept for muscle memory from the old
829
829
  // cross-provider knob this replaced; `m` is the mnemonic (mode).
830
830
  this._cycleRoutingMode();
831
+ } else if (k === 'd' || k === 'D') {
832
+ // Toggle peak de-preference. THE knob that matters: with it ON, the peak window
833
+ // OVERRIDES your routing mode for 4h every weekday (balance stops being balance).
834
+ // OFF keeps the weekly cap but lets your chosen mode run all day.
835
+ this._togglePeakDepreference();
836
+ } else if (k === 'c' || k === 'C') {
837
+ // Cycle the peak weekly cap: off → 25% → 50% → 75% → never-during-peak.
838
+ this._cyclePeakCap();
831
839
  } else if (k === 'g' || k === 'k') {
832
840
  // Per-provider Claude→provider fallback. Only affects routing under `sticky`
833
841
  // mode — under balance/prefer-* the mode itself controls eligibility. Still
@@ -887,6 +895,41 @@ export class TUI {
887
895
  return TUI.ROUTING_MODES.find(m => m.id === id) || TUI.ROUTING_MODES[0];
888
896
  }
889
897
 
898
+ /** Toggle peak de-preference for every provider that HAS a peak window. This is the
899
+ * setting that silently overrides the routing mode during the window, so it gets a
900
+ * first-class key rather than living only in the config file. */
901
+ async _togglePeakDepreference() {
902
+ const fams = [...new Set(this.am.accounts.filter(a => a.type === 'provider').map(a => a.provider))]
903
+ .filter(p => (this.am._peakSettingsFor?.(p)?.windows || []).length > 0);
904
+ if (!fams.length) { this._addLog('No provider has a peak window configured'); return; }
905
+ const next = !(this.am._peakSettingsFor(fams[0]).depreference);
906
+ for (const p of fams) this.am.setPeakSettingsForProvider?.(p, { peakDepreference: next });
907
+ this.config.scheduler = { ...(this.config.scheduler || {}) };
908
+ this.config.scheduler.providers = { ...(this.am.scheduler.providers || {}) };
909
+ await this.saveConfig(this.config);
910
+ this._addLog(next
911
+ ? 'Peak hours: prefer other accounts (your routing mode is overridden during the window)'
912
+ : 'Peak hours: keep your routing mode all day (the weekly cap still applies)');
913
+ }
914
+
915
+ /** Cycle the peak weekly cap across the values that mean something. */
916
+ async _cyclePeakCap() {
917
+ const order = [1, 0.75, 0.5, 0.25, 0];
918
+ const fams = [...new Set(this.am.accounts.filter(a => a.type === 'provider').map(a => a.provider))]
919
+ .filter(p => (this.am._peakSettingsFor?.(p)?.windows || []).length > 0);
920
+ if (!fams.length) { this._addLog('No provider has a peak window configured'); return; }
921
+ const cur = this.am._peakSettingsFor(fams[0]).cap;
922
+ const idx = order.findIndex(v => Math.abs(v - cur) < 1e-9);
923
+ const next = order[(idx === -1 ? 0 : idx + 1) % order.length];
924
+ for (const p of fams) this.am.setPeakSettingsForProvider?.(p, { peakCap: next });
925
+ this.config.scheduler = { ...(this.config.scheduler || {}) };
926
+ this.config.scheduler.providers = { ...(this.am.scheduler.providers || {}) };
927
+ await this.saveConfig(this.config);
928
+ this._addLog(next === 1 ? 'Peak cap: off (no weekly limit during peak)'
929
+ : next === 0 ? 'Peak cap: never use GLM during peak hours'
930
+ : `Peak cap: bench a GLM account once it passes ${Math.round(next * 100)}% of its weekly quota`);
931
+ }
932
+
890
933
  async _cycleRoutingMode() {
891
934
  const modes = TUI.ROUTING_MODES;
892
935
  const cur = this.am.scheduler?.routingMode || 'sticky';
@@ -1932,7 +1975,22 @@ export class TUI {
1932
1975
  const provPart = (hasProviders && mode.id === 'sticky')
1933
1976
  ? ` ${bold('g')} GLM: ${cyan(this.am._claudeFallbackFor?.('zai') || 'never')} ${bold('k')} Kimi: ${cyan(this.am._claudeFallbackFor?.('kimi') || 'never')}`
1934
1977
  : '';
1935
- return ` ${bold('f')} Routing: ${cyan(mode.label)} ↻${provPart} ${bold('p')} Manual preference ${bold('Esc')} Back`;
1978
+ // PEAK controls — always visible when a provider has a window, in-window or not.
1979
+ // They were config-file-only at first ship, which meant the feature was
1980
+ // undiscoverable from the UI (reported 2026-08-19). The state is shown next to
1981
+ // the key so you can read the current setting without opening the config.
1982
+ let peakPart = '';
1983
+ const peakFam = this.am.accounts.find(a => a.type === 'provider'
1984
+ && (this.am._peakSettingsFor?.(a.provider)?.windows || []).length > 0);
1985
+ if (peakFam) {
1986
+ const ps = this.am._peakSettingsFor(peakFam.provider);
1987
+ const st = this.am._peakStateFor?.(peakFam.provider);
1988
+ const now = st?.inPeak ? red(' NOW') : '';
1989
+ const dep = ps.depreference ? yellow('prefer others') : cyan('keep my mode');
1990
+ const cap = ps.cap >= 1 ? 'off' : ps.cap === 0 ? 'never' : `${Math.round(ps.cap * 100)}%`;
1991
+ peakPart = ` ${dim('│')} ${bold('d')} Peak${now}: ${dep} ${bold('c')} cap ${cyan(cap)}`;
1992
+ }
1993
+ return ` ${bold('f')} Routing: ${cyan(mode.label)} ↻${provPart}${peakPart} ${bold('p')} Preference ${bold('Esc')} Back`;
1936
1994
  }
1937
1995
  case 'select': {
1938
1996
  const act = this.selAction === 'prefer'