maxpool 1.8.6 → 1.8.8

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.8.6",
3
+ "version": "1.8.8",
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",
@@ -3689,6 +3689,12 @@ export class AccountManager {
3689
3689
  // Running version + npm update state (set at startup by maybeCheckForUpdate).
3690
3690
  // null until the check resolves; `current` is known even offline.
3691
3691
  version: this.versionInfo || null,
3692
+ // The version whose CODE is EXECUTING. `version.current` is a package.json DISK
3693
+ // read, so after a self-install (or on an npm-link'd checkout) it reports the
3694
+ // newest INSTALLED build, not the running one — a post-deploy check keyed on it
3695
+ // verifies the wrong thing (measured 2026-08-23: reported 1.8.7 while executing
3696
+ // 1.8.6). This field is the one to assert on.
3697
+ runningVersion: this.runningVersion || null,
3692
3698
  currentAccount: this.accounts[this.currentIndex]?.name,
3693
3699
  switchThreshold: this.switchThreshold,
3694
3700
  routing: {
@@ -40,9 +40,9 @@ const MAX_DAY_BUCKETS = 10;
40
40
  // CLAMPS a future endedAt to startedAt so the stored row itself stops violating
41
41
  // invariants after migration, not just the averages.
42
42
  function demote(c) {
43
- const fixed = { ...c, complete: false, partialReason: 'pre-v3-unverified' };
44
- if (fixed.endedAt > Date.now() + 60_000) fixed.endedAt = fixed.startedAt;
45
- return fixed;
43
+ // Timestamp repair is NOT done here — the load-time pass below handles a future
44
+ // endedAt at every schema version, so doing it twice would be two places to fix.
45
+ return { ...c, complete: false, partialReason: 'pre-v3-unverified' };
46
46
  }
47
47
 
48
48
  export class CapacityLedger {
@@ -88,7 +88,19 @@ export class CapacityLedger {
88
88
  const a = { ses: { open: null, closed: [] }, wk: { open: null, closed: [] }, days: {} };
89
89
  for (const w of ['ses', 'wk']) {
90
90
  if (rec[w]?.open) a[w].open = { ...rec[w].open };
91
- if (Array.isArray(rec[w]?.closed)) a[w].closed = rec[w].closed.slice(-MAX_CYCLES_PER_WINDOW);
91
+ if (Array.isArray(rec[w]?.closed)) {
92
+ // LOAD-TIME REPAIR, applied at EVERY version — not inside the v2→v3 branch,
93
+ // which is where it was first (wrongly) placed: a future-dated row can be
94
+ // written by any build whose writer had the bug, and the payload it lands in
95
+ // is then already current, so a version-gated repair never runs. Measured
96
+ // 2026-08-23: a row ending 19:54 survived the v3 migration untouched because
97
+ // the state was already v3. A cycle cannot end after now; such a row is
98
+ // corrupt, so demote it and clamp it rather than let it re-alert until evicted.
99
+ a[w].closed = rec[w].closed.slice(-MAX_CYCLES_PER_WINDOW).map(c =>
100
+ (c.endedAt > Date.now() + 60_000)
101
+ ? { ...c, endedAt: c.startedAt, complete: false, partialReason: 'repaired-future-endedAt' }
102
+ : c);
103
+ }
92
104
  }
93
105
  if (rec.days && typeof rec.days === 'object') {
94
106
  for (const [d, v] of Object.entries(rec.days)) a.days[d] = { ...v };
package/src/index.js CHANGED
@@ -33,7 +33,7 @@ import { loginOAuth, fetchProfile, refreshAccessToken, isTokenExpiringSoon, toke
33
33
  import { TUI } from './tui.js';
34
34
  import { RestartController } from './restart-controller.js';
35
35
  import { resolveAccounts } from './account-config.js';
36
- import { maybeCheckForUpdate, getCurrentVersion, markApplied, clearQuarantine } from './updater.js';
36
+ import { maybeCheckForUpdate, getCurrentVersion, markApplied, clearQuarantine, getBootVersion } from './updater.js';
37
37
  import {
38
38
  runReloadBaton,
39
39
  RELOAD_SWAPPED, RELOAD_ROLLED_BACK,
@@ -1259,6 +1259,8 @@ async function serverWorkerCommand() {
1259
1259
  // pass the real config so they still respect the user's autoUpdate choice.
1260
1260
  const cfg = forceInstall ? { ...config, autoUpdate: true } : config;
1261
1261
  const r = await maybeCheckForUpdate(cfg, notifyUpdate, info => { accountManager.versionInfo = info; }, { announce });
1262
+ // Capture the EXECUTING version for /maxpool/status (see AccountManager.getStatus).
1263
+ accountManager.runningVersion = getBootVersion();
1262
1264
  apply(r);
1263
1265
  return r;
1264
1266
  } catch { return undefined; /* update path is best-effort; never break the proxy */ }
package/src/updater.js CHANGED
@@ -85,6 +85,17 @@ let _lastAttemptedTarget = null;
85
85
  /** Test-only: reset the module's version-tracking state between cases. */
86
86
  export function __resetUpdaterState() { _bootVersion = undefined; _lastAttemptedTarget = null; }
87
87
 
88
+ /** The version whose CODE this process is EXECUTING (captured at boot, before any
89
+ * self-install can rewrite the disk). Everything user-facing that says "current"
90
+ * should use THIS — a disk read reports the newest INSTALLED version, which after a
91
+ * background self-install (or on an npm-link'd dev checkout whose package.json moves
92
+ * with every commit) is not what is running. Measured 2026-08-23: the status endpoint
93
+ * answered 1.8.7 for a process executing 1.8.6, and a post-deploy check keyed on that
94
+ * number verified the wrong build. */
95
+ export function getBootVersion() {
96
+ return _bootVersion ?? null; // null only before the first maybeCheckForUpdate
97
+ }
98
+
88
99
  /** Mark a version as ATTEMPTED-to-apply. The caller calls this at the moment it triggers
89
100
  * the reload — BEFORE the reload — so a rolled-back target is quarantined (advance-only).
90
101
  * Kept as the caller's action (not a side effect of maybeCheckForUpdate) so a caller that