maxpool 1.15.0 → 1.16.0

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 +91 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
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
@@ -162,22 +162,54 @@ function countdown(ts) {
162
162
  return `${Math.ceil(ms / 86_400_000)}d`;
163
163
  }
164
164
 
165
+ /** ACTIVITY cell — "is this account working, and is it healthy?" in as few glyphs
166
+ * as that question needs. Rewritten 2026-08-27 on owner feedback: the old form
167
+ * ("Now 0 15m 0r 1h 0r") printed three numbers on EVERY row, and on a resting
168
+ * account all three were zero — a column of noise whose vocabulary ("now", then
169
+ * some minutes, then hours) nobody could read. An idle account now renders
170
+ * NOTHING, and a working one renders live · rate · latency.
171
+ *
172
+ * ▶2 · 17/h · 8.5s 2 in flight, 17 requests in the last hour, 8.5s average
173
+ * 17/h · 8.5s idle this second, but working
174
+ * 17/h · 8.5s 2f …with 2 failures in the last 15m
175
+ * (blank) resting — nothing to say
176
+ */
165
177
  function loadText(load) {
166
- const cur = load?.current || {};
167
- const m15 = load?.last15m || {};
168
- const h1 = load?.last1h || {};
169
- // "Now" = what this account is handling right THIS moment: N in-flight requests
170
- // (and their combined weight ~ payload size, the scheduler's load input). Kept
171
- // distinct from the "15m"/"1h" THROUGHPUT counts that follow, which the old
172
- // "Load X/Y" label collided with.
173
- const inflight = cur.inFlight || 0;
174
- const weight = cur.activeWeight || 0;
175
- const now = weight > 0 ? `Now ${inflight} (${weight}w)` : `Now ${inflight}`;
176
- const recent = `${m15.requests || 0}r`;
177
- const recentAvg = m15.avgMs != null ? ` ${formatMs(m15.avgMs)}` : '';
178
- const hour = `${h1.requests || 0}r`;
179
- const fails = (m15.failed || 0) > 0 ? ` ${red(`${m15.failed}f`)}` : '';
180
- return `${now} 15m ${recent}${recentAvg}${fails} 1h ${hour}`;
178
+ const inflight = load?.current?.inFlight || 0;
179
+ const hourReq = load?.last1h?.requests || 0;
180
+ const failed = load?.last15m?.failed || 0;
181
+ if (!inflight && !hourReq && !failed) return '';
182
+ const parts = [];
183
+ if (inflight) parts.push(`▶${inflight}`);
184
+ if (hourReq) parts.push(`${hourReq}/h`);
185
+ const avg = load?.last15m?.avgMs ?? load?.last1h?.avgMs;
186
+ if (avg != null && hourReq) parts.push(formatMs(avg));
187
+ let s = parts.join(' · ');
188
+ if (failed) s += ` ${red(`${failed}f`)}`;
189
+ return s;
190
+ }
191
+
192
+ /** The usage CAP is an account PROPERTY, not a state — so it renders whenever one
193
+ * is set, on every account type, whatever the account is doing. It previously
194
+ * lived inline in the provider branch only, so the OAuth account the feature was
195
+ * built for (max@gomokka.com) showed no cap anywhere: reported 2026-08-27, "I need
196
+ * to be able to see whether an account has a cap or not." Yellow while the cap is
197
+ * actively holding traffic back, dim otherwise. */
198
+ function capText(a, benched) {
199
+ if (a?.capUtilization == null) return '';
200
+ const t = `cap ${Math.round(a.capUtilization * 100)}%`;
201
+ return benched ? yellow(t) : dim(t);
202
+ }
203
+
204
+ /** True when the reservation is what is currently keeping traffic off this account
205
+ * — either window at or past the cap. Reads the manager's own predicate so the
206
+ * label can never disagree with routing. */
207
+ function capBenched(am, a) {
208
+ if (a?.capUtilization == null) return false;
209
+ const q = a.quota || {};
210
+ const ses = a.type === 'provider' ? q.providerSes : q.unified5h;
211
+ const wk = a.type === 'provider' ? q.providerWk : q.unified7d;
212
+ return !!(am?._capped?.(a, ses) || am?._capped?.(a, wk));
181
213
  }
182
214
 
