maxpool 1.14.0 → 1.15.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.
- package/package.json +1 -1
- package/src/account-manager.js +79 -11
- package/src/index.js +8 -0
- package/src/tui.js +69 -5
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -325,6 +325,20 @@ function parseResetHeader(value) {
|
|
|
325
325
|
return Number.isNaN(asDate) ? null : asDate;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/**
|
|
329
|
+
* A capUtilization is valid only as a real number strictly inside (0,1). Everything
|
|
330
|
+
* else — strings, NaN, negative, 0, >=1 — means "no cap"; an invalid EXPLICIT value
|
|
331
|
+
* also logs once so a hand-edited config doesn't silently fail open (a NaN cap makes
|
|
332
|
+
* every `util >= cap` comparison false = uncapped, with no error anywhere).
|
|
333
|
+
*/
|
|
334
|
+
function _sanitizeCap(value, name) {
|
|
335
|
+
if (value == null) return null;
|
|
336
|
+
const n = Number(value);
|
|
337
|
+
if (Number.isFinite(n) && n > 0 && n < 1) return n;
|
|
338
|
+
console.log(`[Maxpool] Ignoring invalid capUtilization ${JSON.stringify(value)} for "${name}" — expected 0-1`);
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
328
342
|
export class AccountManager {
|
|
329
343
|
constructor(accounts, switchThreshold = 0.90, schedulerOptions = {}, dependencies = {}) {
|
|
330
344
|
this.scheduler = { ...DEFAULT_SCHEDULER, ...schedulerOptions };
|
|
@@ -354,6 +368,13 @@ export class AccountManager {
|
|
|
354
368
|
authHeader: acct.authHeader || null,
|
|
355
369
|
profiles: acct.profiles || (acct.type === 'provider' ? ['all'] : ['claude', 'all']),
|
|
356
370
|
priority: Number.isFinite(acct.priority) ? acct.priority : 0,
|
|
371
|
+
// USAGE CAP (owner-directed 2026-08-26): reserve capacity on this account —
|
|
372
|
+
// the proxy benches it at capUtilization of BOTH the 5h and 7d windows, keeping
|
|
373
|
+
// the rest for out-of-band use. null/undefined = fully utilized (the default;
|
|
374
|
+
// no behavior change). SANITIZED at parse: non-finite/out-of-range values drop
|
|
375
|
+
// to null HERE, visibly (below), so a hand-edited "50" or "abc" in the config
|
|
376
|
+
// can never fail the >= comparisons open as NaN.
|
|
377
|
+
capUtilization: _sanitizeCap(acct.capUtilization, acct.name),
|
|
357
378
|
model: acct.model || null,
|
|
358
379
|
modelMap: acct.modelMap || null,
|
|
359
380
|
stripBetaHeaders: Boolean(acct.stripBetaHeaders),
|
|
@@ -837,7 +858,7 @@ export class AccountManager {
|
|
|
837
858
|
// headroom (e.g. 69% used, resets in days) must stay in the healthy-spread
|
|
838
859
|
// pool even if it's burning fast. Pace is a soft SCORE cost, never a bench.
|
|
839
860
|
const weeklyState = this._weeklyRawState(account);
|
|
840
|
-
if (weeklyState === 'exhausted') return false;
|
|
861
|
+
if (weeklyState === 'exhausted' || weeklyState === 'capped') return false;
|
|
841
862
|
if (weeklyState === 'critical' && !options.allowWeeklyCritical) return false;
|
|
842
863
|
if (weeklyState === 'reserve' && !options.allowWeeklyReserve) return false;
|
|
843
864
|
|
|
@@ -1266,28 +1287,47 @@ export class AccountManager {
|
|
|
1266
1287
|
}
|
|
1267
1288
|
}
|
|
1268
1289
|
|
|
1290
|
+
/**
|
|
1291
|
+
* The session-bench threshold for THIS account: the global switchThreshold, lowered
|
|
1292
|
+
* to its usage cap when one is set. Shared by _isSessionQuotaUnavailable (the bench)
|
|
1293
|
+
* and _shortTermRetry (the retry oracle) — one helper, both call sites, so the
|
|
1294
|
+
* oracle can never desync from the bench (a capped-benched account MUST report a
|
|
1295
|
+
* finite retry time or a live session holding on it gets error-fasted).
|
|
1296
|
+
*/
|
|
1297
|
+
_sessionBenchThreshold(account) {
|
|
1298
|
+
const cap = account?.capUtilization;
|
|
1299
|
+
return (cap != null && cap < this.switchThreshold) ? cap : this.switchThreshold;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/** True when the account's usage cap has it benched on the given window reading. */
|
|
1303
|
+
_capped(account, utilization) {
|
|
1304
|
+
const cap = account?.capUtilization;
|
|
1305
|
+
return cap != null && utilization != null && utilization >= cap;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1269
1308
|
_isSessionQuotaUnavailable(account) {
|
|
1270
1309
|
const q = account.quota;
|
|
1271
1310
|
this._clearExpiredQuotas(account);
|
|
1311
|
+
const bench = this._sessionBenchThreshold(account);
|
|
1272
1312
|
|
|
1273
1313
|
// Unified 5h quota is immediate availability. Weekly quota is handled
|
|
1274
1314
|
// separately as long-horizon admission control.
|
|
1275
|
-
if (q.unified5h != null && q.unified5h >=
|
|
1315
|
+
if (q.unified5h != null && q.unified5h >= bench) return true;
|
|
1276
1316
|
|
|
1277
1317
|
// Standard quotas (API key accounts)
|
|
1278
1318
|
if (q.tokensLimit != null && q.tokensRemaining != null) {
|
|
1279
1319
|
const used = 1 - (q.tokensRemaining / q.tokensLimit);
|
|
1280
|
-
if (used >=
|
|
1320
|
+
if (used >= bench) return true;
|
|
1281
1321
|
}
|
|
1282
1322
|
|
|
1283
1323
|
if (q.requestsLimit != null && q.requestsRemaining != null) {
|
|
1284
1324
|
const used = 1 - (q.requestsRemaining / q.requestsLimit);
|
|
1285
|
-
if (used >=
|
|
1325
|
+
if (used >= bench) return true;
|
|
1286
1326
|
}
|
|
1287
1327
|
|
|
1288
1328
|
// Provider (z.ai/Kimi) session quota — the 5h token window. Without this a
|
|
1289
1329
|
// provider at 95% of its 5h cap reads as fully available.
|
|
1290
|
-
if (q.providerSes != null && q.providerSes >=
|
|
1330
|
+
if (q.providerSes != null && q.providerSes >= bench) return true;
|
|
1291
1331
|
|
|
1292
1332
|
return false;
|
|
1293
1333
|
}
|
|
@@ -1578,9 +1618,10 @@ export class AccountManager {
|
|
|
1578
1618
|
queueable: true,
|
|
1579
1619
|
};
|
|
1580
1620
|
}
|
|
1581
|
-
if (weeklyState === 'exhausted') {
|
|
1621
|
+
if (weeklyState === 'exhausted' || weeklyState === 'capped') {
|
|
1582
1622
|
// Hard block: only a weekly reset unblocks it — a sooner short-term clear
|
|
1583
|
-
// does not help — so key the hold on the weekly reset.
|
|
1623
|
+
// does not help — so key the hold on the weekly reset. 'capped' (usage cap)
|
|
1624
|
+
// shares this arm: the cap's recovery time IS the window reset.
|
|
1584
1625
|
//
|
|
1585
1626
|
// Read the PROVIDER reset too. `unified7dReset` is an Anthropic-only field;
|
|
1586
1627
|
// a GLM/Kimi account stores its weekly reset in `providerWkReset`
|
|
@@ -1672,6 +1713,11 @@ export class AccountManager {
|
|
|
1672
1713
|
// queueable} the retry oracle can hold on. Kept separate from the weekly state
|
|
1673
1714
|
// so weekly-critical accounts surface their real near-term recovery time.
|
|
1674
1715
|
_shortTermRetry(account, now, q) {
|
|
1716
|
+
// USAGE CAP: the oracle reads the SAME per-account bench threshold as
|
|
1717
|
+
// _isSessionQuotaUnavailable (_sessionBenchThreshold) — a capped account benched
|
|
1718
|
+
// at 50% MUST report a finite retryAt here or a live session holding on it gets
|
|
1719
|
+
// error-fasted instead of waiting out the window (red-team blocker 2).
|
|
1720
|
+
const bench = this._sessionBenchThreshold(account);
|
|
1675
1721
|
if (account.status === 'throttled' && account.rateLimitedUntil && now < account.rateLimitedUntil) {
|
|
1676
1722
|
return { cause: 'rate_limited', retryAt: account.rateLimitedUntil, queueable: true };
|
|
1677
1723
|
}
|
|
@@ -1684,13 +1730,13 @@ export class AccountManager {
|
|
|
1684
1730
|
return { cause: 'upstream_failure', retryAt: account.provisionalUpstreamUntil, queueable: true };
|
|
1685
1731
|
}
|
|
1686
1732
|
|
|
1687
|
-
if (q.unified5h != null && q.unified5h >=
|
|
1733
|
+
if (q.unified5h != null && q.unified5h >= bench) {
|
|
1688
1734
|
return { cause: 'session_limit', retryAt: q.unified5hReset || null, queueable: Boolean(q.unified5hReset) };
|
|
1689
1735
|
}
|
|
1690
1736
|
|
|
1691
1737
|
if (q.tokensLimit != null && q.tokensRemaining != null && q.tokensLimit > 0) {
|
|
1692
1738
|
const used = 1 - q.tokensRemaining / q.tokensLimit;
|
|
1693
|
-
if (used >=
|
|
1739
|
+
if (used >= bench) {
|
|
1694
1740
|
const retryAt = q.resetsAt ? new Date(q.resetsAt).getTime() : null;
|
|
1695
1741
|
return { cause: 'token_limit', retryAt, queueable: Boolean(retryAt) };
|
|
1696
1742
|
}
|
|
@@ -1698,7 +1744,7 @@ export class AccountManager {
|
|
|
1698
1744
|
|
|
1699
1745
|
if (q.requestsLimit != null && q.requestsRemaining != null && q.requestsLimit > 0) {
|
|
1700
1746
|
const used = 1 - q.requestsRemaining / q.requestsLimit;
|
|
1701
|
-
if (used >=
|
|
1747
|
+
if (used >= bench) {
|
|
1702
1748
|
const retryAt = q.resetsAt ? new Date(q.resetsAt).getTime() : null;
|
|
1703
1749
|
return { cause: 'request_limit', retryAt, queueable: Boolean(retryAt) };
|
|
1704
1750
|
}
|
|
@@ -2819,7 +2865,7 @@ export class AccountManager {
|
|
|
2819
2865
|
|
|
2820
2866
|
_weeklyState(account) {
|
|
2821
2867
|
const rawState = this._weeklyRawState(account);
|
|
2822
|
-
if (rawState === 'unknown' || rawState === 'exhausted') return rawState;
|
|
2868
|
+
if (rawState === 'unknown' || rawState === 'exhausted' || rawState === 'capped') return rawState;
|
|
2823
2869
|
|
|
2824
2870
|
const pressure = Math.max(clamp01(account.quota.unified7d ?? 0), this._effectiveWeeklyUsage(account));
|
|
2825
2871
|
if (pressure >= this.scheduler.weeklyCriticalThreshold) return 'critical';
|
|
@@ -2858,6 +2904,11 @@ export class AccountManager {
|
|
|
2858
2904
|
const sesUsed = q.providerSes != null ? clamp01(q.providerSes) : null;
|
|
2859
2905
|
const wkUsed = q.providerWk != null ? clamp01(q.providerWk) : null;
|
|
2860
2906
|
const used = Math.max(sesUsed ?? 0, wkUsed ?? 0);
|
|
2907
|
+
// USAGE CAP — checked FIRST, before every tier: a reservation is owner intent
|
|
2908
|
+
// and outranks both the tier ladder and the upstream verdict. There is no
|
|
2909
|
+
// upstreamAllows carve-out for providers anyway, but the ordering documents
|
|
2910
|
+
// that a cap can never be talked out of by the vendor's "allowed".
|
|
2911
|
+
if (this._capped(account, used)) return 'capped';
|
|
2861
2912
|
if (used >= this.scheduler.weeklyExhaustedThreshold) return 'exhausted';
|
|
2862
2913
|
if (used >= this.scheduler.weeklyCriticalThreshold) return 'critical';
|
|
2863
2914
|
if (used >= this.scheduler.weeklyReserveThreshold) return 'reserve';
|
|
@@ -2878,6 +2929,12 @@ export class AccountManager {
|
|
|
2878
2929
|
// was waiting on, withheld because a threshold outranked the upstream's own answer.
|
|
2879
2930
|
// 'exhausted' is the only state that removes an account from routing, so the override
|
|
2880
2931
|
// is scoped to it — critical/reserve still apply their soft costs unchanged.
|
|
2932
|
+
// USAGE CAP — before the upstreamAllows carve-out BY DESIGN (red-team blocker 1):
|
|
2933
|
+
// a capped account is below its REAL limit, so upstream keeps saying "allowed"
|
|
2934
|
+
// right through the cap — the override exists for genuine over-limit-but-allowed
|
|
2935
|
+
// states and would otherwise make the cap a no-op on exactly the account it is
|
|
2936
|
+
// for (measured: this exact shape sat at unified7d=1.00 'allowed_warning').
|
|
2937
|
+
if (this._capped(account, used)) return 'capped';
|
|
2881
2938
|
const upstreamAllows = typeof q.unifiedStatus === 'string' && q.unifiedStatus.startsWith('allowed');
|
|
2882
2939
|
if (used >= this.scheduler.weeklyExhaustedThreshold && !upstreamAllows) return 'exhausted';
|
|
2883
2940
|
if (used >= this.scheduler.weeklyCriticalThreshold) return 'critical';
|
|
@@ -3748,6 +3805,7 @@ export class AccountManager {
|
|
|
3748
3805
|
runtime: Boolean(acctData.runtime),
|
|
3749
3806
|
configSourced: Boolean(acctData.configSourced),
|
|
3750
3807
|
secretName: acctData.secretName || null,
|
|
3808
|
+
capUtilization: _sanitizeCap(acctData.capUtilization, acctData.name),
|
|
3751
3809
|
enabled: acctData.enabled !== false,
|
|
3752
3810
|
refreshToken: acctData.refreshToken || null,
|
|
3753
3811
|
expiresAt: acctData.expiresAt || null,
|
|
@@ -3808,6 +3866,12 @@ export class AccountManager {
|
|
|
3808
3866
|
// all` header path (prepareRuntimeProviders) omits enabled, so a re-sent token
|
|
3809
3867
|
// NEVER silently re-enables a provider the user benched in the TUI.
|
|
3810
3868
|
if (acctData.enabled !== undefined) account.enabled = acctData.enabled !== false;
|
|
3869
|
+
// Same guard for the usage cap: the restore path carries an explicit persisted
|
|
3870
|
+
// value; the `cc all` header path omits it, so a re-sent token never clears a
|
|
3871
|
+
// cap the user set in the TUI.
|
|
3872
|
+
if (acctData.capUtilization !== undefined) {
|
|
3873
|
+
account.capUtilization = _sanitizeCap(acctData.capUtilization, account.name);
|
|
3874
|
+
}
|
|
3811
3875
|
if (account.status === 'error' && changed) {
|
|
3812
3876
|
account.status = 'active';
|
|
3813
3877
|
account.lastError = null;
|
|
@@ -3845,6 +3909,9 @@ export class AccountManager {
|
|
|
3845
3909
|
// benched across a restart — without this an intentionally-disabled GLM/Kimi
|
|
3846
3910
|
// silently comes back enabled on the next boot (restore defaults enabled:true).
|
|
3847
3911
|
enabled: a.enabled,
|
|
3912
|
+
// And the usage cap, same reasoning: a TUI-set reservation must survive both
|
|
3913
|
+
// the restart AND the next `cc all` header re-send (the upsert guard).
|
|
3914
|
+
capUtilization: a.capUtilization ?? null,
|
|
3848
3915
|
}));
|
|
3849
3916
|
}
|
|
3850
3917
|
|
|
@@ -4057,6 +4124,7 @@ export class AccountManager {
|
|
|
4057
4124
|
upstream: a.upstream,
|
|
4058
4125
|
profiles: a.profiles,
|
|
4059
4126
|
priority: a.priority,
|
|
4127
|
+
capUtilization: a.capUtilization ?? null,
|
|
4060
4128
|
runtime: a.runtime,
|
|
4061
4129
|
status: a.status,
|
|
4062
4130
|
refreshDead: Boolean(a.refreshDead),
|
package/src/index.js
CHANGED
|
@@ -2099,6 +2099,14 @@ async function syncAccountsFromDisk(diskConfig, memConfig, accountManager) {
|
|
|
2099
2099
|
accountManager.setAccountEnabled(mgr.index, enabled);
|
|
2100
2100
|
console.log(`[Maxpool] ${enabled ? 'Enabled' : 'Disabled'} account "${mgr.name}" from config`);
|
|
2101
2101
|
}
|
|
2102
|
+
// Propagate a hand-edited usage cap the same way as enabled (red-team: without
|
|
2103
|
+
// this a config-edit cap is stale until the next full reload).
|
|
2104
|
+
const diskCap = Number.isFinite(diskAcct.capUtilization) && diskAcct.capUtilization > 0 && diskAcct.capUtilization < 1
|
|
2105
|
+
? diskAcct.capUtilization : null;
|
|
2106
|
+
if (mgr.capUtilization !== diskCap) {
|
|
2107
|
+
mgr.capUtilization = diskCap;
|
|
2108
|
+
console.log(`[Maxpool] Usage cap for "${mgr.name}" ${diskCap ? `set to ${Math.round(diskCap * 100)}%` : 'removed'} from config`);
|
|
2109
|
+
}
|
|
2102
2110
|
memConfig.accounts[memIdx] = { ...memConfig.accounts[memIdx], ...diskAcct };
|
|
2103
2111
|
|
|
2104
2112
|
if (freshCred.accessToken) {
|
package/src/tui.js
CHANGED
|
@@ -183,6 +183,12 @@ function loadText(load) {
|
|
|
183
183
|
export function weeklyPolicyText(am, account) {
|
|
184
184
|
if (!am?._weeklyState || !account || account.type === 'provider') return '';
|
|
185
185
|
const state = am._weeklyState(account);
|
|
186
|
+
// USAGE CAP: the reservation outranks every tier label — a capped account is NOT
|
|
187
|
+
// exhausted, it's deliberately held, and labelling it "Wk exhausted 50%" (red team)
|
|
188
|
+
// while the bar shows half-full misstates the owner's own setting.
|
|
189
|
+
if (state === 'capped') {
|
|
190
|
+
return yellow(`Cap ${Math.round((account.capUtilization || 0) * 100)}%`);
|
|
191
|
+
}
|
|
186
192
|
if (!state || state === 'unknown' || state === 'normal') return '';
|
|
187
193
|
const rawState = am._weeklyRawState?.(account) || state;
|
|
188
194
|
const used = Number(account.quota?.unified7d);
|
|
@@ -647,6 +653,11 @@ export class TUI {
|
|
|
647
653
|
this._startSelection('rename');
|
|
648
654
|
} else if (k === 't' && this.am.accounts.length > 0) {
|
|
649
655
|
this._startSelection('toggle');
|
|
656
|
+
} else if (k === 'u' && this.am.accounts.length > 0) {
|
|
657
|
+
// USAGE CAP (owner-directed 2026-08-26): reserve capacity — the proxy benches
|
|
658
|
+
// this account at the chosen % of both the 5h and weekly windows, keeping the
|
|
659
|
+
// rest for personal use. Enter 1-99 to set; enter 0 to remove (fully utilized).
|
|
660
|
+
this._startSelection('cap');
|
|
650
661
|
} else if (k === 'd' && this.am.accounts.length > 0) {
|
|
651
662
|
this._startSelection('delete');
|
|
652
663
|
} else if (k === 'esc' || k === 'q') {
|
|
@@ -1025,6 +1036,15 @@ export class TUI {
|
|
|
1025
1036
|
this.inputBuf = '';
|
|
1026
1037
|
this.inputSensitive = false;
|
|
1027
1038
|
this.inputCb = value => this._doRename(targetIdx, String(value || '').trim());
|
|
1039
|
+
} else if (this.selAction === 'cap') {
|
|
1040
|
+
const targetIdx = this.selIdx;
|
|
1041
|
+
const current = account.name;
|
|
1042
|
+
const existing = this.am.accounts[targetIdx]?.capUtilization;
|
|
1043
|
+
this.mode = 'input';
|
|
1044
|
+
this.inputPrompt = `Usage cap % for "${current}" (1-99, 0 = off, now ${existing ? Math.round(existing * 100) + '%' : 'off'})`;
|
|
1045
|
+
this.inputBuf = '';
|
|
1046
|
+
this.inputSensitive = false;
|
|
1047
|
+
this.inputCb = value => this._doSetCap(targetIdx, String(value || '').trim());
|
|
1028
1048
|
}
|
|
1029
1049
|
}
|
|
1030
1050
|
else if (k === 'esc' || k === 'q') { this.mode = 'normal'; }
|
|
@@ -1196,6 +1216,49 @@ export class TUI {
|
|
|
1196
1216
|
}
|
|
1197
1217
|
|
|
1198
1218
|
// Rename an account in config and in the running manager.
|
|
1219
|
+
/**
|
|
1220
|
+
* USAGE CAP setter — persists to config (rollback on write failure) and applies
|
|
1221
|
+
* live. `0` / `off` / `100` REMOVE the cap (fully utilized); 1-99 set it. Runtime
|
|
1222
|
+
* providers (not in config) keep the cap in memory like their `enabled` flag —
|
|
1223
|
+
* it rides state.json across restarts and must also survive `cc all` header
|
|
1224
|
+
* re-upserts (the upsert guard in account-manager).
|
|
1225
|
+
*/
|
|
1226
|
+
async _doSetCap(idx, raw) {
|
|
1227
|
+
const account = this.am.accounts[idx];
|
|
1228
|
+
if (!account) { this._addLog('Account no longer exists'); return; }
|
|
1229
|
+
const v = String(raw || '').trim().toLowerCase();
|
|
1230
|
+
const off = v === '' || v === '0' || v === 'off' || v === '100';
|
|
1231
|
+
let pct = null;
|
|
1232
|
+
if (!off) {
|
|
1233
|
+
pct = parseInt(v, 10);
|
|
1234
|
+
if (!Number.isInteger(pct) || pct < 1 || pct > 99) {
|
|
1235
|
+
this._addLog(`Usage cap must be 1-99 (or 0 to remove) — got "${raw}"`);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const cap = off ? null : pct / 100;
|
|
1240
|
+
|
|
1241
|
+
const loc = this._configLocation(account);
|
|
1242
|
+
if (loc) {
|
|
1243
|
+
const prev = this.config[loc.array][loc.index].capUtilization ?? null;
|
|
1244
|
+
if (cap == null) delete this.config[loc.array][loc.index].capUtilization;
|
|
1245
|
+
else this.config[loc.array][loc.index].capUtilization = cap;
|
|
1246
|
+
try {
|
|
1247
|
+
await this.saveConfig(this.config);
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
// rollback both config and (below) skip the live apply
|
|
1250
|
+
if (prev == null) delete this.config[loc.array][loc.index].capUtilization;
|
|
1251
|
+
else this.config[loc.array][loc.index].capUtilization = prev;
|
|
1252
|
+
throw error;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
// No loc: a runtime provider — in-memory + state.json persistence (same as enabled).
|
|
1256
|
+
account.capUtilization = cap;
|
|
1257
|
+
this._addLog(cap == null
|
|
1258
|
+
? `Usage cap removed for "${account.name}" — fully utilized`
|
|
1259
|
+
: `Usage cap ${pct}% set for "${account.name}" — the proxy stops routing to it at ${pct}% of the 5h and weekly windows`);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1199
1262
|
async _doRename(idx, newName) {
|
|
1200
1263
|
const account = this.am.accounts[idx];
|
|
1201
1264
|
if (!account) { this._addLog('Account no longer exists'); return; }
|
|
@@ -1968,6 +2031,7 @@ export class TUI {
|
|
|
1968
2031
|
const m = this.am._fastRefillMultiplier(a);
|
|
1969
2032
|
if (m < 1) note += ` ${cyan(`fast·refill ×${m.toFixed(2)}`)}`;
|
|
1970
2033
|
}
|
|
2034
|
+
if (a.capUtilization != null) note += ` ${yellow(`cap ${Math.round(a.capUtilization * 100)}%`)}`;
|
|
1971
2035
|
} else if (q.providerQuotaSource === 'console-only') {
|
|
1972
2036
|
sesCell = emptyBar('n/a', bw);
|
|
1973
2037
|
wkCell = emptyBar('n/a', bw);
|
|
@@ -2066,7 +2130,7 @@ export class TUI {
|
|
|
2066
2130
|
if (win === 'wk' && a.type === 'provider' && a.quota?.weeklyAbsent) {
|
|
2067
2131
|
const sesTank = this.am.capacityTank?.(i, 'ses');
|
|
2068
2132
|
const approx = sesTank && sesTank.n > 0
|
|
2069
|
-
?
|
|
2133
|
+
? formatTokens(Math.round(sesTank.avg * (7 * 24) / 5)) : '--';
|
|
2070
2134
|
const cellsByName = { Current: cyan(approx), Prev: '--', Avg: '--', N: sesTank?.n ? String(sesTank.n) : '--' };
|
|
2071
2135
|
out.push(' ' + name + ' ' + prov + ' '
|
|
2072
2136
|
+ COLS.map(c => cellsByName[c].padStart(CW)).join('')
|
|
@@ -2085,11 +2149,11 @@ export class TUI {
|
|
|
2085
2149
|
// vendor says it is. `~` estimate-from-open-window, `≥` lower bound (joined late).
|
|
2086
2150
|
let cur = '--';
|
|
2087
2151
|
if (tank && !(tank.source === 'live' && unprovenLive)) {
|
|
2088
|
-
cur =
|
|
2152
|
+
cur = formatTokens(tank.avg);
|
|
2089
2153
|
} else if (nowOpen?.tokensSoFar > 0 && util > 0) {
|
|
2090
|
-
cur =
|
|
2154
|
+
cur = formatTokens(Math.round(nowOpen.tokensSoFar / util));
|
|
2091
2155
|
}
|
|
2092
|
-
const curPct = util != null && cur !== '--' ? dim(
|
|
2156
|
+
const curPct = util != null && cur !== '--' ? dim(`@${Math.round(util * 100)}%`) : '';
|
|
2093
2157
|
const st = ledger.windowStats(a.name, win);
|
|
2094
2158
|
const cellsByName = {
|
|
2095
2159
|
Current: cur.padStart(CW - (curPct ? curPct.length + 1 : 0)),
|
|
@@ -2104,7 +2168,7 @@ export class TUI {
|
|
|
2104
2168
|
|
|
2105
2169
|
out.push('');
|
|
2106
2170
|
out.push(' ' + dim('Capacity = tokens ÷ how full the provider said the window was, at close.'));
|
|
2107
|
-
out.push(' ' + dim('
|
|
2171
|
+
out.push(' ' + dim('Current @% = live window, % full now · no-weekly weekly = avg 5h × 33.6.'));
|
|
2108
2172
|
return out;
|
|
2109
2173
|
}
|
|
2110
2174
|
|