maxpool 1.24.5 → 1.24.7

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.24.5",
3
+ "version": "1.24.7",
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",
package/src/index.js CHANGED
@@ -748,6 +748,27 @@ async function serverWorkerCommand() {
748
748
  // background probe has gone stale (> 3× interval since last success).
749
749
  accountManager.quotaProbeIntervalMs = probeSeconds * 1000;
750
750
 
751
+ // SECRET RE-RESOLVE LOOP (2026-10-05). Provider keys resolve ONCE at startup; a
752
+ // transient gcloud/ADC failure at that moment benched all four z.ai providers as
753
+ // "secret-unresolved" permanently (measured 2026-10-05 20:18 startup: cache had
754
+ // been rewritten without the RESTRICTED_* entries, the gcloud path failed, and
755
+ // nothing retried — every GLM request 429'd "no_eligible_route" until restart).
756
+ // Re-attempt unresolved providers every 5 min until they resolve; a resolved
757
+ // provider is never touched again.
758
+ {
759
+ const { pendingProviders, reresolveProviders } = await import('./secret-resolver.js');
760
+ if (pendingProviders(accountManager.accounts).length) {
761
+ const secretTimer = setInterval(async () => {
762
+ if (!pendingProviders(accountManager.accounts).length) { clearInterval(secretTimer); return; }
763
+ try {
764
+ const fixed = await reresolveProviders(accountManager.accounts);
765
+ if (fixed) console.log(`[Maxpool] Secret re-resolve: ${fixed} provider(s) recovered`);
766
+ } catch { /* next tick */ }
767
+ }, 5 * 60_000);
768
+ secretTimer.unref?.();
769
+ }
770
+ }
771
+
751
772
  // Seed the running version immediately so the TUI header always shows it, even
752
773
  // before (or without) the npm update check. The cold worker's update check below
753
774
  // fills in latest/hasUpdate.
package/src/prober.js CHANGED
@@ -13,8 +13,12 @@ import { listResetGrants, claimResetGrant } from './claude-reset-grants.js';
13
13
  import { decideZai, decideClaude } from './reset-policy.js';
14
14
 
15
15
  export class Prober {
16
- constructor(accountManager, { intervalMs = 0, probeFn = fetchUsage, providerProbeFn = fetchProviderUsage, timeoutMs = 10_000, log = console.log, usageGapMs = null } = {}) {
16
+ constructor(accountManager, { intervalMs = 0, probeFn = fetchUsage, providerProbeFn = fetchProviderUsage, grantsFn = listResetGrants, timeoutMs = 10_000, log = console.log, usageGapMs = null } = {}) {
17
17
  this.am = accountManager;
18
+ // Injectable like probeFn: the default hits api.anthropic.com, so a harness that
19
+ // doesn't stub it makes a REAL network call per oauth sweep (a test then passes
20
+ // or fails on internet latency — prober-sweep-liveness, 2026-10-06).
21
+ this.grantsFn = grantsFn;
18
22
  this.intervalMs = intervalMs;
19
23
  this.probeFn = probeFn;
20
24
  this.providerProbeFn = providerProbeFn;
@@ -247,7 +251,7 @@ export class Prober {
247
251
  // OWNER GATE as z.ai cards: a DISABLED account is never listed or claimed.
248
252
  try {
249
253
  if (account.enabled !== false && account.credential && this.am.applyResetGrants) {
250
- const listing = await this._withTimeout(listResetGrants(account.credential));
254
+ const listing = await this._withTimeout(this.grantsFn(account.credential));
251
255
  if (listing) {
252
256
  this.am.applyResetGrants(account.index, listing);
253
257
  const act = decideClaude(account, listing);
@@ -107,3 +107,30 @@ export async function resolveSecrets(secretNames, opts = {}) {
107
107
  }
108
108
  return out;
109
109
  }
110
+
111
+ /**
112
+ * Re-resolve config-sourced providers whose key never loaded. Accounts carry their
113
+ * key in `credential` (upsertRuntimeAccount maps authToken -> credential); the first
114
+ * version of this loop (v1.24.6) tested and wrote `authToken`, a field accounts never
115
+ * hold — so every provider looked unresolved forever, its status was reset to
116
+ * 'unknown' every 5 min, and a genuinely-unresolved one was never actually repaired.
117
+ * Returns the number of providers recovered.
118
+ */
119
+ export function pendingProviders(accounts) {
120
+ return accounts.filter(a => a.configSourced && a.type === 'provider' && a.secretName && !a.credential);
121
+ }
122
+
123
+ export async function reresolveProviders(accounts, resolve = resolveSecrets) {
124
+ const pending = pendingProviders(accounts);
125
+ if (!pending.length) return 0;
126
+ const resolved = await resolve(pending.map(a => a.secretName));
127
+ let fixed = 0;
128
+ for (const a of pending) {
129
+ const tok = resolved[a.secretName];
130
+ if (!tok) continue;
131
+ a.credential = tok;
132
+ if (a.status === 'error' && a.lastError === 'secret-unresolved') { a.status = 'active'; a.lastError = null; }
133
+ fixed++;
134
+ }
135
+ return fixed;
136
+ }