183
215
  export function weeklyPolicyText(am, account) {
@@ -190,6 +222,14 @@ export function weeklyPolicyText(am, account) {
190
222
  return yellow(`Cap ${Math.round((account.capUtilization || 0) * 100)}%`);
191
223
  }
192
224
  if (!state || state === 'unknown' || state === 'normal') return '';
225
+ // SAY IT ONCE (2026-08-27). An account Anthropic is rejecting outright already
226
+ // says so twice on this row: the Wk bar reads 100%, and the Status column reads
227
+ // "exhausted". A third "Wk exhausted 100%" tag was pure repetition, and it pushed
228
+ // the genuinely-informative tags (Cap, a per-model sub-limit) off to the right.
229
+ // Dropped ONLY when the Status column carries the same fact — a soft/reserve/
230
+ // critical row, or an exhausted-by-threshold row the status column shows as
231
+ // "active", still needs the tag.
232
+ if (am._isAccountWideRejected?.(account)) return '';
193
233
  const rawState = am._weeklyRawState?.(account) || state;
194
234
  const used = Number(account.quota?.unified7d);
195
235
  const pct = Number.isFinite(used)
@@ -1738,7 +1778,7 @@ export class TUI {
1738
1778
  // Glossary FOOTER (expands the abbreviations the header + inline labels can't
1739
1779
  // spell out). Below the rows so it never breaks the header↔column alignment.
1740
1780
  if (W >= 88) {
1741
- lines.push(' ' + dim('Legend Ses = 5h · Wk = 7d · Now = in-flight (weight) · 15m/1h = served (avg · f = fails)'));
1781
+ lines.push(' ' + dim('Legend Ses = 5h · Wk = 7d · ▶ = in-flight · /h = requests last hour · avg latency · f = fails'));
1742
1782
  }
1743
1783
  }
1744
1784
 
@@ -1859,13 +1899,16 @@ export class TUI {
1859
1899
  if (a.enabled !== false && upstreamBlocking && a.status === 'active') {
1860
1900
  effectiveStatus = a.inFlight > 0 ? 'probing' : 'waiting';
1861
1901
  }
1862
- // "blocked" = Anthropic is rejecting the WHOLE account (a 'rejected' unified
1863
- // status corroborated by an exhausted unified bucket). A per-model cap (Fable)
1864
- // is NOT account-wide — it shows as the separate "… maxed" tag, leaving the
1865
- // status column truthful. Keys on a.status (not effectiveStatus) so a genuine
1866
- // block wins even inside an upstream-throttle window.
1902
+ // Anthropic is rejecting the WHOLE account (a 'rejected' unified status
1903
+ // corroborated by an exhausted unified bucket). A per-model cap (Fable) is NOT
1904
+ // account-wide — it shows as the separate "… maxed" tag, leaving the status
1905
+ // column truthful. Keys on a.status (not effectiveStatus) so a genuine block
1906
+ // wins even inside an upstream-throttle window.
1907
+ // Named "exhausted", not "blocked" (2026-08-27): the row's own quota bar and
1908
+ // every other surface call this state exhausted, and two words for one state
1909
+ // read as two different problems.
1867
1910
  if (a.enabled !== false && a.status === 'active' && this.am._isAccountWideRejected?.(a)) {
1868
- effectiveStatus = 'blocked';
1911
+ effectiveStatus = 'exhausted';
1869
1912
  }
1870
1913
  // A dead refresh token surfaces as "reauth" (re-login needed) rather than a
1871
1914
  // generic "error", so the user knows the fix. Display-only — account.status
@@ -1879,7 +1922,7 @@ export class TUI {
1879
1922
  switch (effectiveStatus) {
1880
1923
  case 'active': status = isCur ? green('active') : 'active'; break;
1881
1924
  case 'reauth': status = yellow('reauth'); break;
1882
- case 'blocked': status = red('blocked'); break;
1925
+ case 'blocked': status = red('exhausted'); break;
1883
1926
  case 'probing': status = green('probing'); break;
1884
1927
  case 'waiting': status = yellow('waiting'); break;
1885
1928
  case 'paused': status = yellow('paused'); break;
@@ -1936,6 +1979,14 @@ export class TUI {
1936
1979
  }
1937
1980
  const weekly = weeklyPolicyText(this.am, a);
1938
1981
  if (weekly) line += ` ${weekly}`;
1982
+ // The reservation, whatever the account is doing. weeklyPolicyText renders
1983
+ // "Cap 50%" only while the cap is the ACTIVE weekly state; this is the standing
1984
+ // property, so a capped account that is exhausted, throttled or idle still says
1985
+ // so. Suppressed when the weekly tag is already the Cap label (no double tag).
1986
+ if (weekly.includes('Cap ') === false) {
1987
+ const capTag = capText(a, capBenched(this.am, a));
1988
+ if (capTag) line += ` ${capTag}`;
1989
+ }
1939
1990
  // Per-model weekly caps (e.g. Fable, while the unified weekly still has
1940
1991
  // headroom). Show the ACTUAL utilization — "Fable 90%" (yellow) while high but
1941
1992
  // still usable, "Fable maxed" (red) ONLY at genuine exhaustion. This is the
@@ -1958,7 +2009,8 @@ export class TUI {
1958
2009
  // cause (e.g. rate-limited) when a probe failure is on record — rather than imply
1959
2010
  // the last-known value is current.
1960
2011
  line += this._probeHealthNote(a);
1961
- line += ` ${dim(loadText(this._accountLoad(a)))}`;
2012
+ const act = loadText(this._accountLoad(a));
2013
+ if (act) line += ` ${dim(act)}`;
1962
2014
  return line;
1963
2015
  }
1964
2016
 
@@ -2031,7 +2083,6 @@ export class TUI {
2031
2083
  const m = this.am._fastRefillMultiplier(a);
2032
2084
  if (m < 1) note += ` ${cyan(`fast·refill ×${m.toFixed(2)}`)}`;
2033
2085
  }
2034
- if (a.capUtilization != null) note += ` ${yellow(`cap ${Math.round(a.capUtilization * 100)}%`)}`;
2035
2086
  } else if (q.providerQuotaSource === 'console-only') {
2036
2087
  sesCell = emptyBar('n/a', bw);
2037
2088
  wkCell = emptyBar('n/a', bw);
@@ -2040,15 +2091,27 @@ export class TUI {
2040
2091
  sesCell = emptyBar('probing', bw);
2041
2092
  wkCell = emptyBar('probing', bw);
2042
2093
  }
2094
+ // The cap rides OUTSIDE the quota-readable branch: an account with an
2095
+ // unreadable or not-yet-probed quota still HAS its reservation, and hiding the
2096
+ // setting whenever the probe is quiet is how a shipped feature reads as absent.
2097
+ const capTag = capText(a, capBenched(this.am, a));
2098
+ if (capTag) note += ` ${capTag}`;
2043
2099
 
2044
2100
  let line = ` ${sel}${cur} ${name} ${type} ${status} Ses ${sesCell}`;
2045
2101
  if (showBoth) line += ` Wk ${wkCell}`;
2046
2102
  line += note;
2047
2103
  // Same recent-load columns as OAuth (providers track inFlight + load events),
2048
2104
  // plus a compact Last <status> <ms> — the one signal that matters for a
2049
- // rarely-hit fallback (loadText reads 0r when idle).
2050
- line += ` ${dim(loadText(this._accountLoad(a)))}`;
2051
- if (a.lastStatus) line += ` ${dim('Last')} ${statusColor(a.lastStatus)} ${dim(formatMs(a.lastResponseMs))}`;
2105
+ // rarely-hit fallback. loadText renders empty when fully idle.
2106
+ const act = loadText(this._accountLoad(a));
2107
+ if (act) line += ` ${dim(act)}`;
2108
+ // "Last 200 6.4s" next to a live "17/h · 6.4s" said the same thing twice. Keep it
2109
+ // for the two cases where it is the ONLY thing that speaks: a non-2xx last result
2110
+ // (always worth seeing), or an idle row whose activity cell is blank.
2111
+ const lastOk = a.lastStatus >= 200 && a.lastStatus < 300;
2112
+ if (a.lastStatus && (!lastOk || !act)) {
2113
+ line += ` ${dim('Last')} ${statusColor(a.lastStatus)} ${dim(formatMs(a.lastResponseMs))}`;
2114
+ }
2052
2115
  // Header-derived generic rate-limit (distinct from the monitor-endpoint quota),
2053
2116
  // if the provider upstream returns x-ratelimit-* headers — only when present.
2054
2117
  if (q.genericLimit != null && q.genericRemaining != null) {