maxpool 1.5.13 → 1.5.14

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.5.13",
3
+ "version": "1.5.14",
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",
@@ -1617,10 +1617,27 @@ export class AccountManager {
1617
1617
  return 'normal';
1618
1618
  }
1619
1619
 
1620
+ // True only when the account is REJECTED ACCOUNT-WIDE — a 'rejected' unified
1621
+ // status CORROBORATED by an actually-exhausted unified bucket. A bare 'rejected'
1622
+ // with healthy unified buckets is a PER-MODEL sub-limit rejection (e.g. Fable
1623
+ // weekly, tracked in scopedWeekly) that Anthropic reports on the unified-status
1624
+ // header — it must NOT bench or "block" the whole account, which still has
1625
+ // headroom for its other models. Guards both routing (_weeklyRawState) and the
1626
+ // TUI "blocked" label against the per-model→account-wide conflation. NOTE: this
1627
+ // is a display/inter-429 hint, not the primary bench — a genuinely-dead account
1628
+ // is benched account-wide by markRateLimited (status='throttled') on its next 429.
1629
+ _isAccountWideRejected(account) {
1630
+ const q = account?.quota || {};
1631
+ if (q.unifiedStatus !== 'rejected') return false;
1632
+ const floor = this.scheduler.weeklyExhaustedThreshold;
1633
+ return (Number.isFinite(q.unified5h) && q.unified5h >= floor)
1634
+ || (Number.isFinite(q.unified7d) && q.unified7d >= floor);
1635
+ }
1636
+
1620
1637
  _weeklyRawState(account) {
1621
1638
  const q = account.quota;
1622
1639
  this._clearExpiredQuotas(account);
1623
- if (q.unifiedStatus === 'rejected') return 'exhausted';
1640
+ if (this._isAccountWideRejected(account)) return 'exhausted';
1624
1641
  if (q.unified7d == null) return 'unknown';
1625
1642
 
1626
1643
  const used = clamp01(q.unified7d);
@@ -1727,7 +1744,18 @@ export class AccountManager {
1727
1744
  }
1728
1745
 
1729
1746
  const uStatus = headers['anthropic-ratelimit-unified-status'];
1730
- if (uStatus) account.quota.unifiedStatus = uStatus;
1747
+ if (uStatus) {
1748
+ // 'rejected' with HEALTHY unified buckets is a PER-MODEL sub-limit rejection
1749
+ // (e.g. Fable weekly) — Anthropic surfaces it on the account-wide status
1750
+ // header, but recording it account-wide would bench the account for EVERY
1751
+ // model and render it "blocked" (the per-model cap is tracked in scopedWeekly).
1752
+ // Only honor 'rejected' when a unified bucket corroborates a real account block.
1753
+ const floor = this.scheduler.weeklyExhaustedThreshold;
1754
+ const unifiedExhausted =
1755
+ (Number.isFinite(account.quota.unified5h) && account.quota.unified5h >= floor)
1756
+ || (Number.isFinite(account.quota.unified7d) && account.quota.unified7d >= floor);
1757
+ account.quota.unifiedStatus = (uStatus === 'rejected' && !unifiedExhausted) ? 'allowed' : uStatus;
1758
+ }
1731
1759
 
1732
1760
  // Standard rate limits (API key accounts)
1733
1761
  const tokensLimit = parseInt(headers['anthropic-ratelimit-tokens-limit'], 10);
package/src/tui.js CHANGED
@@ -925,14 +925,12 @@ export class TUI {
925
925
  if (a.enabled !== false && upstreamBlocking && a.status === 'active') {
926
926
  effectiveStatus = a.inFlight > 0 ? 'probing' : 'waiting';
927
927
  }
928
- // Anthropic is actively REJECTING this account right now (e.g. a per-model weekly
929
- // sub-limit the general utilization % doesn't expose). It's unusable — surface
930
- // that instead of a benign green "active", so a low weekly % (the bar keeps its
931
- // true value) is never misread as available headroom.
932
- // Keys on a.status (not effectiveStatus) so a rejected account reads 'blocked'
933
- // even inside an upstream-throttle window (where it would otherwise show
934
- // probing/waiting) — a rejected account is unusable, not part of the recovery.
935
- if (a.enabled !== false && a.quota?.unifiedStatus === 'rejected' && a.status === 'active') {
928
+ // "blocked" = Anthropic is rejecting the WHOLE account (a 'rejected' unified
929
+ // status corroborated by an exhausted unified bucket). A per-model cap (Fable)
930
+ // is NOT account-wide — it shows as the separate "… maxed" tag, leaving the
931
+ // status column truthful. Keys on a.status (not effectiveStatus) so a genuine
932
+ // block wins even inside an upstream-throttle window.
933
+ if (a.enabled !== false && a.status === 'active' && this.am._isAccountWideRejected?.(a)) {
936
934
  effectiveStatus = 'blocked';
937
935
  }
938
936
  switch (effectiveStatus) {