maxpool 1.24.22 → 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.
- package/package.json +1 -1
- package/src/tui.js +98 -6
package/package.json
CHANGED
package/src/tui.js
CHANGED
|
@@ -271,6 +271,28 @@ function settingsTags(am, a) {
|
|
|
271
271
|
return tags;
|
|
272
272
|
}
|
|
273
273
|
|
|
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
|
+
function sessionFull(am, a) {
|
|
281
|
+
const q = a?.quota || {};
|
|
282
|
+
const now = Date.now();
|
|
283
|
+
const bench = am?._sessionBenchThreshold?.(a, now);
|
|
284
|
+
if (!Number.isFinite(bench)) return false;
|
|
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;
|
|
294
|
+
}
|
|
295
|
+
|
|
274
296
|
/** True when the reservation is what is currently keeping traffic off this account
|
|
275
297
|
* — either window at or past the cap. Reads the manager's own predicate so the
|
|
276
298
|
* label can never disagree with routing. */
|
|
@@ -282,7 +304,13 @@ function capBenched(am, a) {
|
|
|
282
304
|
// Each reading is judged against ITS OWN window's cap — under the dynamic cap the
|
|
283
305
|
// two differ (a 5h window nearly over is lifted while the weekly is still at its
|
|
284
306
|
// floor), so passing the default window for both would mislabel one of them.
|
|
285
|
-
|
|
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;
|
|
286
314
|
}
|
|
287
315
|
|
|
288
316
|
export function weeklyPolicyText(am, account) {
|
|
@@ -1000,10 +1028,12 @@ export class TUI {
|
|
|
1000
1028
|
|
|
1001
1029
|
|
|
1002
1030
|
_keyRouting(k) {
|
|
1003
|
-
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') {
|
|
1004
1034
|
this._confirm(
|
|
1005
|
-
'
|
|
1006
|
-
'
|
|
1035
|
+
'Unpin and use automatic routing?',
|
|
1036
|
+
'Removes the pinned account. New requests are spread across all healthy accounts again.',
|
|
1007
1037
|
() => this._setAutomaticRouting(),
|
|
1008
1038
|
);
|
|
1009
1039
|
} else if (k === 'p' && this.am.accounts.some(account => account.type !== 'provider')) {
|
|
@@ -1999,12 +2029,22 @@ export class TUI {
|
|
|
1999
2029
|
// 2 = separator + footer; confirm adds its detail line; updates adds its detail block.
|
|
2000
2030
|
const footerH = this.mode === 'confirm' ? 3
|
|
2001
2031
|
: this.mode === 'updates' ? 2 + this._renderUpdatesDetail().length
|
|
2032
|
+
: this.mode === 'routing' ? 2 + this._renderRoutingDetail().length
|
|
2002
2033
|
: 2;
|
|
2003
2034
|
const space = Math.max(0, H - lines.length - footerH);
|
|
2004
2035
|
for (let i = 0; i < space && i < this.log.length; i++) {
|
|
2005
2036
|
lines.push(` ${gray(this.log[i].t)} ${this.log[i].msg}`);
|
|
2006
2037
|
}
|
|
2007
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
|
+
|
|
2008
2048
|
// Pad to fill
|
|
2009
2049
|
while (lines.length < H - footerH) lines.push('');
|
|
2010
2050
|
|
|
@@ -2015,6 +2055,7 @@ export class TUI {
|
|
|
2015
2055
|
// very bottom of a busy screen, which reads as "nothing happened" (reported twice).
|
|
2016
2056
|
// Every other mode draws something visible; this one must too.
|
|
2017
2057
|
if (this.mode === 'updates') lines.push(...this._renderUpdatesDetail());
|
|
2058
|
+
if (this.mode === 'routing') lines.push(...this._renderRoutingDetail());
|
|
2018
2059
|
lines.push(this._renderFooter());
|
|
2019
2060
|
|
|
2020
2061
|
this._writeScreen(lines, W, H);
|
|
@@ -2094,6 +2135,20 @@ export class TUI {
|
|
|
2094
2135
|
// credentials were fine or long dead, so re-enabling it later silently produced a
|
|
2095
2136
|
// broken account. Reported 2026-08-10 with all 8 disabled accounts sitting on dead
|
|
2096
2137
|
// credentials (HTTP 401) and no way to see it.
|
|
2138
|
+
// HELD BY ITS OWN LIMITS (2026-10-13). An account the router refuses must not read
|
|
2139
|
+
// "active": 16 requests sat parked for 35 minutes while two rows said "active" next
|
|
2140
|
+
// to an account held at its usage cap and another whose 5h window was full. Only
|
|
2141
|
+
// relabels an otherwise-active row; exhausted / throttled / auth states keep their
|
|
2142
|
+
// own (more specific) word. "capped" = the owner's usage cap is holding it;
|
|
2143
|
+
// "5h full" = its session window is used up until the reset shown on the row.
|
|
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.)
|
|
2148
|
+
if (a.enabled !== false && effectiveStatus === 'active') {
|
|
2149
|
+
if (capBenched(this.am, a)) effectiveStatus = 'capped';
|
|
2150
|
+
else if (sessionFull(this.am, a)) effectiveStatus = '5h full';
|
|
2151
|
+
}
|
|
2097
2152
|
if (a.refreshDead) effectiveStatus = a.enabled === false ? 'disabled-reauth' : 'reauth';
|
|
2098
2153
|
// Subscription gone (org-disabled 403): a DISTINCT state from reauth — re-logging in
|
|
2099
2154
|
// will NOT fix it until the subscription is re-purchased. Says the actionable thing.
|
|
@@ -2130,6 +2185,8 @@ export class TUI {
|
|
|
2130
2185
|
break;
|
|
2131
2186
|
}
|
|
2132
2187
|
case 'exhausted': status = red('exhausted'); break;
|
|
2188
|
+
case 'capped': status = yellow('capped'); break;
|
|
2189
|
+
case '5h full': status = yellow('5h full'); break;
|
|
2133
2190
|
case 'error': status = red('error'); break;
|
|
2134
2191
|
default: status = a.status || 'ready';
|
|
2135
2192
|
}
|
|
@@ -2493,6 +2550,37 @@ export class TUI {
|
|
|
2493
2550
|
return out;
|
|
2494
2551
|
}
|
|
2495
2552
|
|
|
2553
|
+
/**
|
|
2554
|
+
* Routing DETAIL — what each key on this screen controls and how to undo it.
|
|
2555
|
+
* (2026-10-13: "the cap setting only changes GLM", "I pinned an account and have no
|
|
2556
|
+
* idea how to switch it back", "where is the cap for individual accounts?")
|
|
2557
|
+
*/
|
|
2558
|
+
_renderRoutingDetail() {
|
|
2559
|
+
// Every line fits 80 columns: a truncated explanation is no explanation.
|
|
2560
|
+
const out = [];
|
|
2561
|
+
const pinned = this.am.routingMode === 'preferred' && this.am.preferredAccountName;
|
|
2562
|
+
// The manual pin: state, what it does, and the way out — always visible.
|
|
2563
|
+
if (pinned) {
|
|
2564
|
+
out.push(` ${yellow(`Pinned to ${this.am.preferredAccountName}`)} ${bold('a')} Unpin ${bold('p')} Change`);
|
|
2565
|
+
out.push(` ${dim('It gets every request it can take; the others only cover for it.')}`);
|
|
2566
|
+
} else {
|
|
2567
|
+
out.push(` ${bold('p')} Pin one Claude account ${dim('(undo:')} ${bold('a')}${dim(')')}`);
|
|
2568
|
+
out.push(` ${dim('Sends everything to it first; the others only cover for it.')}`);
|
|
2569
|
+
}
|
|
2570
|
+
const hasPeak = this.am.accounts.some(a => a.type === 'provider'
|
|
2571
|
+
&& (this.am._peakSettingsFor?.(a.provider)?.windows || []).length > 0);
|
|
2572
|
+
if (hasPeak) {
|
|
2573
|
+
out.push(` ${bold('c')} GLM peak cap ${dim('— GLM accounts only, in GLM peak hours. Not Claude.')}`);
|
|
2574
|
+
}
|
|
2575
|
+
// Where the per-account cap actually is.
|
|
2576
|
+
out.push(` ${dim('Usage cap for ONE account:')} ${bold('Esc')} ${dim('then')} ${bold('a')} ${dim('→')} ${bold('u')} ${dim('(Claude or GLM)')}`);
|
|
2577
|
+
const capped = this.am.accounts.filter(a => a.capUtilization != null && a.enabled !== false);
|
|
2578
|
+
if (capped.length) {
|
|
2579
|
+
out.push(` ${dim('Caps set:')} ${capped.map(a => `${a.name} ${Math.round(a.capUtilization * 100)}%`).join(', ')}`);
|
|
2580
|
+
}
|
|
2581
|
+
return out;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2496
2584
|
_renderFooter() {
|
|
2497
2585
|
switch (this.mode) {
|
|
2498
2586
|
case 'normal':
|
|
@@ -2531,9 +2619,13 @@ export class TUI {
|
|
|
2531
2619
|
const dep = ps.depreference ? yellow('GLM last') : cyan('normal');
|
|
2532
2620
|
const cap = ps.cap >= 1 ? 'off' : ps.cap === 0 ? 'never' : `${Math.round(ps.cap * 100)}%`;
|
|
2533
2621
|
const wk = this.am.scheduler.weeklyAwareScoring === false ? yellow('5h-only') : cyan('weekly');
|
|
2534
|
-
|
|
2622
|
+
// "cap" alone read as THE usage cap for every account (2026-10-13). It is not:
|
|
2623
|
+
// it only limits GLM accounts, only during GLM's peak hours. Say so.
|
|
2624
|
+
peakPart = ` ${dim('│')} ${bold(' d ')}Peak${now}: ${dep} ${bold(' c ')}GLM peak cap ${cyan(cap)} ${bold(' w ')}score:${wk}`;
|
|
2535
2625
|
}
|
|
2536
|
-
|
|
2626
|
+
// The pin and the account-cap pointer live in the DETAIL block above this line
|
|
2627
|
+
// (_renderRoutingDetail) — one footer line cannot carry them without truncating.
|
|
2628
|
+
return ` ${bold('f')} Routing: ${cyan(mode.label)} ↻${provPart}${peakPart} ${bold('Esc')} Back`;
|
|
2537
2629
|
}
|
|
2538
2630
|
case 'select': {
|
|
2539
2631
|
const act = this.selAction === 'prefer'
|