maxpool 1.24.23 → 1.24.24

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tui.js +42 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.24.23",
3
+ "version": "1.24.24",
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/tui.js CHANGED
@@ -271,23 +271,31 @@ function settingsTags(am, a) {
271
271
  return tags;
272
272
  }
273
273
 
274
- /** True when the reservation is what is currently keeping traffic off this account
275
- * — either window at or past the cap. Reads the manager's own predicate so the
276
- * label can never disagree with routing. */
277
- /** The 5h window is used up past the bench — same predicate the router's
278
- * _isSessionQuotaUnavailable applies, without its expiry-clearing side effect (a
279
- * window whose reset has passed is NOT full: the router clears it on next use). */
274
+ /** The session (5h) window is what keeps traffic off this account: a pure mirror of
275
+ * the router's _isSessionQuotaUnavailable, without its expiry-clearing write.
276
+ * Which readings expire on their reset is the ROUTER's rule, copied here: it clears
277
+ * the Claude 5h reading and the API-key limits when their reset passes, but NEVER
278
+ * a provider's (GLM/Kimi) providerSes — that stays until the next usage probe, so a
279
+ * provider past its reset stamp is still refused and must not read "active". */
280
280
  function sessionFull(am, a) {
281
281
  const q = a?.quota || {};
282
282
  const now = Date.now();
283
283
  const bench = am?._sessionBenchThreshold?.(a, now);
284
284
  if (!Number.isFinite(bench)) return false;
285
- const live = (util, reset) => util != null && !(reset && now >= reset) && util >= bench;
286
- return a.type === 'provider'
287
- ? live(q.providerSes, q.providerSesReset)
288
- : live(q.unified5h, q.unified5hReset);
285
+ const expired = reset => reset != null && now >= reset;
286
+ if (q.unified5h != null && !expired(q.unified5hReset) && q.unified5h >= bench) return true;
287
+ const apiLive = !(q.resetsAt && now >= new Date(q.resetsAt).getTime());
288
+ if (apiLive && q.tokensLimit != null && q.tokensRemaining != null
289
+ && 1 - q.tokensRemaining / q.tokensLimit >= bench) return true;
290
+ if (apiLive && q.requestsLimit != null && q.requestsRemaining != null
291
+ && 1 - q.requestsRemaining / q.requestsLimit >= bench) return true;
292
+ if (q.providerSes != null && q.providerSes >= bench) return true;
293
+ return false;
289
294
  }
290
295
 
296
+ /** True when the reservation is what is currently keeping traffic off this account
297
+ * — either window at or past the cap. Reads the manager's own predicate so the
298
+ * label can never disagree with routing. */
291
299
  function capBenched(am, a) {
292
300
  if (a?.capUtilization == null) return false;
293
301
  const q = a.quota || {};
@@ -296,7 +304,13 @@ function capBenched(am, a) {
296
304
  // Each reading is judged against ITS OWN window's cap — under the dynamic cap the
297
305
  // two differ (a 5h window nearly over is lifted while the weekly is still at its
298
306
  // floor), so passing the default window for both would mislabel one of them.
299
- return !!(am?._capped?.(a, ses, 'ses') || am?._capped?.(a, wk, 'wk'));
307
+ if (am?._capped?.(a, ses, 'ses') || am?._capped?.(a, wk, 'wk')) return true;
308
+ // Providers: the router's weekly gate judges max(5h, weekly) against the WEEKLY
309
+ // cap (_weeklyRawState), so a 5h reading over the weekly cap benches it too.
310
+ if (a.type === 'provider' && (ses != null || wk != null)) {
311
+ return !!am?._capped?.(a, Math.max(ses ?? 0, wk ?? 0), 'wk');
312
+ }
313
+ return false;
300
314
  }
301
315
 
302
316
  export function weeklyPolicyText(am, account) {
@@ -1014,7 +1028,9 @@ export class TUI {
1014
1028
 
1015
1029
 
1016
1030
  _keyRouting(k) {
1017
- if (k === 'a') {
1031
+ if (k === 'a' && this.am.routingMode !== 'preferred') {
1032
+ this._addLog('No account is pinned — routing is already automatic');
1033
+ } else if (k === 'a') {
1018
1034
  this._confirm(
1019
1035
  'Unpin and use automatic routing?',
1020
1036
  'Removes the pinned account. New requests are spread across all healthy accounts again.',
@@ -2020,6 +2036,15 @@ export class TUI {
2020
2036
  lines.push(` ${gray(this.log[i].t)} ${this.log[i].msg}`);
2021
2037
  }
2022
2038
 
2039
+ // The footer is the screen's controls — it must never be pushed off. On a short
2040
+ // terminal cut the BODY from the bottom, and say so.
2041
+ if (lines.length > H - footerH) {
2042
+ const keep = Math.max(0, H - footerH - 1);
2043
+ const hidden = lines.length - keep;
2044
+ lines.length = keep;
2045
+ lines.push(` ${dim(`… ${hidden} more row${hidden === 1 ? '' : 's'} — make the window taller to see them`)}`);
2046
+ }
2047
+
2023
2048
  // Pad to fill
2024
2049
  while (lines.length < H - footerH) lines.push('');
2025
2050
 
@@ -2116,10 +2141,10 @@ export class TUI {
2116
2141
  // relabels an otherwise-active row; exhausted / throttled / auth states keep their
2117
2142
  // own (more specific) word. "capped" = the owner's usage cap is holding it;
2118
2143
  // "5h full" = its session window is used up until the reset shown on the row.
2119
- // READ-ONLY by design: _weeklyRawState/_isSessionQuotaUnavailable call
2120
- // _clearExpiredQuotas, which MUTATES the quota (nulls an expired window). A render
2121
- // must never change the state it is drawing — the row's own bars, read later in
2122
- // this function, then showed blanks. capBenched + _sessionBenchThreshold are pure.
2144
+ // Pure predicates on purpose: _weeklyRawState/_isSessionQuotaUnavailable call
2145
+ // _clearExpiredQuotas, which MUTATES the quota (nulls an expired window), so calling
2146
+ // them here blanked this row's own bars mid-render. (_render already runs the
2147
+ // manager's expiry refresh once per frame, before any row is drawn.)
2123
2148
  if (a.enabled !== false && effectiveStatus === 'active') {
2124
2149
  if (capBenched(this.am, a)) effectiveStatus = 'capped';
2125
2150
  else if (sessionFull(this.am, a)) effectiveStatus = '5h full';
@@ -2551,7 +2576,7 @@ export class TUI {
2551
2576
  out.push(` ${dim('Usage cap for ONE account:')} ${bold('Esc')} ${dim('then')} ${bold('a')} ${dim('→')} ${bold('u')} ${dim('(Claude or GLM)')}`);
2552
2577
  const capped = this.am.accounts.filter(a => a.capUtilization != null && a.enabled !== false);
2553
2578
  if (capped.length) {
2554
- out.push(` ${dim('Capped now:')} ${capped.map(a => `${a.name} ${Math.round(a.capUtilization * 100)}%`).join(', ')}`);
2579
+ out.push(` ${dim('Caps set:')} ${capped.map(a => `${a.name} ${Math.round(a.capUtilization * 100)}%`).join(', ')}`);
2555
2580
  }
2556
2581
  return out;
2557
2582
  }