maxpool 1.5.19 → 1.5.20

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.19",
3
+ "version": "1.5.20",
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",
@@ -974,16 +974,52 @@ export class AccountManager {
974
974
  */
975
975
  refreshExpiredQuotas() {
976
976
  let changed = false;
977
+ const now = Date.now();
977
978
  const sessionReset = [];
978
979
  for (const account of this.accounts) {
979
980
  const r = this._clearExpiredQuotas(account);
980
981
  if (r.changed) changed = true;
981
982
  if (r.session) sessionReset.push(account);
983
+ this._clearRecoveredNetworkError(account, now);
982
984
  }
983
985
  if (sessionReset.length) this._switchOnSessionReset(sessionReset);
984
986
  return changed;
985
987
  }
986
988
 
989
+ /**
990
+ * Clear a fully-healed transient error so a long-gone blip stops showing as a
991
+ * phantom "Err" in the TUI. A network blip (markTransientFailure network:true)
992
+ * and an expired upstream_throttled window both set `lastError` WITHOUT bumping
993
+ * `consecutiveFailures`; once the account is active again with no live backoff
994
+ * window, that error is history. Runs for EVERY account on the display/request
995
+ * refresh — required (not just `_isAvailable`, which never evaluates an idle
996
+ * fallback provider that Claude-health keeps out of the candidate set, so its
997
+ * blip lingered ~100 min until restart). Mirrors the rate_limited /
998
+ * upstream_throttled recovery clears in `_isAvailable`.
999
+ *
1000
+ * The `consecutiveFailures > 0` guard is load-bearing: a genuinely-flaky account
1001
+ * (markResult failure — windowless, status active, counter bumped) must KEEP its
1002
+ * error visible. Only the fleet-wide-blip paths leave the counter at 0.
1003
+ */
1004
+ _clearRecoveredNetworkError(account, now = Date.now()) {
1005
+ if (!account.lastError) return;
1006
+ if (account.status !== 'active') return; // throttled / error / exhausted keep their error
1007
+ if (account.consecutiveFailures > 0) return; // an ongoing per-account fault stays visible
1008
+ const blocked = Math.max(
1009
+ account.cooldownUntil || 0,
1010
+ account.rateLimitedUntil || 0,
1011
+ account.provisionalUpstreamUntil || 0,
1012
+ );
1013
+ if (blocked && now < blocked) return; // still in a backoff window → keep showing why
1014
+ account.lastError = null;
1015
+ account.lastErrorAt = null;
1016
+ // Mirror _isAvailable's recovery: drop the now-past backoff windows + fingerprint
1017
+ // so getStatus() doesn't emit a stale past cooldown and no fingerprint dangles.
1018
+ account.cooldownUntil = null;
1019
+ account.provisionalUpstreamUntil = null;
1020
+ account.provisionalUpstreamFingerprint = null;
1021
+ }
1022
+
987
1023
  /**
988
1024
  * Given accounts whose session quota just reset, switch to the one whose
989
1025
  * weekly limit expires soonest — but only if that is sooner than the current