maxpool 1.5.42 → 1.5.44

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/README.md CHANGED
@@ -270,7 +270,7 @@ TEAMCLAUDE_CONFIG=./my-config.json maxpool server
270
270
  "weeklySoftThreshold": 0.65,
271
271
  "weeklyReserveThreshold": 0.85,
272
272
  "weeklyCriticalThreshold": 0.95,
273
- "weeklyExhaustedThreshold": 0.985,
273
+ "weeklyExhaustedThreshold": 0.999,
274
274
  "weeklyBurnDebtWeight": 0.6
275
275
  },
276
276
  "retry": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.5.42",
3
+ "version": "1.5.44",
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",
@@ -91,7 +91,12 @@ const DEFAULT_SCHEDULER = {
91
91
  weeklySoftThreshold: 0.65,
92
92
  weeklyReserveThreshold: 0.85,
93
93
  weeklyCriticalThreshold: 0.95,
94
- weeklyExhaustedThreshold: 0.985,
94
+ // Use-it-or-lose-it (2026-07-24): bench only at 99.9%, not 98.5% — the weekly quota
95
+ // resets, so reserving the top ~1.5% just wastes it. The thin 0.1% floor is the ONE
96
+ // remaining guard: don't fire a request at an account whose own header already says
97
+ // it's essentially full (a near-guaranteed-waste hard-429). A real 429 sets util≈1.0
98
+ // and still benches here. Critical (0.95-0.999) stays last-resort-only (pass 2).
99
+ weeklyExhaustedThreshold: 0.999,
95
100
  weeklyBurnDebtWeight: 0.6,
96
101
  // Routing-cost tuning (lower cost = preferred). The goal is to AVOID
97
102
  // short-term (rate/concurrency) throttling by spreading load across healthy
package/src/config.js CHANGED
@@ -90,7 +90,9 @@ export function createDefaultConfig() {
90
90
  weeklySoftThreshold: 0.65,
91
91
  weeklyReserveThreshold: 0.85,
92
92
  weeklyCriticalThreshold: 0.95,
93
- weeklyExhaustedThreshold: 0.985,
93
+ // Use-it-or-lose-it: bench only at 99.9% (the weekly quota resets, so reserving
94
+ // the top ~1.5% wastes it). A real 429 sets util≈1.0 and still benches.
95
+ weeklyExhaustedThreshold: 0.999,
94
96
  // Cross-PROVIDER fallback policy for 'cc all' (profile=all): whether a session
95
97
  // may be served by a provider family other than its home (Claude → GLM/Kimi).
96
98
  // 'never' — (DEFAULT) a Claude Code session stays on Anthropic; no
package/src/index.js CHANGED
@@ -12,7 +12,7 @@ import { loginOAuth, fetchProfile, refreshAccessToken, isTokenExpiringSoon, toke
12
12
  import { TUI } from './tui.js';
13
13
  import { RestartController } from './restart-controller.js';
14
14
  import { resolveAccounts } from './account-config.js';
15
- import { maybeCheckForUpdate, getCurrentVersion, markApplied } from './updater.js';
15
+ import { maybeCheckForUpdate, getCurrentVersion, markApplied, clearQuarantine } from './updater.js';
16
16
  import {
17
17
  runReloadBaton,
18
18
  RELOAD_SWAPPED, RELOAD_ROLLED_BACK,
@@ -23,10 +23,20 @@ import {
23
23
  const args = process.argv.slice(2);
24
24
  const command = args[0];
25
25
  const SERVER_RESTART_EXIT_CODE = 75;
26
+ // Reload readiness handshake budget. On a loaded machine (Bitdefender, many node procs,
27
+ // a local LLM) a fresh worker can need well over 10s to boot + signal ready; too tight →
28
+ // the seamless reload rolls back and the update/restart silently never lands. Generous +
29
+ // env-tunable; covers BOTH the ready and the takeover waits in runReloadBaton.
30
+ const RELOAD_READY_MS = Math.max(10_000, Number(process.env.MAXPOOL_RELOAD_READY_MS) || 30_000);
26
31
  // If a seamless reload doesn't release us (→ take over) within this window, the new
27
- // worker rolled back — self-heal admission so we don't 503 forever. > the baton
28
- // readiness timeout (10s) + margin.
29
- const RELOAD_ROLLBACK_SELFHEAL_MS = Math.max(15_000, Number(process.env.MAXPOOL_RELOAD_SELFHEAL_MS) || 30_000);
32
+ // worker rolled back — self-heal admission so we don't 503 forever. Must sit ABOVE the
33
+ // baton readiness budget + margin so it never fires WHILE the baton is still handshaking.
34
+ // Default (RELOAD_READY_MS + 20s = 50s) sits comfortably above the readiness budget, so
35
+ // the self-heal never fires mid-handshake. An explicit MAXPOOL_RELOAD_SELFHEAL_MS override
36
+ // is honored down to a 15s floor (used by the reload integration test) — a sub-ready
37
+ // override only risks a spurious "rolled back" log + brief admission flap (no corruption:
38
+ // the new worker holds no lease until takeover), never a false double-writer.
39
+ const RELOAD_ROLLBACK_SELFHEAL_MS = Math.max(15_000, Number(process.env.MAXPOOL_RELOAD_SELFHEAL_MS) || RELOAD_READY_MS + 20_000);
30
40
  // Seamless-reload drain cap. On a seamless reload the NEW worker already serves
31
41
  // ALL new traffic while the OLD worker only finishes its own in-flight requests,
32
42
  // so a long old-worker drain has zero request-facing cost — let a long streaming
@@ -326,6 +336,10 @@ async function supervisorCommand() {
326
336
  // listener to cover the cutover gap, then hand THAT live handle to the
327
337
  // new worker. The new worker's MSG_PRIMARY then closes it again.
328
338
  prepareHandle: async () => { await relistenMaster(); return masterServer; },
339
+ // Generous, env-tunable readiness/takeover budgets — the 10s default rolled back
340
+ // on this user's loaded Mac (the update never landed). See RELOAD_READY_MS.
341
+ readyTimeoutMs: RELOAD_READY_MS,
342
+ takeoverTimeoutMs: RELOAD_READY_MS,
329
343
  log: msg => console.log(`[Maxpool] ${msg}`),
330
344
  });
331
345
 
@@ -1083,6 +1097,10 @@ async function serverWorkerCommand() {
1083
1097
  if (updateInFlight) { notifyUpdate('Update check already running'); return; }
1084
1098
  if (!hasLease) { notifyUpdate('Updates run on the primary worker only'); return; }
1085
1099
  if (config?.updateCheck === false) { notifyUpdate('Update checks are disabled in config'); return; }
1100
+ // An EXPLICIT manual apply always re-attempts — clear any quarantine a prior auto-reload
1101
+ // left (e.g. a rollback from a too-tight readiness timeout), so 'u'→'c' can't dead-end on
1102
+ // "already attempted — will retry only a newer release".
1103
+ clearQuarantine();
1086
1104
  notifyUpdate('Checking for updates…');
1087
1105
  const r = await runUpdateCheck({ announce: true, apply: applyNow, forceInstall: true });
1088
1106
  if (r && !r.hasUpdate) notifyUpdate('Already on the latest version');
package/src/server.js CHANGED
@@ -1348,16 +1348,22 @@ function classifyRateLimit(account, headers, body, opts = {}) {
1348
1348
  // genuine account cap with stripped headers must bench the whole account, not one
1349
1349
  // model). Neither bucket may be at/above the exhaustion floor.
1350
1350
  const haveUnifiedEvidence = Number.isFinite(weekly) || Number.isFinite(fiveHour);
1351
+ // Exhaustion floor for 429-SCOPE classification — kept in sync with the scheduler's
1352
+ // weeklyExhaustedThreshold (0.999, use-it-or-lose-it). A 429 whose unified buckets are
1353
+ // still below the floor isn't weekly-exhaustion, so a model-family + long-retry-after
1354
+ // 429 benches only that model, not the whole (still-usable) account — matching routing,
1355
+ // which now treats 0.95-0.999 as usable (critical), not benched.
1356
+ const EXHAUSTION_FLOOR = 0.999;
1351
1357
  const unifiedNotExhausted =
1352
- (!Number.isFinite(weekly) || weekly < 0.985) && (!Number.isFinite(fiveHour) || fiveHour < 0.985);
1358
+ (!Number.isFinite(weekly) || weekly < EXHAUSTION_FLOOR) && (!Number.isFinite(fiveHour) || fiveHour < EXHAUSTION_FLOOR);
1353
1359
  const modelScope =
1354
1360
  (fam && haveUnifiedEvidence && unifiedNotExhausted && Number.isFinite(retryAfter) && retryAfter >= 30 * 60)
1355
1361
  ? fam : null;
1356
1362
 
1357
1363
  const quotaHeaderExhaustion =
1358
1364
  unifiedStatus === 'rejected'
1359
- || (Number.isFinite(fiveHour) && fiveHour >= 0.985)
1360
- || (Number.isFinite(weekly) && weekly >= 0.985)
1365
+ || (Number.isFinite(fiveHour) && fiveHour >= EXHAUSTION_FLOOR)
1366
+ || (Number.isFinite(weekly) && weekly >= EXHAUSTION_FLOOR)
1361
1367
  || (headers['anthropic-ratelimit-tokens-remaining'] != null && tokensRemaining <= 0)
1362
1368
  || (headers['anthropic-ratelimit-requests-remaining'] != null && requestsRemaining <= 0);
1363
1369
  if (quotaHeaderExhaustion) return { scope: 'account', fingerprint: null, modelScope };
package/src/tui.js CHANGED
@@ -1294,7 +1294,7 @@ export class TUI {
1294
1294
  // still usable, "Fable maxed" (red) ONLY at genuine exhaustion. This is the
1295
1295
  // SAME predicate the router benches on (_scopedExhausted), so "maxed" renders
1296
1296
  // iff the model is actually benched — 90%/critical is no longer mislabelled.
1297
- const exhaustedFloor = this.am.scheduler?.weeklyExhaustedThreshold ?? 0.985;
1297
+ const exhaustedFloor = this.am.scheduler?.weeklyExhaustedThreshold ?? 0.999;
1298
1298
  const reserveFloor = this.am.scheduler?.weeklyReserveThreshold ?? 0.85;
1299
1299
  const scopedTags = [];
1300
1300
  for (const [fam, e] of Object.entries(q.scopedWeekly || {})) {
package/src/updater.js CHANGED
@@ -95,6 +95,14 @@ export function markApplied(version) {
95
95
  }
96
96
  }
97
97
 
98
+ /** Clear the applied-version quarantine floor. The AUTO path quarantines a version it
99
+ * ATTEMPTED (markApplied, before the reload) so a genuinely boot-broken release can't
100
+ * reload-loop — but a reload that rolled back for a NON-version reason (e.g. a slow
101
+ * readiness handshake on a loaded machine) then wrongly strands a perfectly-good version
102
+ * ("already attempted — will retry only a newer release"). An EXPLICIT manual apply calls
103
+ * this first so the user can always re-attempt the current latest. */
104
+ export function clearQuarantine() { _lastAttemptedTarget = null; }
105
+
98
106
  /**
99
107
  * Check for an update; with `config.autoUpdate` also self-install; with
100
108
  * `config.autoApply` SIGNAL that the caller should seamlessly reload to APPLY it