maxpool 1.21.3 → 1.22.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.21.3",
3
+ "version": "1.22.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",
@@ -68,6 +68,16 @@ function emptyQuota() {
68
68
  // Provider (z.ai / Kimi) quota — kept SEPARATE from unified* so a provider
69
69
  // reading never leaks into the OAuth quota gates (_isAvailable / _weeklyRawState
70
70
  // / _accountScarcity read unified* only). z.ai is pollable; Kimi is not.
71
+ // Largest "time until reset" ever OBSERVED for each window, i.e. a lower bound on
72
+ // the window's true length. The dynamic cap needs a duration to know how far into a
73
+ // window it is, and the nominal 5h/7d table is an assumption about the VENDOR: a
74
+ // provider whose "weekly" is really 3 days would sit part-way up the ramp from the
75
+ // moment the window is born. Learning the span from observation fixes that, and the
76
+ // error direction of an UNDER-estimate (seen only late in a window) is conservative —
77
+ // it reports less elapsed, so the cap stays nearer its floor. Grows monotonically and
78
+ // self-corrects the first time a fresh window is seen.
79
+ capSpanSes: null,
80
+ capSpanWk: null,
71
81
  providerSes: null, // utilization 0-1 (z.ai 5h token window)
72
82
  providerSesReset: null, // ms
73
83
  providerWk: null, // utilization 0-1 (z.ai weekly), null if plan has none
@@ -141,6 +151,11 @@ const DEFAULT_SCHEDULER = {
141
151
  // ACTIVE (_peakTier ≥ 1). Default OFF — mechanism shipped, immediate use declined.
142
152
  criticalPeakUnlock: false,
143
153
  weeklyBurnDebtWeight: 0.6,
154
+ // DYNAMIC CAP: the fraction of a quota window that elapses before a dynamic cap
155
+ // starts lifting off its floor. 0.5 holds the owner's full reserve through the first
156
+ // half of every window, then opens it up over the second half — so the reserve is
157
+ // protected while there is still time to use it, and spent when there is not.
158
+ capRampStart: 0.5,
144
159
  // Routing-cost tuning (lower cost = preferred). The goal is to AVOID
145
160
  // short-term (rate/concurrency) throttling by spreading load across healthy
146
161
  // accounts. So in-flight concurrency is the DOMINANT term, with a steep
@@ -289,7 +304,7 @@ const PERSISTED_QUOTA_FIELDS = [
289
304
  // flag and the fast-refill discount (plus the TUI "Wk none" rendering) silently
290
305
  // drops until the next probe sweep re-learns it. The probe still rewrites it on
291
306
  // every successful sweep, so a plan change heals on the same cadence.
292
- 'weeklyAbsent', 'providerSes', 'providerSesReset', 'providerWk', 'providerWkReset',
307
+ 'weeklyAbsent', 'capSpanSes', 'capSpanWk', 'providerSes', 'providerSesReset', 'providerWk', 'providerWkReset',
293
308
  'providerQuotaSource', 'lastProbeOkAt',
294
309
  ];
295
310
 
@@ -344,14 +359,70 @@ function parseResetHeader(value) {
344
359
  * also logs once so a hand-edited config doesn't silently fail open (a NaN cap makes
345
360
  * every `util >= cap` comparison false = uncapped, with no error anywhere).
346
361
  */
347
- function _sanitizeCap(value, name) {
362
+ function _sanitizeCap(value, name, quiet = false) {
348
363
  if (value == null) return null;
349
364
  const n = Number(value);
350
365
  if (Number.isFinite(n) && n > 0 && n < 1) return n;
351
- console.log(`[Maxpool] Ignoring invalid capUtilization ${JSON.stringify(value)} for "${name}" — expected 0-1`);
366
+ // `quiet` is for the second call that only resolves the MODE from the same value —
367
+ // it would otherwise print the identical complaint twice per account per load.
368
+ if (!quiet) console.log(`[Maxpool] Ignoring invalid capUtilization ${JSON.stringify(value)} for "${name}" — expected 0-1`);
352
369
  return null;
353
370
  }
354
371
 
372
+ /**
373
+ * The cap MODE for an account. Only meaningful when a cap is set. 'fixed' must be
374
+ * asked for EXPLICITLY; everything else (absent, unknown string) resolves to
375
+ * 'dynamic' — the owner's directive that the dynamic cap "become the new default for
376
+ * the accounts that currently have the fixed cap", which is exactly the config shape
377
+ * with a `capUtilization` and no `capMode`.
378
+ */
379
+ /** A cap ramp start is a fraction of the window in [0,1); anything else means "use the
380
+ * default". 1 or more would mean "never ramp", which is what `capMode:'fixed'` says
381
+ * properly, so it is rejected here rather than silently creating a second way to say it. */
382
+ function _sanitizeRampStart(value, name) {
383
+ if (value == null) return null;
384
+ const n = Number(value);
385
+ if (Number.isFinite(n) && n >= 0 && n < 1) return n;
386
+ console.log(`[Maxpool] Ignoring invalid capRampStart ${JSON.stringify(value)} for "${name}" — expected 0 to <1 (use capMode:"fixed" for no ramp)`);
387
+ return null;
388
+ }
389
+
390
+ function _capMode(value, sanitizedCap, name) {
391
+ if (sanitizedCap == null) return null;
392
+ if (value === 'fixed' || value === 'dynamic') return value;
393
+ // Absent is the migration case and is silent BY DESIGN — every pre-2026-09-24 config
394
+ // has a cap and no mode, and the owner asked for those to become dynamic. A value that
395
+ // is present but unrecognised is a typo, and swallowing it silently would be the same
396
+ // fail-open-without-a-word shape _sanitizeCap exists to prevent for the number.
397
+ if (value != null) {
398
+ console.log(`[Maxpool] Ignoring unknown capMode ${JSON.stringify(value)} for "${name}" — expected "fixed" or "dynamic"; using dynamic`);
399
+ }
400
+ return 'dynamic';
401
+ }
402
+
403
+ /**
404
+ * How far into a quota window we are, 0-1, from its reset stamp. Returns null when the
405
+ * stamp is unusable (absent / non-finite) so every caller can fail CLOSED rather than
406
+ * inventing a position in a window it cannot see. A stamp in the past clamps to 1
407
+ * (window over) and one further out than a nominal window clamps to 0 — both are real
408
+ * states around a rollover, and neither may produce a NaN.
409
+ */
410
+ function _windowElapsedRatio(resetAt, durationMs, now) {
411
+ if (!Number.isFinite(resetAt) || !Number.isFinite(durationMs) || durationMs <= 0) return null;
412
+ // A stamp in the PAST means the window already rolled and we have not yet learned the
413
+ // new one — so our POSITION in the live window is unknown, and the utilization we hold
414
+ // belongs to the window that closed. Treating that as "fully elapsed" would ramp the cap
415
+ // to its ceiling on stale data: reproduced on a provider account (providerWk 0.55, stamp
416
+ // 3 days past) reading `normal` where the fixed cap says `capped`, and provider stamps
417
+ // are never cleared by _clearExpiredQuotas, so a probe outage holds that state open
418
+ // indefinitely. Unknown position must fail CLOSED to the floor, like a missing stamp.
419
+ if (resetAt <= now) return null;
420
+ // `resetAt > now` is guaranteed above and `durationMs > 0`, so the ratio cannot exceed
421
+ // 1; only the LOW side needs bounding, for a stamp further out than one nominal window
422
+ // (a vendor window longer than we assume) which must read as "window just started".
423
+ return Math.max(0, (durationMs - (resetAt - now)) / durationMs);
424
+ }
425
+
355
426
  export class AccountManager {
356
427
  constructor(accounts, switchThreshold = 0.90, schedulerOptions = {}, dependencies = {}) {
357
428
  this.scheduler = { ...DEFAULT_SCHEDULER, ...schedulerOptions };
@@ -388,6 +459,18 @@ export class AccountManager {
388
459
  // to null HERE, visibly (below), so a hand-edited "50" or "abc" in the config
389
460
  // can never fail the >= comparisons open as NaN.
390
461
  capUtilization: _sanitizeCap(acct.capUtilization, acct.name),
462
+ // CAP MODE (owner-directed 2026-09-24). 'dynamic' is the DEFAULT for any account
463
+ // that carries a cap: the value above becomes a FLOOR, and the effective cap ramps
464
+ // toward switchThreshold as the window nears its reset, so reserved-but-unused
465
+ // capacity is spent instead of dying at reset. `capMode:'fixed'` opts back in to the
466
+ // constant cap. Uncapped accounts carry no mode (nothing to modulate).
467
+ capMode: _capMode(acct.capMode, _sanitizeCap(acct.capUtilization, acct.name, true), acct.name),
468
+ // Per-account overrides for the two numbers that decide how much reserve is kept
469
+ // and for how long. Defaults live in DEFAULT_SCHEDULER; these exist because the
470
+ // right answer is the OWNER's (how much of their own account to hold back, and
471
+ // from when), not a constant buried in the scheduler.
472
+ capCeiling: _sanitizeCap(acct.capCeiling, acct.name, true),
473
+ capRampStart: _sanitizeRampStart(acct.capRampStart, acct.name),
391
474
  model: acct.model || null,
392
475
  modelMap: acct.modelMap || null,
393
476
  stripBetaHeaders: Boolean(acct.stripBetaHeaders),
@@ -869,11 +952,15 @@ export class AccountManager {
869
952
  if (account.inFlight >= this.scheduler.safetyMaxActivePerAccount) return false;
870
953
  if (this.getGlobalInFlight() >= this.scheduler.safetyMaxGlobalActive) return false;
871
954
  if (account.status === 'exhausted' || account.status === 'error') return false;
872
- if (this._isSessionQuotaUnavailable(account)) return false;
955
+ // `now` is threaded deliberately: under a DYNAMIC cap the bench threshold moves with
956
+ // the clock, so an availability decision and the retry oracle that explains it must
957
+ // read the same instant. The drift is milliseconds today, but it is always in the
958
+ // direction that manufactures a spurious 'capped' verdict, and nothing else enforces it.
959
+ if (this._isSessionQuotaUnavailable(account, now)) return false;
873
960
  // Gate on RAW weekly usage, not pace-adjusted: an account with real
874
961
  // headroom (e.g. 69% used, resets in days) must stay in the healthy-spread
875
962
  // pool even if it's burning fast. Pace is a soft SCORE cost, never a bench.
876
- const weeklyState = this._weeklyRawState(account);
963
+ const weeklyState = this._weeklyRawState(account, now);
877
964
  if (weeklyState === 'exhausted' || weeklyState === 'capped') return false;
878
965
  if (weeklyState === 'critical' && !options.allowWeeklyCritical) return false;
879
966
  if (weeklyState === 'reserve' && !options.allowWeeklyReserve) return false;
@@ -1310,21 +1397,125 @@ export class AccountManager {
1310
1397
  * oracle can never desync from the bench (a capped-benched account MUST report a
1311
1398
  * finite retry time or a live session holding on it gets error-fasted).
1312
1399
  */
1313
- _sessionBenchThreshold(account) {
1314
- const cap = account?.capUtilization;
1400
+ _sessionBenchThreshold(account, now = Date.now()) {
1401
+ const cap = this._effectiveCap(account, 'ses', now);
1315
1402
  return (cap != null && cap < this.switchThreshold) ? cap : this.switchThreshold;
1316
1403
  }
1317
1404
 
1405
+ /**
1406
+ * The reset stamp + nominal duration of one quota window on this account. OAuth
1407
+ * accounts carry `unified*`; providers carry `provider*` (the two never mix — see
1408
+ * the note on emptyQuota). An account type with no reset stamp for the window
1409
+ * (API-key) yields a null stamp, which the caller treats as "cannot ramp".
1410
+ */
1411
+ /** Record the largest remaining-time seen for a window — see capSpanSes/capSpanWk. */
1412
+ _noteCapWindowSpan(account, window, resetAt, now = Date.now()) {
1413
+ if (!Number.isFinite(resetAt)) return;
1414
+ const remaining = resetAt - now;
1415
+ if (!(remaining > 0)) return;
1416
+ const key = window === 'ses' ? 'capSpanSes' : 'capSpanWk';
1417
+ const q = account.quota;
1418
+ const nominal = WINDOW_MS_BY_KIND[window];
1419
+ // Never learn a span LONGER than nominal: that direction would push the cap up the
1420
+ // ramp on an assumption, which is the fail-open side.
1421
+ const bounded = Math.min(remaining, nominal);
1422
+ if (q[key] == null || bounded > q[key]) q[key] = bounded;
1423
+ }
1424
+
1425
+ _capWindowFields(account, window) {
1426
+ const q = account?.quota || {};
1427
+ const learned = window === 'ses' ? q.capSpanSes : q.capSpanWk;
1428
+ // Prefer the observed span over the nominal assumption; both are bounded above by
1429
+ // nominal, so this can only ever move the cap DOWN toward its floor.
1430
+ const durationMs = Number.isFinite(learned) && learned > 0
1431
+ ? Math.min(learned, WINDOW_MS_BY_KIND[window])
1432
+ : WINDOW_MS_BY_KIND[window];
1433
+ if (account?.type === 'provider') {
1434
+ return { resetAt: window === 'ses' ? q.providerSesReset : q.providerWkReset, durationMs };
1435
+ }
1436
+ return { resetAt: window === 'ses' ? q.unified5hReset : q.unified7dReset, durationMs };
1437
+ }
1438
+
1439
+ /**
1440
+ * The cap THIS account is actually held to on THIS window RIGHT NOW.
1441
+ *
1442
+ * fixed mode / no reset stamp -> the configured value, unchanged
1443
+ * dynamic mode -> floor early in the window, ramping to the
1444
+ * ceiling (switchThreshold) as the reset nears
1445
+ *
1446
+ * The ceiling is switchThreshold and never 1.0, deliberately: the owner asked for a
1447
+ * cap that "always preserves some meaningful room for usage of those accounts outside
1448
+ * of MaxPool". A fully-ramped dynamic account therefore behaves exactly like an
1449
+ * UNCAPPED one — never more aggressively — so this mechanism can only ever make an
1450
+ * account MORE available than the fixed cap it replaces (pinned by T3c). That
1451
+ * one-directionality is why it is not a live-session control surface.
1452
+ *
1453
+ * Fails CLOSED in every uncertain case (no stamp, unusable duration): an unknown
1454
+ * window position returns the floor, never an opened-up cap.
1455
+ */
1456
+ _effectiveCap(account, window = 'wk', now = Date.now()) {
1457
+ const floor = account?.capUtilization;
1458
+ if (floor == null) return null; // uncapped — unchanged
1459
+ if (account.capMode !== 'dynamic') return floor; // fixed — byte-for-byte as before
1460
+ const ceiling = account.capCeiling ?? this.switchThreshold;
1461
+ if (floor >= ceiling) return floor; // never pull a high floor DOWN
1462
+ const { resetAt, durationMs } = this._capWindowFields(account, window);
1463
+ const elapsed = _windowElapsedRatio(resetAt, durationMs, now);
1464
+ if (elapsed == null) return floor; // fail closed
1465
+ const rampStart = account.capRampStart ?? this.scheduler.capRampStart;
1466
+ const ramp = rampStart >= 1 ? 0 : clamp01((elapsed - rampStart) / (1 - rampStart));
1467
+ return floor + (ceiling - floor) * ramp;
1468
+ }
1469
+
1470
+ /**
1471
+ * When a DYNAMIC cap will have risen far enough to stop benching `utilization` on
1472
+ * this window — i.e. the instant the ramp crosses it. Null when that never happens
1473
+ * before the reset (utilization at/above the ceiling), when the cap is fixed, or
1474
+ * when the window position is unknown; the caller then falls back to the reset.
1475
+ *
1476
+ * Without this the cap's hold is keyed to the window RESET, which is correct for a
1477
+ * fixed cap (only a reset unbenches it) and systematically too long for a rising one:
1478
+ * util 0.85 on a 0.50-floor weekly with 20h left is routable in 15h, but the oracle
1479
+ * would have told the client to wait the full 20h — an over-hold that lands exactly
1480
+ * in this feature's own target regime (near reset). Linear in elapsed, so invert it.
1481
+ */
1482
+ _capUnbenchAt(account, utilization, window = 'wk', now = Date.now()) {
1483
+ if (account?.capMode !== 'dynamic' || utilization == null) return null;
1484
+ const floor = account.capUtilization;
1485
+ const ceiling = account.capCeiling ?? this.switchThreshold;
1486
+ if (floor == null || floor >= ceiling) return null;
1487
+ if (utilization >= ceiling) return null; // the ramp never reaches it
1488
+ if (utilization < floor) return null; // not cap-benched at all
1489
+ const { resetAt, durationMs } = this._capWindowFields(account, window);
1490
+ if (_windowElapsedRatio(resetAt, durationMs, now) == null) return null;
1491
+ const rampStart = account.capRampStart ?? this.scheduler.capRampStart;
1492
+ if (rampStart >= 1) return null;
1493
+ // effective(t) = floor + (ceiling-floor) * (elapsed(t) - rampStart)/(1 - rampStart)
1494
+ // solve effective(t) = utilization for elapsed, then convert back to wall clock.
1495
+ const neededRamp = (utilization - floor) / (ceiling - floor);
1496
+ const neededElapsed = rampStart + neededRamp * (1 - rampStart);
1497
+ const at = resetAt - durationMs * (1 - neededElapsed);
1498
+ if (!Number.isFinite(at)) return null;
1499
+ // Never report a time outside (now, reset]: a crossing already behind us means the
1500
+ // account is not actually benched, and one past the reset is the reset's own case.
1501
+ if (at <= now) return null;
1502
+ // No upper bound needed: at = reset - duration*(1 - neededElapsed), and neededElapsed
1503
+ // exceeds 1 only when utilization exceeds the ceiling, which returned null above. A
1504
+ // `Math.min(at, resetAt)` here was dead defensive code — a brute-force sweep over
1505
+ // span x remaining x utilization found the crossing never once landed past the reset.
1506
+ return at;
1507
+ }
1508
+
1318
1509
  /** True when the account's usage cap has it benched on the given window reading. */
1319
- _capped(account, utilization) {
1320
- const cap = account?.capUtilization;
1510
+ _capped(account, utilization, window = 'wk', now = Date.now()) {
1511
+ const cap = this._effectiveCap(account, window, now);
1321
1512
  return cap != null && utilization != null && utilization >= cap;
1322
1513
  }
1323
1514
 
1324
- _isSessionQuotaUnavailable(account) {
1515
+ _isSessionQuotaUnavailable(account, now = Date.now()) {
1325
1516
  const q = account.quota;
1326
1517
  this._clearExpiredQuotas(account);
1327
- const bench = this._sessionBenchThreshold(account);
1518
+ const bench = this._sessionBenchThreshold(account, now);
1328
1519
 
1329
1520
  // Unified 5h quota is immediate availability. Weekly quota is handled
1330
1521
  // separately as long-horizon admission control.
@@ -1646,9 +1837,17 @@ export class AccountManager {
1646
1837
  // nextRetryForRequest → retryAfterMs: Infinity → server.js error-fasts → the
1647
1838
  // live session is KILLED, despite the real reset time being known all along.
1648
1839
  // Reproduced 2026-08-18 with providerWk=0.9995 + a known providerWkReset.
1840
+ // A DYNAMIC cap unbenches on its own ramp, BEFORE the reset — so hold until the
1841
+ // crossing, not the window end. `weeklyState === 'capped'` is the only case where
1842
+ // that applies; a genuinely exhausted account still waits for the reset.
1843
+ const wkReset = q.unified7dReset || q.providerWkReset || null;
1844
+ const wkUtil = account.type === 'provider' ? q.providerWk : q.unified7d;
1845
+ const capCrossing = weeklyState === 'capped'
1846
+ ? this._capUnbenchAt(account, wkUtil, 'wk', now)
1847
+ : null;
1649
1848
  return {
1650
1849
  cause: 'weekly_exhausted',
1651
- retryAt: q.unified7dReset || q.providerWkReset || null,
1850
+ retryAt: capCrossing ?? wkReset,
1652
1851
  queueable: false,
1653
1852
  };
1654
1853
  }
@@ -1733,7 +1932,7 @@ export class AccountManager {
1733
1932
  // _isSessionQuotaUnavailable (_sessionBenchThreshold) — a capped account benched
1734
1933
  // at 50% MUST report a finite retryAt here or a live session holding on it gets
1735
1934
  // error-fasted instead of waiting out the window (red-team blocker 2).
1736
- const bench = this._sessionBenchThreshold(account);
1935
+ const bench = this._sessionBenchThreshold(account, now);
1737
1936
  if (account.status === 'throttled' && account.rateLimitedUntil && now < account.rateLimitedUntil) {
1738
1937
  return { cause: 'rate_limited', retryAt: account.rateLimitedUntil, queueable: true };
1739
1938
  }
@@ -2951,7 +3150,7 @@ export class AccountManager {
2951
3150
  || (Number.isFinite(q.unified7d) && q.unified7d >= floor);
2952
3151
  }
2953
3152
 
2954
- _weeklyRawState(account) {
3153
+ _weeklyRawState(account, now = Date.now()) {
2955
3154
  const q = account.quota;
2956
3155
  this._clearExpiredQuotas(account);
2957
3156
  if (this._isAccountWideRejected(account)) return 'exhausted';
@@ -2968,7 +3167,7 @@ export class AccountManager {
2968
3167
  // and outranks both the tier ladder and the upstream verdict. There is no
2969
3168
  // upstreamAllows carve-out for providers anyway, but the ordering documents
2970
3169
  // that a cap can never be talked out of by the vendor's "allowed".
2971
- if (this._capped(account, used)) return 'capped';
3170
+ if (this._capped(account, used, 'wk', now)) return 'capped';
2972
3171
  if (used >= this.scheduler.weeklyExhaustedThreshold) return 'exhausted';
2973
3172
  if (used >= this.scheduler.weeklyCriticalThreshold) return 'critical';
2974
3173
  if (used >= this.scheduler.weeklyReserveThreshold) return 'reserve';
@@ -2994,7 +3193,7 @@ export class AccountManager {
2994
3193
  // right through the cap — the override exists for genuine over-limit-but-allowed
2995
3194
  // states and would otherwise make the cap a no-op on exactly the account it is
2996
3195
  // for (measured: this exact shape sat at unified7d=1.00 'allowed_warning').
2997
- if (this._capped(account, used)) return 'capped';
3196
+ if (this._capped(account, used, 'wk', now)) return 'capped';
2998
3197
  const upstreamAllows = typeof q.unifiedStatus === 'string' && q.unifiedStatus.startsWith('allowed');
2999
3198
  if (used >= this.scheduler.weeklyExhaustedThreshold && !upstreamAllows) return 'exhausted';
3000
3199
  if (used >= this.scheduler.weeklyCriticalThreshold) return 'critical';
@@ -3003,9 +3202,9 @@ export class AccountManager {
3003
3202
  return 'normal';
3004
3203
  }
3005
3204
 
3006
- _weeklyPaceState(account) {
3205
+ _weeklyPaceState(account, now = Date.now()) {
3007
3206
  // Provider quota lives in separate fields — see _weeklyRawState.
3008
- if (account.type === 'provider') return this._weeklyRawState(account);
3207
+ if (account.type === 'provider') return this._weeklyRawState(account, now);
3009
3208
  if (account.quota.unified7d == null) return 'unknown';
3010
3209
  const effective = this._effectiveWeeklyUsage(account);
3011
3210
  if (effective >= this.scheduler.weeklyExhaustedThreshold) return 'exhausted';
@@ -3047,11 +3246,17 @@ export class AccountManager {
3047
3246
 
3048
3247
  if (usage.fiveHour) {
3049
3248
  if (usage.fiveHour.utilization != null) q.unified5h = clamp01(usage.fiveHour.utilization);
3050
- if (usage.fiveHour.resetAt != null) q.unified5hReset = usage.fiveHour.resetAt;
3249
+ if (usage.fiveHour.resetAt != null) {
3250
+ q.unified5hReset = usage.fiveHour.resetAt;
3251
+ this._noteCapWindowSpan(account, 'ses', usage.fiveHour.resetAt);
3252
+ }
3051
3253
  }
3052
3254
  if (usage.sevenDay) {
3053
3255
  if (usage.sevenDay.utilization != null) q.unified7d = clamp01(usage.sevenDay.utilization);
3054
- if (usage.sevenDay.resetAt != null) q.unified7dReset = usage.sevenDay.resetAt;
3256
+ if (usage.sevenDay.resetAt != null) {
3257
+ q.unified7dReset = usage.sevenDay.resetAt;
3258
+ this._noteCapWindowSpan(account, 'wk', usage.sevenDay.resetAt);
3259
+ }
3055
3260
  }
3056
3261
  this.noteCapacityWindowAdvance(account.name, 'ses', prevSesReset, usage.fiveHour?.resetAt, prevSesUtil);
3057
3262
  this.noteCapacityWindowAdvance(account.name, 'wk', prevWkReset, usage.sevenDay?.resetAt, prevWkUtil);
@@ -3202,11 +3407,17 @@ export class AccountManager {
3202
3407
  q.providerQuotaSource = usage.source || 'zai';
3203
3408
  if (usage.ses) {
3204
3409
  if (usage.ses.utilization != null) q.providerSes = clamp01(usage.ses.utilization);
3205
- if (usage.ses.resetAt != null) q.providerSesReset = usage.ses.resetAt;
3410
+ if (usage.ses.resetAt != null) {
3411
+ q.providerSesReset = usage.ses.resetAt;
3412
+ this._noteCapWindowSpan(account, 'ses', usage.ses.resetAt);
3413
+ }
3206
3414
  }
3207
3415
  if (usage.wk) {
3208
3416
  if (usage.wk.utilization != null) q.providerWk = clamp01(usage.wk.utilization);
3209
- if (usage.wk.resetAt != null) q.providerWkReset = usage.wk.resetAt;
3417
+ if (usage.wk.resetAt != null) {
3418
+ q.providerWkReset = usage.wk.resetAt;
3419
+ this._noteCapWindowSpan(account, 'wk', usage.wk.resetAt);
3420
+ }
3210
3421
  q.weeklyAbsent = false;
3211
3422
  } else {
3212
3423
  // Weekly window absent from this plan/response — clear so a stale weekly
@@ -3880,6 +4091,9 @@ export class AccountManager {
3880
4091
  configSourced: Boolean(acctData.configSourced),
3881
4092
  secretName: acctData.secretName || null,
3882
4093
  capUtilization: _sanitizeCap(acctData.capUtilization, acctData.name),
4094
+ capMode: _capMode(acctData.capMode, _sanitizeCap(acctData.capUtilization, acctData.name, true), acctData.name),
4095
+ capCeiling: _sanitizeCap(acctData.capCeiling, acctData.name, true),
4096
+ capRampStart: _sanitizeRampStart(acctData.capRampStart, acctData.name),
3883
4097
  enabled: acctData.enabled !== false,
3884
4098
  refreshToken: acctData.refreshToken || null,
3885
4099
  expiresAt: acctData.expiresAt || null,
@@ -3945,6 +4159,7 @@ export class AccountManager {
3945
4159
  // cap the user set in the TUI.
3946
4160
  if (acctData.capUtilization !== undefined) {
3947
4161
  account.capUtilization = _sanitizeCap(acctData.capUtilization, account.name);
4162
+ account.capMode = _capMode(acctData.capMode ?? account.capMode, account.capUtilization, account.name);
3948
4163
  }
3949
4164
  if (account.status === 'error' && changed) {
3950
4165
  account.status = 'active';
@@ -3986,6 +4201,11 @@ export class AccountManager {
3986
4201
  // And the usage cap, same reasoning: a TUI-set reservation must survive both
3987
4202
  // the restart AND the next `cc all` header re-send (the upsert guard).
3988
4203
  capUtilization: a.capUtilization ?? null,
4204
+ capMode: a.capMode ?? null,
4205
+ capEffective: a.capUtilization == null ? null : {
4206
+ ses: this._effectiveCap(a, 'ses'),
4207
+ wk: this._effectiveCap(a, 'wk'),
4208
+ },
3989
4209
  }));
3990
4210
  }
3991
4211
 
@@ -4199,6 +4419,11 @@ export class AccountManager {
4199
4419
  profiles: a.profiles,
4200
4420
  priority: a.priority,
4201
4421
  capUtilization: a.capUtilization ?? null,
4422
+ capMode: a.capMode ?? null,
4423
+ capEffective: a.capUtilization == null ? null : {
4424
+ ses: this._effectiveCap(a, 'ses'),
4425
+ wk: this._effectiveCap(a, 'wk'),
4426
+ },
4202
4427
  runtime: a.runtime,
4203
4428
  status: a.status,
4204
4429
  refreshDead: Boolean(a.refreshDead),
package/src/index.js CHANGED
@@ -2113,9 +2113,15 @@ async function syncAccountsFromDisk(diskConfig, memConfig, accountManager) {
2113
2113
  // this a config-edit cap is stale until the next full reload).
2114
2114
  const diskCap = Number.isFinite(diskAcct.capUtilization) && diskAcct.capUtilization > 0 && diskAcct.capUtilization < 1
2115
2115
  ? diskAcct.capUtilization : null;
2116
- if (mgr.capUtilization !== diskCap) {
2116
+ // The MODE rides along with the cap: a hand-edited config that adds/changes a cap
2117
+ // without naming a mode gets the dynamic default, exactly as a fresh load would —
2118
+ // otherwise a hot edit would silently produce a capped account with no mode, whose
2119
+ // effective cap is the floor forever (a fixed cap wearing the new feature's name).
2120
+ const diskMode = diskCap == null ? null : (diskAcct.capMode === 'fixed' ? 'fixed' : 'dynamic');
2121
+ if (mgr.capUtilization !== diskCap || mgr.capMode !== diskMode) {
2117
2122
  mgr.capUtilization = diskCap;
2118
- console.log(`[Maxpool] Usage cap for "${mgr.name}" ${diskCap ? `set to ${Math.round(diskCap * 100)}%` : 'removed'} from config`);
2123
+ mgr.capMode = diskMode;
2124
+ console.log(`[Maxpool] Usage cap for "${mgr.name}" ${diskCap ? `set to ${Math.round(diskCap * 100)}% (${diskMode})` : 'removed'} from config`);
2119
2125
  }
2120
2126
  memConfig.accounts[memIdx] = { ...memConfig.accounts[memIdx], ...diskAcct };
2121
2127
 
package/src/server.js CHANGED
@@ -3337,8 +3337,13 @@ async function streamResponse(webStream, res, status, responseHeaders, accountIn
3337
3337
  modelEchoBuffer = modelEchoBuffer ? [...modelEchoBuffer, value] : [value];
3338
3338
  const s = decoder.decode(concatUint8(modelEchoBuffer));
3339
3339
  if (s.includes('"model"') && s.includes('\n\n')) {
3340
+ // \s* — providers serialize SSE JSON with spaces ("model": "glm-5.3"),
3341
+ // Anthropic compact ("model":"…"). The tight form shipped 2026-08-31 and never
3342
+ // matched a single real z.ai byte (all 1,546 glm rows in this very session
3343
+ // leaked through it; fixture JSON was hand-written compact, so tests stayed
3344
+ // green while production leaked. 2026-09-23.
3340
3345
  const normalized = s.replace(
3341
- /("model":")[^"]+(")/,
3346
+ /("model"\s*:\s*")[^"]+(")/,
3342
3347
  `$1${requestInfo.model.replace(/["\\]/g, '\\$&')}$2`,
3343
3348
  );
3344
3349
  out = Buffer.from(normalized, 'utf8');
package/src/tui.js CHANGED
@@ -202,12 +202,34 @@ function loadText(load) {
202
202
  * built for (max@gomokka.com) showed no cap anywhere: reported 2026-08-27, "I need
203
203
  * to be able to see whether an account has a cap or not." Yellow while the cap is
204
204
  * actively holding traffic back, dim otherwise. */
205
- function capText(a, benched) {
205
+ function capText(a, benched, am) {
206
206
  if (a?.capUtilization == null) return '';
207
- const t = `cap ${Math.round(a.capUtilization * 100)}%`;
207
+ const floorPct = Math.round(a.capUtilization * 100);
208
+ // DYNAMIC CAP (2026-09-24): the configured value is only a FLOOR — what routing
209
+ // actually holds the account to right now is the ramped effective cap, which climbs
210
+ // as the window nears its reset. Showing the floor alone would state a number the
211
+ // scheduler is not using. `cap 50%>67%` reads as "reserved 50%, currently allowing
212
+ // 67%"; the two collapse to one number while the cap sits at its floor, so an
213
+ // early-window dynamic account looks exactly like the fixed one it replaced.
214
+ const eff = capEffectivePct(am, a);
215
+ const t = (eff != null && eff !== floorPct)
216
+ ? `cap ${floorPct}%>${eff}%`
217
+ : `cap ${floorPct}%`;
208
218
  return benched ? yellow(t) : dim(t);
209
219
  }
210
220
 
221
+ /** The percentage routing is ACTUALLY enforcing on this account right now: the worse
222
+ * (lower) of its two windows' effective caps, which is the one that benches first.
223
+ * Null for a fixed cap or when the manager cannot compute one. */
224
+ function capEffectivePct(am, a) {
225
+ if (!am?._effectiveCap || a?.capMode !== 'dynamic') return null;
226
+ const vals = ['ses', 'wk']
227
+ .map(w => am._effectiveCap(a, w))
228
+ .filter(v => typeof v === 'number' && Number.isFinite(v));
229
+ if (!vals.length) return null;
230
+ return Math.round(Math.min(...vals) * 100);
231
+ }
232
+
211
233
  /** PER-ACCOUNT SETTINGS the user set by hand — the last column's whole job
212
234
  * (owner, 2026-08-27: "the last column should contain any and all settings that
213
235
  * are custom per account"). Fleet-wide settings (routing mode, peak policy) stay
@@ -223,7 +245,7 @@ function settingsTags(am, a) {
223
245
  if (am?.routingMode === 'preferred' && a?.name === am.preferredAccountName) {
224
246
  tags.push(cyan('preferred'));
225
247
  }
226
- const capTag = capText(a, capBenched(am, a));
248
+ const capTag = capText(a, capBenched(am, a), am);
227
249
  if (capTag) tags.push(capTag);
228
250
  return tags;
229
251
  }
@@ -236,7 +258,10 @@ function capBenched(am, a) {
236
258
  const q = a.quota || {};
237
259
  const ses = a.type === 'provider' ? q.providerSes : q.unified5h;
238
260
  const wk = a.type === 'provider' ? q.providerWk : q.unified7d;
239
- return !!(am?._capped?.(a, ses) || am?._capped?.(a, wk));
261
+ // Each reading is judged against ITS OWN window's cap — under the dynamic cap the
262
+ // two differ (a 5h window nearly over is lifted while the weekly is still at its
263
+ // floor), so passing the default window for both would mislabel one of them.
264
+ return !!(am?._capped?.(a, ses, 'ses') || am?._capped?.(a, wk, 'wk'));
240
265
  }
241
266
 
242
267
  export function weeklyPolicyText(am, account) {
@@ -246,7 +271,10 @@ export function weeklyPolicyText(am, account) {
246
271
  // exhausted, it's deliberately held, and labelling it "Wk exhausted 50%" (red team)
247
272
  // while the bar shows half-full misstates the owner's own setting.
248
273
  if (state === 'capped') {
249
- return yellow(`Cap ${Math.round((account.capUtilization || 0) * 100)}%`);
274
+ // The WEEKLY window's own effective cap — the number that actually benched it.
275
+ const eff = am._effectiveCap?.(account, 'wk');
276
+ const pct = Math.round((Number.isFinite(eff) ? eff : account.capUtilization || 0) * 100);
277
+ return yellow(`Cap ${pct}%`);
250
278
  }
251
279
  if (!state || state === 'unknown' || state === 'normal') return '';
252
280
  // SAY IT ONCE (2026-08-27). An account Anthropic is rejecting outright already
@@ -353,7 +381,7 @@ export function applyProviderEnabledToConfig(config, name, enabled) {
353
381
  return { changed: true, previous };
354
382
  }
355
383
 
356
- export const __tuiTest = { applyProviderEnabledToConfig, formatReset, quotaLabel, bar, emptyBar, strip, loadText, countdown, acctHeader, fitLine, providerLabel };
384
+ export const __tuiTest = { applyProviderEnabledToConfig, formatReset, quotaLabel, bar, emptyBar, strip, loadText, countdown, acctHeader, fitLine, providerLabel, weeklyPolicyText, capText, capEffectivePct };
357
385
 
358
386
  function timestamp() {
359
387
  return new Date().toLocaleTimeString('en-US', { hour12: false });
@@ -1166,9 +1194,14 @@ export class TUI {
1166
1194
  } else if (this.selAction === 'cap') {
1167
1195
  const targetIdx = this.selIdx;
1168
1196
  const current = account.name;
1169
- const existing = this.am.accounts[targetIdx]?.capUtilization;
1197
+ const existingAcct = this.am.accounts[targetIdx];
1198
+ const existing = existingAcct?.capUtilization;
1199
+ const existingMode = existingAcct?.capMode;
1200
+ const nowLabel = existing
1201
+ ? `${Math.round(existing * 100)}%${existingMode === 'dynamic' ? ' dynamic' : ' fixed'}`
1202
+ : 'off';
1170
1203
  this.mode = 'input';
1171
- this.inputPrompt = `Usage cap % for "${current}" (1-99, 0 = off, now ${existing ? Math.round(existing * 100) + '%' : 'off'})`;
1204
+ this.inputPrompt = `Usage cap for "${current}" — NN = dynamic floor (rises near reset), fNN = fixed, 0 = off, now ${nowLabel}`;
1172
1205
  this.inputBuf = '';
1173
1206
  this.inputSensitive = false;
1174
1207
  this.inputCb = value => this._doSetCap(targetIdx, String(value || '').trim());
@@ -1359,35 +1392,50 @@ export class TUI {
1359
1392
  if (!account) { this._addLog('Account no longer exists'); return; }
1360
1393
  const v = String(raw || '').trim().toLowerCase();
1361
1394
  const off = v === '' || v === '0' || v === 'off' || v === '100';
1395
+ // MODE PREFIX/SUFFIX: a bare number means the DYNAMIC cap (the default since
1396
+ // 2026-09-24 — the floor that lifts as the window nears reset); `f` marks the
1397
+ // fixed cap explicitly. `d` is accepted as the explicit dynamic spelling so the
1398
+ // two modes are symmetric to type.
1399
+ const explicitFixed = /^f/.test(v) || /f$/.test(v);
1362
1400
  let pct = null;
1363
1401
  if (!off) {
1364
- pct = parseInt(v, 10);
1402
+ const digits = v.replace(/[fd]/g, '');
1403
+ pct = parseInt(digits, 10);
1365
1404
  if (!Number.isInteger(pct) || pct < 1 || pct > 99) {
1366
- this._addLog(`Usage cap must be 1-99 (or 0 to remove) — got "${raw}"`);
1405
+ this._addLog(`Usage cap must be 1-99, optionally f-prefixed for fixed (0 to remove) — got "${raw}"`);
1367
1406
  return;
1368
1407
  }
1369
1408
  }
1370
1409
  const cap = off ? null : pct / 100;
1410
+ const mode = cap == null ? null : (explicitFixed ? 'fixed' : 'dynamic');
1371
1411
 
1372
1412
  const loc = this._configLocation(account);
1373
1413
  if (loc) {
1374
1414
  const prev = this.config[loc.array][loc.index].capUtilization ?? null;
1415
+ const prevMode = this.config[loc.array][loc.index].capMode ?? null;
1375
1416
  if (cap == null) delete this.config[loc.array][loc.index].capUtilization;
1376
1417
  else this.config[loc.array][loc.index].capUtilization = cap;
1418
+ if (mode == null) delete this.config[loc.array][loc.index].capMode;
1419
+ else this.config[loc.array][loc.index].capMode = mode;
1377
1420
  try {
1378
1421
  await this.saveConfig(this.config);
1379
1422
  } catch (error) {
1380
1423
  // rollback both config and (below) skip the live apply
1381
1424
  if (prev == null) delete this.config[loc.array][loc.index].capUtilization;
1382
1425
  else this.config[loc.array][loc.index].capUtilization = prev;
1426
+ if (prevMode == null) delete this.config[loc.array][loc.index].capMode;
1427
+ else this.config[loc.array][loc.index].capMode = prevMode;
1383
1428
  throw error;
1384
1429
  }
1385
1430
  }
1386
1431
  // No loc: a runtime provider — in-memory + state.json persistence (same as enabled).
1387
1432
  account.capUtilization = cap;
1433
+ account.capMode = mode;
1388
1434
  this._addLog(cap == null
1389
1435
  ? `Usage cap removed for "${account.name}" — fully utilized`
1390
- : `Usage cap ${pct}% set for "${account.name}" — the proxy stops routing to it at ${pct}% of the 5h and weekly windows`);
1436
+ : mode === 'dynamic'
1437
+ ? `Dynamic cap ${pct}% set for "${account.name}" — it keeps ${100 - pct}% free early in each window, then opens up as the window nears its reset so nothing is stranded`
1438
+ : `Fixed cap ${pct}% set for "${account.name}" — the proxy stops routing to it at ${pct}% of the 5h and weekly windows`);
1391
1439
  }
1392
1440
 
1393
1441
  async _doRename(idx, newName) {