maxpool 1.11.1 → 1.13.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 +155 -18
- package/src/capacity-ledger.js +60 -2
- package/src/tui.js +70 -51
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -161,6 +161,27 @@ const DEFAULT_SCHEDULER = {
|
|
|
161
161
|
reserveConcurrencyTarget: 2, // tighter in-flight cap for reserve (capPenalty bites at inflight>2 ⇒ load fans out
|
|
162
162
|
// across the fleet before any single reserve account is dogpiled toward a 429)
|
|
163
163
|
spreadShareWeight: 3, // multiplies an account's share of recent fleet load (0..1)
|
|
164
|
+
// FAST-REFILL DISCOUNT (2026-08-25). The two LOAD-BALANCING terms — utilizationCost
|
|
165
|
+
// and paceCost — price a window by how FULL it is, never by how much absolute
|
|
166
|
+
// headroom it holds or how soon it refills. So "17% of a 5h window that refills 33.6x
|
|
167
|
+
// a week" is priced identically to "17% of a weekly window that refills once", and an
|
|
168
|
+
// account whose only cap is a fast-refilling session never pulls ahead of one guarding
|
|
169
|
+
// a scarce weekly budget. That is the whole reason the fleet's largest-capacity
|
|
170
|
+
// account sat at ~17% of fleet traffic (measured 2026-08-25) while four Claude
|
|
171
|
+
// accounts ran at weekly 1.0.
|
|
172
|
+
//
|
|
173
|
+
// The discount is a MULTIPLIER on those two terms only — never a flat bonus. A flat
|
|
174
|
+
// bonus would drive an idle account's total NEGATIVE (idle floor is concurrency×2 = 2),
|
|
175
|
+
// below the entire band structure that reserveFloorCost:5 / criticalPressureCost:21
|
|
176
|
+
// assume is non-negative. A multiplier in [0,1] cannot: it only ever REMOVES cost that
|
|
177
|
+
// is already there, so the total stays ≥ the concurrency floor and every safety term
|
|
178
|
+
// (concurrency, capPenalty, reserve, critical, ramp, failures) is untouched.
|
|
179
|
+
//
|
|
180
|
+
// It also DECAYS: at session util 0 the discount is full, and it is gone by
|
|
181
|
+
// fastRefillFadeUtil — so as the fast window fills, the account converges back to
|
|
182
|
+
// normal pricing and the fleet re-balances smoothly instead of flapping at a cliff.
|
|
183
|
+
fastRefillDiscount: 0.6, // 0 = off (full cost), 0.6 = discount up to 60% of the two balancing terms
|
|
184
|
+
fastRefillFadeUtil: 0.65, // discount reaches 0 at this session utilization (weeklySoftThreshold)
|
|
164
185
|
recoveryRampWeight: 4, // decaying penalty applied to a just-recovered account
|
|
165
186
|
recoveryRampMs: 5 * 60_000, // how long the post-recovery ramp lasts
|
|
166
187
|
spreadWindowMs: 15 * 60_000, // rolling window used to measure recent per-account load
|
|
@@ -250,6 +271,13 @@ const FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
|
|
|
250
271
|
const PERSISTED_QUOTA_FIELDS = [
|
|
251
272
|
'unified5h', 'unified7d', 'unified5hReset', 'unified7dReset', 'unifiedStatus', 'scopedWeekly',
|
|
252
273
|
'tokensLimit', 'tokensRemaining', 'requestsLimit', 'requestsRemaining', 'resetsAt',
|
|
274
|
+
// Plan identity, not a utilization: a successful probe's "no weekly window" is
|
|
275
|
+
// positive knowledge (2026-08-06). Without persisting it, every restart clears the
|
|
276
|
+
// flag and the fast-refill discount (plus the TUI "Wk none" rendering) silently
|
|
277
|
+
// drops until the next probe sweep re-learns it. The probe still rewrites it on
|
|
278
|
+
// every successful sweep, so a plan change heals on the same cadence.
|
|
279
|
+
'weeklyAbsent', 'providerSes', 'providerSesReset', 'providerWk', 'providerWkReset',
|
|
280
|
+
'providerQuotaSource', 'lastProbeOkAt',
|
|
253
281
|
];
|
|
254
282
|
|
|
255
283
|
function clampRetryAfterSeconds(value) {
|
|
@@ -1106,7 +1134,10 @@ export class AccountManager {
|
|
|
1106
1134
|
// disappears (without this, OAuth cycles essentially never close: red-team 2026-08-22).
|
|
1107
1135
|
if (q.unified5h != null && q.unified5hReset && now >= q.unified5hReset) {
|
|
1108
1136
|
console.log(`[Maxpool] Account "${account.name}" session quota reset`);
|
|
1109
|
-
|
|
1137
|
+
// TANK: q.unified5h is still the CLOSING window's fullness here — the nulls below
|
|
1138
|
+
// come after. Snapshot into the cycle before the rollover wipes it.
|
|
1139
|
+
this.capacity?.closeCycle?.(account.name, 'ses', q.unified5hReset,
|
|
1140
|
+
{ resetAt: q.unified5hReset, finalUtilization: q.unified5h });
|
|
1110
1141
|
q.unified5h = null;
|
|
1111
1142
|
q.unified5hReset = null;
|
|
1112
1143
|
changed = true;
|
|
@@ -1114,7 +1145,8 @@ export class AccountManager {
|
|
|
1114
1145
|
}
|
|
1115
1146
|
if (q.unified7d != null && q.unified7dReset && now >= q.unified7dReset) {
|
|
1116
1147
|
console.log(`[Maxpool] Account "${account.name}" weekly quota reset`);
|
|
1117
|
-
this.capacity?.closeCycle?.(account.name, 'wk', q.unified7dReset,
|
|
1148
|
+
this.capacity?.closeCycle?.(account.name, 'wk', q.unified7dReset,
|
|
1149
|
+
{ resetAt: q.unified7dReset, finalUtilization: q.unified7d });
|
|
1118
1150
|
q.unified7d = null;
|
|
1119
1151
|
q.unified7dReset = null;
|
|
1120
1152
|
q.unifiedStatus = null;
|
|
@@ -2487,7 +2519,11 @@ export class AccountManager {
|
|
|
2487
2519
|
|
|
2488
2520
|
// Burn-pace COST only (demoted from the old dominant scarcity×6 term): a
|
|
2489
2521
|
// soft de-preference of accounts burning ahead of an even pace. Never a bench.
|
|
2490
|
-
|
|
2522
|
+
// FAST-REFILL DISCOUNT: applied to the pace and utilization terms (and only
|
|
2523
|
+
// those) for an account whose only cap is a fast-refilling session window —
|
|
2524
|
+
// see the DEFAULT_SCHEDULER block for the full rationale.
|
|
2525
|
+
const refillMult = this._fastRefillMultiplier(account);
|
|
2526
|
+
const paceCost = this._accountScarcity(account, now) * this.scheduler.paceCostWeight * refillMult;
|
|
2491
2527
|
|
|
2492
2528
|
// RAW utilization cost — direct, not pace-adjusted. The pace cost above discounts
|
|
2493
2529
|
// by how far into the window you are, so an account at 80% with 2h left is only
|
|
@@ -2495,7 +2531,7 @@ export class AccountManager {
|
|
|
2495
2531
|
// benching, but wrong for load balancing: an account at 80% should be clearly less
|
|
2496
2532
|
// attractive than one at 10% even if both are "on pace". Measured 2026-08-10: cc at
|
|
2497
2533
|
// 80% scored 52.30 vs glm at 10% at 52.15 — a 0.15 gap drowned by round-robin.
|
|
2498
|
-
const utilizationCost = this._rawUtilization(account) * this.scheduler.utilizationWeight;
|
|
2534
|
+
const utilizationCost = this._rawUtilization(account) * this.scheduler.utilizationWeight * refillMult;
|
|
2499
2535
|
|
|
2500
2536
|
// Per-model weekly de-preference: an account whose scoped weekly for THIS
|
|
2501
2537
|
// request's model (e.g. Fable) is high-but-not-exhausted is a poor pick for
|
|
@@ -2635,7 +2671,7 @@ export class AccountManager {
|
|
|
2635
2671
|
* in-window) and criticalPeakUnlock is enabled. Default off.
|
|
2636
2672
|
* Precedence prereset > pressure > peak: the cheaper drain wins.
|
|
2637
2673
|
*/
|
|
2638
|
-
_criticalUnlock(account, requestInfo = {}, excludedIndexes = new Set(),
|
|
2674
|
+
_criticalUnlock(account, requestInfo = {}, excludedIndexes = new Set(), _pressureCache = null, now = Date.now()) {
|
|
2639
2675
|
const state = this._weeklyRawState(account);
|
|
2640
2676
|
if (state !== 'critical') return null;
|
|
2641
2677
|
|
|
@@ -2704,7 +2740,7 @@ export class AccountManager {
|
|
|
2704
2740
|
* - pressure/peak: a flat cost ABOVE reserve's attainable max, so critical is
|
|
2705
2741
|
* relief for a LOADED last route and never preempts an idle reserve.
|
|
2706
2742
|
*/
|
|
2707
|
-
_criticalCost(account,
|
|
2743
|
+
_criticalCost(account, _now = Date.now(), unlock = null, weeklyState = this._weeklyRawState(account)) {
|
|
2708
2744
|
if (weeklyState !== 'critical') return 0;
|
|
2709
2745
|
if (!unlock) return 0;
|
|
2710
2746
|
if (unlock.reason === 'prereset') {
|
|
@@ -2722,6 +2758,39 @@ export class AccountManager {
|
|
|
2722
2758
|
return this._criticalUnlock(account, { profile: 'claude' }, new Set(), null, now);
|
|
2723
2759
|
}
|
|
2724
2760
|
|
|
2761
|
+
/**
|
|
2762
|
+
* FAST-REFILL MULTIPLIER — 1.0 (no change) or a discount in (0,1) for an account
|
|
2763
|
+
* whose ONLY cap is a fast-refilling session window. The predicate is exactly the
|
|
2764
|
+
* one the TUI already renders ("Wk none"): a provider whose quota poll succeeded
|
|
2765
|
+
* and carried NO weekly window (positive knowledge the plan has none — measured
|
|
2766
|
+
* 2026-08-06, z.ai `max` returns one TOKENS_LIMIT unit 3 = 5h and no unit-6 weekly).
|
|
2767
|
+
*
|
|
2768
|
+
* The discount is LINEARLY FADED to 0 by session utilization: full at 0%, gone at
|
|
2769
|
+
* fastRefillFadeUtil. So the preference this grants decays as the window fills and
|
|
2770
|
+
* the account converges back to normal pricing — no cliff, no flap. At util ≥ fade
|
|
2771
|
+
* point the multiplier is exactly 1, making the term byte-identical to pre-2026-08-25
|
|
2772
|
+
* behaviour by construction.
|
|
2773
|
+
*
|
|
2774
|
+
* Rationale (why a weeklyAbsent account at all): its window refills 33.6× per week
|
|
2775
|
+
* vs a weekly window's 1×, so equal FULLNESS does not mean equal VALUE — capacity
|
|
2776
|
+
* that expires unused every 5h is worth spending faster than capacity that guards a
|
|
2777
|
+
* whole week. This is the use-it-or-lose-it principle _windowScarcity already applies
|
|
2778
|
+
* WITHIN a window, extended across window KINDS. It is a discount on balancing terms
|
|
2779
|
+
* only — never a flat bonus (a flat bonus drives the total negative, under the
|
|
2780
|
+
* non-negative band structure reserveFloorCost/criticalPressureCost were calibrated
|
|
2781
|
+
* against), and never on safety terms (concurrency, capPenalty, reserve, critical).
|
|
2782
|
+
*/
|
|
2783
|
+
_fastRefillMultiplier(account) {
|
|
2784
|
+
const disc = this.scheduler.fastRefillDiscount;
|
|
2785
|
+
if (!(disc > 0)) return 1; // feature off → multiplier 1
|
|
2786
|
+
if (!(account?.type === 'provider' && account.quota?.weeklyAbsent)) return 1;
|
|
2787
|
+
const fade = this.scheduler.fastRefillFadeUtil;
|
|
2788
|
+
const ses = clamp01(account.quota.providerSes ?? 0);
|
|
2789
|
+
if (ses >= fade) return 1;
|
|
2790
|
+
// 1 at ses=0 → 1-disc at ses=0; linear to 1 at ses=fade
|
|
2791
|
+
return 1 - disc * (1 - ses / Math.max(1e-6, fade));
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2725
2794
|
_reserveCost(account, now = Date.now(), weeklyState = this._weeklyRawState(account)) {
|
|
2726
2795
|
if (weeklyState !== 'reserve') return 0;
|
|
2727
2796
|
const q = account.quota;
|
|
@@ -2850,10 +2919,14 @@ export class AccountManager {
|
|
|
2850
2919
|
const account = this.accounts[accountIndex];
|
|
2851
2920
|
if (!account || !usage) return;
|
|
2852
2921
|
const q = account.quota;
|
|
2853
|
-
// CAPACITY LEDGER: prev stamps, so a probe observing the
|
|
2854
|
-
// the old cycle
|
|
2922
|
+
// CAPACITY LEDGER: prev stamps AND prev utilizations, so a probe observing the
|
|
2923
|
+
// window ADVANCE closes the old cycle with the OLD window's tank reading —
|
|
2924
|
+
// snapshot BEFORE the writes below clobber the fields with the new window's
|
|
2925
|
+
// values (the OAuth twin of the applyProviderUsage hook).
|
|
2855
2926
|
const prevSesReset = q.unified5hReset;
|
|
2856
2927
|
const prevWkReset = q.unified7dReset;
|
|
2928
|
+
const prevSesUtil = q.unified5h;
|
|
2929
|
+
const prevWkUtil = q.unified7d;
|
|
2857
2930
|
|
|
2858
2931
|
if (usage.fiveHour) {
|
|
2859
2932
|
if (usage.fiveHour.utilization != null) q.unified5h = clamp01(usage.fiveHour.utilization);
|
|
@@ -2863,8 +2936,8 @@ export class AccountManager {
|
|
|
2863
2936
|
if (usage.sevenDay.utilization != null) q.unified7d = clamp01(usage.sevenDay.utilization);
|
|
2864
2937
|
if (usage.sevenDay.resetAt != null) q.unified7dReset = usage.sevenDay.resetAt;
|
|
2865
2938
|
}
|
|
2866
|
-
this.noteCapacityWindowAdvance(account.name, 'ses', prevSesReset, usage.fiveHour?.resetAt);
|
|
2867
|
-
this.noteCapacityWindowAdvance(account.name, 'wk', prevWkReset, usage.sevenDay?.resetAt);
|
|
2939
|
+
this.noteCapacityWindowAdvance(account.name, 'ses', prevSesReset, usage.fiveHour?.resetAt, prevSesUtil);
|
|
2940
|
+
this.noteCapacityWindowAdvance(account.name, 'wk', prevWkReset, usage.sevenDay?.resetAt, prevWkUtil);
|
|
2868
2941
|
// Utilization readings feed the capacity ESTIMATE. The probe path passes per-window
|
|
2869
2942
|
// marks so the DELTA method can difference consecutive readings.
|
|
2870
2943
|
this.capacity.noteUtilizationObserved(Date.now(), [
|
|
@@ -2981,11 +3054,14 @@ export class AccountManager {
|
|
|
2981
3054
|
const account = this.accounts[accountIndex];
|
|
2982
3055
|
if (!account || !usage) return;
|
|
2983
3056
|
const q = account.quota;
|
|
2984
|
-
// CAPACITY LEDGER: snapshot the previous reset stamps so a probe
|
|
2985
|
-
// window ADVANCE (new stamp) closes the capacity cycle at the old
|
|
2986
|
-
//
|
|
3057
|
+
// CAPACITY LEDGER: snapshot the previous reset stamps AND utilizations so a probe
|
|
3058
|
+
// observing the window ADVANCE (new stamp) closes the capacity cycle at the old
|
|
3059
|
+
// boundary WITH the old window's tank reading — covers windows whose old stamp
|
|
3060
|
+
// was never learned (clock-close can't fire). Snapshot before the writes below.
|
|
2987
3061
|
const prevSesReset = q.providerSesReset;
|
|
2988
3062
|
const prevWkReset = q.providerWkReset;
|
|
3063
|
+
const prevSesUtil = q.providerSes;
|
|
3064
|
+
const prevWkUtil = q.providerWk;
|
|
2989
3065
|
if (usage.error) {
|
|
2990
3066
|
// Distinguish "no pollable quota" (Kimi) from a transient probe failure.
|
|
2991
3067
|
// Never clear existing values on a transient error — let them age into the
|
|
@@ -3013,8 +3089,8 @@ export class AccountManager {
|
|
|
3013
3089
|
q.weeklyAbsent = true;
|
|
3014
3090
|
}
|
|
3015
3091
|
q.lastProbeOkAt = Date.now();
|
|
3016
|
-
this.noteCapacityWindowAdvance(account.name, 'ses', prevSesReset, usage.ses?.resetAt);
|
|
3017
|
-
this.noteCapacityWindowAdvance(account.name, 'wk', prevWkReset, usage.wk?.resetAt);
|
|
3092
|
+
this.noteCapacityWindowAdvance(account.name, 'ses', prevSesReset, usage.ses?.resetAt, prevSesUtil);
|
|
3093
|
+
this.noteCapacityWindowAdvance(account.name, 'wk', prevWkReset, usage.wk?.resetAt, prevWkUtil);
|
|
3018
3094
|
this.capacity.noteUtilizationObserved(Date.now(), [
|
|
3019
3095
|
{ name: account.name, window: 'ses', utilization: usage.ses?.utilization },
|
|
3020
3096
|
{ name: account.name, window: 'wk', utilization: usage.wk?.utilization },
|
|
@@ -3212,6 +3288,24 @@ export class AccountManager {
|
|
|
3212
3288
|
return this.capacity.estimateFromUtilization(a.name, window, util) || null;
|
|
3213
3289
|
}
|
|
3214
3290
|
|
|
3291
|
+
/** Measured TANK for an account+window: capacity, not delivery. Prefers completed
|
|
3292
|
+
* cycles (tokens ÷ closing utilization, averaged); falls back to the live open
|
|
3293
|
+
* window's estimate so a row is useful from minute one instead of reading
|
|
3294
|
+
* "no completed cycle yet" while the vendor is plainly reporting a percentage. */
|
|
3295
|
+
capacityTank(accountIndex, window) {
|
|
3296
|
+
const a = this.accounts[accountIndex];
|
|
3297
|
+
if (!a) return null;
|
|
3298
|
+
const measured = this.capacity.tankStats(a.name, window);
|
|
3299
|
+
if (measured) return { ...measured, source: 'cycles' };
|
|
3300
|
+
const est = this.capacityEstimate(accountIndex, window);
|
|
3301
|
+
if (!est) return null;
|
|
3302
|
+
return {
|
|
3303
|
+
avg: est.tokens, last: est.tokens, n: 0, exact: est.lowerBound ? 0 : 1,
|
|
3304
|
+
bounded: est.lowerBound ? 1 : 0, lowerBound: Boolean(est.lowerBound),
|
|
3305
|
+
source: 'live', utilization: est.utilization, method: est.method, fresh: est.fresh,
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3215
3309
|
accrueCapacity(accountIndex, { input = 0, output = 0 } = {}) {
|
|
3216
3310
|
const account = this.accounts[accountIndex];
|
|
3217
3311
|
if (!account) return;
|
|
@@ -3224,6 +3318,18 @@ export class AccountManager {
|
|
|
3224
3318
|
this.capacity.accrue(account.name, { input, output }, undefined, windows);
|
|
3225
3319
|
}
|
|
3226
3320
|
|
|
3321
|
+
/** The vendor's CURRENT fullness reading for a window — the tank numerator's
|
|
3322
|
+
* denominator. Returns null when unreadable (a null reading divides nothing and
|
|
3323
|
+
* must never coerce to 0, which would make tank = Infinity). */
|
|
3324
|
+
_windowUtilization(account, window) {
|
|
3325
|
+
const q = account?.quota;
|
|
3326
|
+
if (!q) return null;
|
|
3327
|
+
const v = account.type === 'provider'
|
|
3328
|
+
? (window === 'wk' ? q.providerWk : q.providerSes)
|
|
3329
|
+
: (window === 'wk' ? q.unified7d : q.unified5h);
|
|
3330
|
+
return Number.isFinite(v) && v >= 0 ? v : null;
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3227
3333
|
/** Close any window cycle whose reset time has passed — CLOCK-AUTHORITATIVE, so a
|
|
3228
3334
|
* stale or dead probe can never leave a cycle open and mis-attribute the next
|
|
3229
3335
|
* window's tokens to it (pre-mortem M5; worst case is the no-weekly account whose
|
|
@@ -3267,7 +3373,14 @@ export class AccountManager {
|
|
|
3267
3373
|
// here as well (round-2) made a prober-first notice silently swallow all of
|
|
3268
3374
|
// those whenever the sweep won the race (red-team round 3, RT3-1).
|
|
3269
3375
|
if (resetAt && now >= resetAt) {
|
|
3270
|
-
this.capacity.closeCycle(a.name, win, resetAt, {
|
|
3376
|
+
this.capacity.closeCycle(a.name, win, resetAt, {
|
|
3377
|
+
resetAt,
|
|
3378
|
+
// TANK: the vendor's own fullness for the window we are closing. Read it
|
|
3379
|
+
// BEFORE _clearExpiredQuotas nulls it — this sweep runs first by design
|
|
3380
|
+
// (see the close-only note above), which is exactly why the reading is
|
|
3381
|
+
// still the CLOSING window's and not the new one's.
|
|
3382
|
+
finalUtilization: this._windowUtilization(a, win),
|
|
3383
|
+
});
|
|
3271
3384
|
}
|
|
3272
3385
|
}
|
|
3273
3386
|
}
|
|
@@ -3275,7 +3388,7 @@ export class AccountManager {
|
|
|
3275
3388
|
|
|
3276
3389
|
/** Close a cycle because a probe observed the window ADVANCE (a new reset stamp) —
|
|
3277
3390
|
* covers the case where the old stamp was never learned. */
|
|
3278
|
-
noteCapacityWindowAdvance(accountName, window, prevResetAt, nextResetAt) {
|
|
3391
|
+
noteCapacityWindowAdvance(accountName, window, prevResetAt, nextResetAt, prevUtilization = null) {
|
|
3279
3392
|
if (!prevResetAt || !nextResetAt) return;
|
|
3280
3393
|
// TWO guards, both learned from live data (2026-08-23):
|
|
3281
3394
|
// 1. PAST stamp = a probe that answered late (its window rolled mid-request) or
|
|
@@ -3296,7 +3409,15 @@ export class AccountManager {
|
|
|
3296
3409
|
// expired) — endedAt is always within [start, now].
|
|
3297
3410
|
const boundary = Math.min(nextResetAt, nowMs);
|
|
3298
3411
|
if (boundary - prevResetAt < WINDOW_ADVANCE_EPSILON_MS) return;
|
|
3299
|
-
|
|
3412
|
+
// TANK: the CLOSING window's own fullness, passed in by the caller. It must be the
|
|
3413
|
+
// caller's SNAPSHOT, never a re-read here: both probe paths write the new window's
|
|
3414
|
+
// utilization into the quota fields before calling us, so re-reading would divide
|
|
3415
|
+
// the old window's tokens by the NEW window's percentage — a silently wrong tank
|
|
3416
|
+
// on exactly the rollover this path exists to catch.
|
|
3417
|
+
this.capacity.closeCycle(accountName, window, boundary, {
|
|
3418
|
+
resetAt: prevResetAt,
|
|
3419
|
+
finalUtilization: Number.isFinite(prevUtilization) && prevUtilization >= 0 ? prevUtilization : null,
|
|
3420
|
+
});
|
|
3300
3421
|
}
|
|
3301
3422
|
|
|
3302
3423
|
/**
|
|
@@ -3974,6 +4095,22 @@ export class AccountManager {
|
|
|
3974
4095
|
safetyMaxActivePerAccount: this.scheduler.safetyMaxActivePerAccount,
|
|
3975
4096
|
safetyMaxGlobalActive: this.scheduler.safetyMaxGlobalActive,
|
|
3976
4097
|
peak: this.peakSummary(),
|
|
4098
|
+
// FAST-REFILL visibility (2026-08-25): monitors must be able to assert the
|
|
4099
|
+
// discount is ARMED (config > 0) and, per eligible account, the multiplier
|
|
4100
|
+
// actually being applied — a flag that can never show "inert" is not a
|
|
4101
|
+
// monitorable feature. Mirrors the peak block's shape.
|
|
4102
|
+
fastRefill: {
|
|
4103
|
+
enabled: this.scheduler.fastRefillDiscount > 0,
|
|
4104
|
+
discount: this.scheduler.fastRefillDiscount,
|
|
4105
|
+
fadeUtil: this.scheduler.fastRefillFadeUtil,
|
|
4106
|
+
accounts: this.accounts
|
|
4107
|
+
.filter(a => a.type === 'provider' && a.quota?.weeklyAbsent)
|
|
4108
|
+
.map(a => ({
|
|
4109
|
+
name: a.name,
|
|
4110
|
+
sesUtilization: clamp01(a.quota.providerSes ?? 0),
|
|
4111
|
+
multiplier: Number(this._fastRefillMultiplier(a).toFixed(3)),
|
|
4112
|
+
})),
|
|
4113
|
+
},
|
|
3977
4114
|
},
|
|
3978
4115
|
upstreamThrottle: {
|
|
3979
4116
|
active: this._isUpstreamThrottleBlocking(),
|
package/src/capacity-ledger.js
CHANGED
|
@@ -199,8 +199,15 @@ export class CapacityLedger {
|
|
|
199
199
|
|
|
200
200
|
/** Close the open cycle for a window (M5: clock-authoritative — the close is keyed
|
|
201
201
|
* on `endedAt`, which the caller derives from the reset stamp or the clock, and the
|
|
202
|
-
* cycle keeps its own book regardless of probe health). No-op if none open.
|
|
203
|
-
|
|
202
|
+
* cycle keeps its own book regardless of probe health). No-op if none open.
|
|
203
|
+
*
|
|
204
|
+
* TANK (2026-08-25, owner-directed): the closed row records `finalUtilization` —
|
|
205
|
+
* the vendor's own fullness reading for the window at close. tank = tokens ÷ util
|
|
206
|
+
* is the CAPACITY of the plan, as distinct from the tokens we happened to deliver.
|
|
207
|
+
* Delivery measures demand; tank measures the plan. Both are recorded; the UI
|
|
208
|
+
* decides which to show. Recording happens on a best-effort basis here (the ledger
|
|
209
|
+
* keeps its own book — the caller passes the reading in, it does not poll). */
|
|
210
|
+
closeCycle(name, window, endedAt = this._now(), { resetAt = null, finalUtilization = null } = {}) {
|
|
204
211
|
const rec = this._accounts.get(name);
|
|
205
212
|
if (!rec || !rec[window]?.open) return null;
|
|
206
213
|
const open = rec[window].open;
|
|
@@ -221,7 +228,10 @@ export class CapacityLedger {
|
|
|
221
228
|
// Fold ONLY a complete tail: folding a partial/disabled tail would flip the
|
|
222
229
|
// flags on the prior legitimate observation and ERASE it from the averages
|
|
223
230
|
// (round 3, RT3-2) — strictly worse than leaving a tiny excluded cycle.
|
|
231
|
+
// The fold ALSO takes the tail's tank reading if the prior row lacks one —
|
|
232
|
+
// same boundary, same window, so the later reading is simply fresher.
|
|
224
233
|
prev.tokens += open.tokensSoFar;
|
|
234
|
+
if (prev.finalUtilization == null && finalUtilization != null) prev.finalUtilization = finalUtilization;
|
|
225
235
|
rec[window].open = null;
|
|
226
236
|
return prev;
|
|
227
237
|
}
|
|
@@ -232,6 +242,12 @@ export class CapacityLedger {
|
|
|
232
242
|
complete: open.complete,
|
|
233
243
|
disabledDuring: open.disabledDuring,
|
|
234
244
|
...(open.partialReason ? { partialReason: open.partialReason } : {}),
|
|
245
|
+
...(finalUtilization != null ? { finalUtilization } : {}),
|
|
246
|
+
// Carried onto the closed row because tankStats needs it: a cycle observed from
|
|
247
|
+
// its window START yields an EXACT tank; one we joined late yields a lower bound
|
|
248
|
+
// (we only counted the tokens that flowed through maxpool, while the vendor's
|
|
249
|
+
// percentage counts everything). Dropping it here made every tank read "bounded".
|
|
250
|
+
...(open.windowStartedAt != null ? { windowStartedAt: open.windowStartedAt } : {}),
|
|
235
251
|
resetAt,
|
|
236
252
|
});
|
|
237
253
|
if (rec[window].closed.length > MAX_CYCLES_PER_WINDOW) rec[window].closed.shift();
|
|
@@ -366,6 +382,48 @@ export class CapacityLedger {
|
|
|
366
382
|
* ABSENCE (present in dayKeys, absent from days) — and with MAX_DAY_BUCKETS=10 an
|
|
367
383
|
* idle day no longer even evicts; only real activity ages out. `partial` is true
|
|
368
384
|
* when any bucket in the window is flagged partial — the figure is ≤ observed. */
|
|
385
|
+
/** TANK STATS — the CAPACITY of the plan, from the owner's own formula:
|
|
386
|
+
* tank = tokens delivered ÷ utilization at close, per cycle, averaged across
|
|
387
|
+
* cycles (2026-08-25, owner-directed). This measures the plan, not the demand:
|
|
388
|
+
* a cycle that delivered 812k at 96% and one that delivered 51k at 6% both say
|
|
389
|
+
* "~846k tank". Delivered-only averages (windowStats) measure demand and stay
|
|
390
|
+
* available separately.
|
|
391
|
+
*
|
|
392
|
+
* Guards, because the raw formula lies in two ways:
|
|
393
|
+
* - We only count tokens that flowed THROUGH maxpool; a cycle whose vendor util
|
|
394
|
+
* includes spend we never saw (joined mid-window, or usage outside the proxy)
|
|
395
|
+
* yields a tank ≥ the truth but not equal to it. Only a cycle observed from its
|
|
396
|
+
* window start is exact; later ones are marked `lowerBound`.
|
|
397
|
+
* - Vendors report whole percents. At 3% full, 1pp of rounding = 33% error, so a
|
|
398
|
+
* reading below MIN_UTIL is excluded (rounding-dominated) rather than folded
|
|
399
|
+
* into the average as fake precision.
|
|
400
|
+
* Returns { avg, exact, n, bounded, last } or null when no usable readings. */
|
|
401
|
+
tankStats(name, window) {
|
|
402
|
+
const rec = this._accounts.get(name);
|
|
403
|
+
const floor = (this._readFloorOverride ?? READ_FLOOR_MS)[window] ?? 0;
|
|
404
|
+
const usable = (rec?.[window]?.closed || []).filter(c =>
|
|
405
|
+
c.complete && !c.disabledDuring
|
|
406
|
+
&& Number.isFinite(c.finalUtilization)
|
|
407
|
+
&& c.finalUtilization >= 0.05
|
|
408
|
+
&& (c.endedAt - c.startedAt) >= floor - 1_000);
|
|
409
|
+
if (!usable.length) return null;
|
|
410
|
+
let sum = 0, exact = 0, bounded = 0;
|
|
411
|
+
for (const c of usable) {
|
|
412
|
+
const observedFromStart = c.startedAt != null && c.windowStartedAt != null
|
|
413
|
+
&& c.startedAt <= c.windowStartedAt + 60_000;
|
|
414
|
+
sum += c.tokens / c.finalUtilization;
|
|
415
|
+
if (observedFromStart) exact++; else bounded++;
|
|
416
|
+
}
|
|
417
|
+
const last = usable[usable.length - 1];
|
|
418
|
+
return {
|
|
419
|
+
avg: Math.round(sum / usable.length),
|
|
420
|
+
exact, bounded,
|
|
421
|
+
n: usable.length,
|
|
422
|
+
last: Math.round(last.tokens / last.finalUtilization),
|
|
423
|
+
lowerBound: bounded > 0 && exact === 0,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
369
427
|
rollingThroughput(name, days = 7) {
|
|
370
428
|
const rec = this._accounts.get(name);
|
|
371
429
|
if (!rec) return { tokens: 0, partial: false };
|
package/src/tui.js
CHANGED
|
@@ -1961,6 +1961,13 @@ export class TUI {
|
|
|
1961
1961
|
if (tier === 2) note += ` ${yellow(`peak·capped ${Math.round((wu ?? 0) * 100)}%`)}`;
|
|
1962
1962
|
else if (tier === 1) note += wu == null ? ` ${dim('peak·cap n/a')}` : ` ${yellow('peak')}`;
|
|
1963
1963
|
}
|
|
1964
|
+
// FAST-REFILL tag (2026-08-25): mirrors the router's own _fastRefillMultiplier —
|
|
1965
|
+
// the label cannot disagree with routing because it reads the same predicate.
|
|
1966
|
+
// Only shown when a discount is actually applied (< 1).
|
|
1967
|
+
if (a.quota?.weeklyAbsent && this.am._fastRefillMultiplier) {
|
|
1968
|
+
const m = this.am._fastRefillMultiplier(a);
|
|
1969
|
+
if (m < 1) note += ` ${cyan(`fast·refill ×${m.toFixed(2)}`)}`;
|
|
1970
|
+
}
|
|
1964
1971
|
} else if (q.providerQuotaSource === 'console-only') {
|
|
1965
1972
|
sesCell = emptyBar('n/a', bw);
|
|
1966
1973
|
wkCell = emptyBar('n/a', bw);
|
|
@@ -2024,7 +2031,7 @@ export class TUI {
|
|
|
2024
2031
|
const ledger = this.am.capacity;
|
|
2025
2032
|
const title = win === 'wk' ? 'Weekly (7d) capacity' : 'Session (5h) capacity';
|
|
2026
2033
|
out.push('');
|
|
2027
|
-
out.push(` ${bold(title)} ${dim('— tokens
|
|
2034
|
+
out.push(` ${bold(title)} ${dim('— how many tokens each account can deliver per window')}`);
|
|
2028
2035
|
out.push('');
|
|
2029
2036
|
|
|
2030
2037
|
if (!ledger) { out.push(yellow(' Capacity ledger unavailable on this worker.')); return out; }
|
|
@@ -2032,15 +2039,19 @@ export class TUI {
|
|
|
2032
2039
|
// Narrow terminals: drop trailing columns rather than let fitLine chop a number
|
|
2033
2040
|
// mid-digit (at W=80 the full 6-column row is 82+ chars — every row silently lost
|
|
2034
2041
|
// its last two cells). The dropped ones are the aggregates, not the observations.
|
|
2035
|
-
|
|
2036
|
-
|
|
2042
|
+
// CAPACITY (tank) is the headline: tokens ÷ the vendor's own fullness at close,
|
|
2043
|
+
// per cycle. That measures the PLAN. The delivered-token columns measure DEMAND —
|
|
2044
|
+
// useful, but they were the headline before 2026-08-25 and read as capacity, which
|
|
2045
|
+
// is why an account that simply went unused looked small.
|
|
2046
|
+
const ALL_COLS = ['Capacity', 'Used now', 'Last cyc', 'Avg cyc'];
|
|
2047
|
+
const CW = 10;
|
|
2037
2048
|
const nameW = 12;
|
|
2038
2049
|
let COLS = ALL_COLS;
|
|
2039
|
-
while (COLS.length > 1 && (nameW + PROVIDER_W + 2 + COLS.length * CW +
|
|
2050
|
+
while (COLS.length > 1 && (nameW + PROVIDER_W + 2 + COLS.length * CW + 16) > W) {
|
|
2040
2051
|
COLS = COLS.slice(0, -1);
|
|
2041
2052
|
}
|
|
2042
2053
|
const header = ' ' + 'Account'.padEnd(nameW) + ' ' + 'Provider'.padEnd(PROVIDER_W) + ' '
|
|
2043
|
-
+ COLS.map(c => c.padStart(CW)).join('') + '
|
|
2054
|
+
+ COLS.map(c => c.padStart(CW)).join('') + ' Basis';
|
|
2044
2055
|
out.push(dimUnderline(fitLine(header, W)));
|
|
2045
2056
|
|
|
2046
2057
|
let anyData = false;
|
|
@@ -2058,14 +2069,16 @@ export class TUI {
|
|
|
2058
2069
|
// capacity). 33.6 five-hour windows fit in 7 days — the user's own
|
|
2059
2070
|
// approximation ("from the session limits"), shipped as a ceiling, never a cap.
|
|
2060
2071
|
const t = ledger.rollingThroughput(a.name, 7);
|
|
2061
|
-
const ses = ledger.windowStats(a.name, 'ses');
|
|
2062
2072
|
const windowsPerWk = (7 * 24) / 5;
|
|
2063
2073
|
anyData = anyData || t.tokens > 0;
|
|
2064
2074
|
const vol = t.tokens > 0 ? formatTokens(t.tokens) : '--';
|
|
2065
|
-
// The ceiling needs a measured session capacity
|
|
2066
|
-
//
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2075
|
+
// The ceiling needs a measured session TANK (capacity, not avg delivery) —
|
|
2076
|
+
// multiplying an old avg-delivery number understates a demand-limited account.
|
|
2077
|
+
const sesTank = this.am.capacityTank?.(i, 'ses');
|
|
2078
|
+
const ceiling = sesTank
|
|
2079
|
+
? ` · ≈${formatTokens(Math.round(windowsPerWk * sesTank.avg))}/wk ceiling`
|
|
2080
|
+
+ dim(` (${windowsPerWk.toFixed(0)} × ${formatTokens(sesTank.avg)} per 5h)`)
|
|
2081
|
+
: '';
|
|
2069
2082
|
// Always disclose the window's age boundary: today is unfinished, so the 7d
|
|
2070
2083
|
// figure grows through the day; a genuinely partial day adds the ≤-observed floor.
|
|
2071
2084
|
const note = t.partial
|
|
@@ -2076,54 +2089,60 @@ export class TUI {
|
|
|
2076
2089
|
continue;
|
|
2077
2090
|
}
|
|
2078
2091
|
const st = ledger.windowStats(a.name, win);
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
const op = est.lowerBound ? '≥' : '~';
|
|
2098
|
-
const via = est.method === 'delta'
|
|
2099
|
-
? `Δ ${(est.utilization * 100).toFixed(0)}% full` : `${(est.utilization * 100).toFixed(0)}% full`;
|
|
2100
|
-
out.push(' ' + name + ' ' + prov + ' ' + cyan(op + formatTokens(est.tokens).padStart(CW - 1))
|
|
2101
|
-
+ dim(` est from ${via}${caveat} — measured after this window completes`) + nowTag);
|
|
2102
|
-
} else {
|
|
2103
|
-
out.push(' ' + name + ' ' + prov + ' ' + dim('no completed cycle yet') + nowTag);
|
|
2104
|
-
}
|
|
2092
|
+
let tank = this.am.capacityTank?.(i, win);
|
|
2093
|
+
const nowOpen = ledger.openCycle(a.name, win);
|
|
2094
|
+
const util = this.am._windowUtilization?.(a, win);
|
|
2095
|
+
// A reading we cannot prove is from THIS window must not badge the capacity
|
|
2096
|
+
// number as "live" — it may describe the previous window entirely (the estimate
|
|
2097
|
+
// still renders; the basis line just stops claiming freshness it can't prove).
|
|
2098
|
+
if (tank?.source === 'live' && tank.fresh === false) tank = null;
|
|
2099
|
+
|
|
2100
|
+
// A row with NEITHER a tank nor any delivery has genuinely nothing to say. Say
|
|
2101
|
+
// WHY in the account's own terms — "no completed cycle yet" was true and useless
|
|
2102
|
+
// (reported 2026-08-25: an account sitting at 99% weekly rendered that line).
|
|
2103
|
+
if (!tank && !st && !(nowOpen?.tokensSoFar > 0)) {
|
|
2104
|
+
const why = util == null
|
|
2105
|
+
? 'no quota reading from this provider'
|
|
2106
|
+
: util > 0
|
|
2107
|
+
? `${(util * 100).toFixed(0)}% used, but no traffic through maxpool to measure with`
|
|
2108
|
+
: 'window empty — nothing used yet';
|
|
2109
|
+
out.push(' ' + name + ' ' + prov + ' ' + dim(why));
|
|
2105
2110
|
continue;
|
|
2106
2111
|
}
|
|
2107
2112
|
anyData = true;
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2113
|
+
|
|
2114
|
+
// Capacity: measured tank, or the live in-window estimate. `~` = estimate from an
|
|
2115
|
+
// open window; `≥` = we joined the window late, so the vendor's percentage counts
|
|
2116
|
+
// spend maxpool never saw and the true tank is at least this.
|
|
2117
|
+
const capCell = tank
|
|
2118
|
+
? (tank.lowerBound ? '≥' : tank.source === 'live' ? '~' : ' ') + formatTokens(tank.avg)
|
|
2119
|
+
: '--';
|
|
2120
|
+
const usedNow = nowOpen?.tokensSoFar > 0 ? formatTokens(nowOpen.tokensSoFar) : '--';
|
|
2121
|
+
const cellsByName = {
|
|
2122
|
+
Capacity: capCell,
|
|
2123
|
+
'Used now': usedNow,
|
|
2124
|
+
'Last cyc': st ? formatTokens(st.last) : '--',
|
|
2125
|
+
'Avg cyc': st ? formatTokens(st.avg10) : '--',
|
|
2126
|
+
};
|
|
2127
|
+
const cells = COLS.map(c => cellsByName[c].padStart(CW)).join('');
|
|
2128
|
+
|
|
2129
|
+
// Basis: how the capacity number was arrived at, in one short phrase. Never a
|
|
2130
|
+
// bare count — "3" told the reader nothing about what it was counting.
|
|
2131
|
+
const basis = !tank ? dim('no capacity reading yet')
|
|
2132
|
+
: tank.source === 'cycles'
|
|
2133
|
+
? dim(`${tank.n} full ${tank.n === 1 ? 'window' : 'windows'}`)
|
|
2134
|
+
: dim(`live · ${((tank.utilization ?? 0) * 100).toFixed(0)}% used`);
|
|
2135
|
+
const pct = util != null && tank?.source !== 'live' ? dim(` (${(util * 100).toFixed(0)}% full now)`) : '';
|
|
2136
|
+
out.push(' ' + name + ' ' + prov + ' ' + cyan(cells) + ' ' + basis + pct);
|
|
2114
2137
|
}
|
|
2115
2138
|
|
|
2116
2139
|
out.push('');
|
|
2117
2140
|
if (!anyData) {
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
: ' A session figure appears after an account\'s 5h window resets once.'));
|
|
2124
|
-
}
|
|
2125
|
-
out.push(' ' + dim('A cycle counts only if maxpool ran for all of it and the account stayed enabled.'));
|
|
2126
|
-
out.push(' ' + dim('~ = estimated; ≥ = at least; Δ = exact-by-difference; ≈/wk = session-rate ceiling; ▸ = live this window.'));
|
|
2141
|
+
out.push(' ' + yellow('Nothing to measure yet.')
|
|
2142
|
+
+ dim(' Capacity needs traffic through maxpool plus a quota reading from the provider.'));
|
|
2143
|
+
}
|
|
2144
|
+
out.push(' ' + dim('Capacity = tokens delivered ÷ how full the provider said the window was.'));
|
|
2145
|
+
out.push(' ' + dim('~ = from the window still running · ≥ = at least this (maxpool joined the window late).'));
|
|
2127
2146
|
return out;
|
|
2128
2147
|
}
|
|
2129
2148
|
|