maxpool 1.24.23 → 1.24.25
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 +1 -1
- package/src/capacity-ledger.js +64 -7
- package/src/tui.js +55 -22
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -3728,7 +3728,7 @@ export class AccountManager {
|
|
|
3728
3728
|
spannedDays.add(new Date(now).toISOString().slice(0, 10));
|
|
3729
3729
|
for (const name of this.capacity.accounts()) {
|
|
3730
3730
|
if (this.capacity.openCycle(name, 'ses') || this.capacity.openCycle(name, 'wk')) {
|
|
3731
|
-
this.capacity.markPartial(name);
|
|
3731
|
+
this.capacity.markPartial(name, { reason: 'maxpool-downtime' });
|
|
3732
3732
|
}
|
|
3733
3733
|
for (const day of spannedDays) this.capacity.markDayPartial(name, day);
|
|
3734
3734
|
}
|
package/src/capacity-ledger.js
CHANGED
|
@@ -43,6 +43,14 @@ const READ_FLOOR_MS = { ses: 4 * 3600_000, wk: 5 * 86400_000 };
|
|
|
43
43
|
// in-window estimate must apply the same floor; one guarding without the other is how
|
|
44
44
|
// the absurd number reached the screen in the first place.
|
|
45
45
|
export const TANK_MIN_UTIL = 0.05;
|
|
46
|
+
|
|
47
|
+
// Nominal window length. A closed cycle much LONGER than its window swallowed a
|
|
48
|
+
// rollover it never saw (measured live 2026-10-13: "weekly" cycles of 11.9d-14d,
|
|
49
|
+
// "5h" cycles of 7-8h) — two tanks' tokens over one tank's percentage. Not an
|
|
50
|
+
// observation of one window; excluded from tank stats.
|
|
51
|
+
const WINDOW_MS = { ses: 5 * 3600_000, wk: 7 * 86400_000 };
|
|
52
|
+
const WINDOW_CEIL_FACTOR = 1.25;
|
|
53
|
+
|
|
46
54
|
const MAX_CYCLES_PER_WINDOW = 50;
|
|
47
55
|
const MAX_DAY_BUCKETS = 10;
|
|
48
56
|
|
|
@@ -190,7 +198,7 @@ export class CapacityLedger {
|
|
|
190
198
|
|
|
191
199
|
/** Mark the open cycles as partial (maxpool was down / an account was disabled).
|
|
192
200
|
* Called by the boot path when a gap is detected (B2), and by the disable hook. */
|
|
193
|
-
markPartial(name, { disabled = false } = {}) {
|
|
201
|
+
markPartial(name, { disabled = false, reason = null } = {}) {
|
|
194
202
|
const rec = this._accounts.get(name);
|
|
195
203
|
if (!rec) return;
|
|
196
204
|
for (const w of ['ses', 'wk']) {
|
|
@@ -200,8 +208,15 @@ export class CapacityLedger {
|
|
|
200
208
|
// account out of rotation. Setting both for a disable made the disabled flag
|
|
201
209
|
// query-redundant and therefore untested (red-team F6-1) — each now excludes on
|
|
202
210
|
// its own, and each is pinned by its own test.
|
|
211
|
+
// `reason` ('maxpool-downtime', 'no-weekly-plan', …) rides along for display;
|
|
212
|
+
// a bare-false (reason null) keeps the historic shape. It is NEVER
|
|
213
|
+
// 'pre-v3-unverified' — that reason alone means "never trust this row"
|
|
214
|
+
// (see tankStats' reason gate).
|
|
203
215
|
if (disabled) rec[w].open.disabledDuring = true;
|
|
204
|
-
else
|
|
216
|
+
else {
|
|
217
|
+
rec[w].open.complete = false;
|
|
218
|
+
if (reason != null && !rec[w].open.partialReason) rec[w].open.partialReason = reason;
|
|
219
|
+
}
|
|
205
220
|
}
|
|
206
221
|
}
|
|
207
222
|
|
|
@@ -219,6 +234,15 @@ export class CapacityLedger {
|
|
|
219
234
|
const rec = this._accounts.get(name);
|
|
220
235
|
if (!rec || !rec[window]?.open) return null;
|
|
221
236
|
const open = rec[window].open;
|
|
237
|
+
// The closing window's fullness is the HIGHEST reading the cycle saw, never
|
|
238
|
+
// merely the latest: fullness only rises inside a window, so a caller's reading
|
|
239
|
+
// BELOW the peak is the next window's, arrived before this close ran (measured
|
|
240
|
+
// live 2026-10-13: kira closed a 3.1M-token week at "3% full" ⇒ a 105M tank —
|
|
241
|
+
// the reading was the freshly-reset NEXT week's).
|
|
242
|
+
if (Number.isFinite(open.peakUtilization)
|
|
243
|
+
&& !(Number.isFinite(finalUtilization) && finalUtilization >= open.peakUtilization)) {
|
|
244
|
+
finalUtilization = open.peakUtilization;
|
|
245
|
+
}
|
|
222
246
|
// ONE CYCLE PER BOUNDARY CLOSURE — the structural backstop for the two-closer race
|
|
223
247
|
// (round-2 F1). If both closers observe the SAME reset stamp within the same
|
|
224
248
|
// rollover moment, the second close's tokens are a straddling tail of that same
|
|
@@ -332,9 +356,15 @@ export class CapacityLedger {
|
|
|
332
356
|
if (!marks) return;
|
|
333
357
|
this._utilMarks = this._utilMarks || new Map();
|
|
334
358
|
for (const m of marks) {
|
|
335
|
-
if (!(m.utilization
|
|
359
|
+
if (!Number.isFinite(m.utilization) || m.utilization < 0) continue;
|
|
336
360
|
const open = this.openCycle(m.name, m.window);
|
|
337
361
|
if (!open) continue;
|
|
362
|
+
// PEAK: fullness only rises inside one window, so the highest reading a cycle
|
|
363
|
+
// saw is its closing fullness (closeCycle prefers it over a later, lower one).
|
|
364
|
+
if (!Number.isFinite(open.peakUtilization) || m.utilization > open.peakUtilization) {
|
|
365
|
+
open.peakUtilization = Math.min(1, m.utilization);
|
|
366
|
+
}
|
|
367
|
+
if (!(m.utilization < 1)) continue;
|
|
338
368
|
const key = `${m.name}:${m.window}`;
|
|
339
369
|
const prev = this._utilMarks.get(key);
|
|
340
370
|
// Keep the OLDEST usable mark within this cycle: a wider span means a larger
|
|
@@ -409,8 +439,24 @@ export class CapacityLedger {
|
|
|
409
439
|
tankStats(name, window) {
|
|
410
440
|
const rec = this._accounts.get(name);
|
|
411
441
|
const floor = (this._readFloorOverride ?? READ_FLOOR_MS)[window] ?? 0;
|
|
442
|
+
// TANK READING SET (2026-10-13): partial cycles COUNT here. The strict
|
|
443
|
+
// `complete` gate was written for demand stats and quietly deleted every
|
|
444
|
+
// weekly observation — a 7d window outlives every maxpool restart/sleep, so
|
|
445
|
+
// restoreCapacityState's >60s downtime check marked nearly all of them
|
|
446
|
+
// partial and the Weekly (7d) page read "--" everywhere (owner report
|
|
447
|
+
// 2026-10-13). For TANK math a gap is already handled: tokens ÷ vendor % is
|
|
448
|
+
// a lower bound whenever spend was unseen, and exact/bounded/lowerBound
|
|
449
|
+
// exist to say so. Still excluded, hard: disabledDuring (operator benched
|
|
450
|
+
// the account — not a capacity observation) and `pre-v3-unverified` (the
|
|
451
|
+
// ONE reason that means "never trust this row").
|
|
412
452
|
const closed = (rec?.[window]?.closed || []).filter(c =>
|
|
413
|
-
|
|
453
|
+
!c.disabledDuring
|
|
454
|
+
&& c.partialReason !== 'pre-v3-unverified'
|
|
455
|
+
&& (c.endedAt - c.startedAt) >= floor - 1_000
|
|
456
|
+
// CEILING: a cycle far longer than its window swallowed a rollover it never
|
|
457
|
+
// saw — two tanks' tokens over one tank's percentage (see WINDOW_CEIL_FACTOR).
|
|
458
|
+
&& (this._readFloorOverride != null
|
|
459
|
+
|| (c.endedAt - c.startedAt) <= WINDOW_MS[window] * WINDOW_CEIL_FACTOR));
|
|
414
460
|
// PHYSICAL FLOOR: a tank can never be smaller than the most tokens ever
|
|
415
461
|
// delivered in one complete window — every token we count is a vendor token
|
|
416
462
|
// (C ≤ V ≤ tank), so maxDelivered ≤ tank is an invariant, not a heuristic.
|
|
@@ -425,23 +471,34 @@ export class CapacityLedger {
|
|
|
425
471
|
// floor rather than being thrown away — discarding left an account whose
|
|
426
472
|
// every reading was contaminated with NOTHING to show. After a plan
|
|
427
473
|
// downgrade the floor over-states until old cycles age out — conservative.
|
|
428
|
-
|
|
474
|
+
// The floor's own base uses the complete windows only — a partial cycle's
|
|
475
|
+
// tokens may end mid-window, understating a single delivery.
|
|
476
|
+
const maxDelivered = closed.filter(c => c.complete)
|
|
477
|
+
.reduce((m, c) => Math.max(m, c.tokens), 0);
|
|
429
478
|
const usable = closed.filter(c =>
|
|
430
479
|
Number.isFinite(c.finalUtilization) && c.finalUtilization >= TANK_MIN_UTIL);
|
|
431
480
|
if (!usable.length) return null;
|
|
432
481
|
let sum = 0, exact = 0, bounded = 0;
|
|
433
482
|
for (const c of usable) {
|
|
434
483
|
const observedFromStart = c.startedAt != null && c.windowStartedAt != null
|
|
435
|
-
&& c.startedAt <= c.windowStartedAt + 60_000;
|
|
484
|
+
&& c.startedAt <= c.windowStartedAt + 60_000 && c.complete;
|
|
436
485
|
sum += Math.max(c.tokens / c.finalUtilization, maxDelivered);
|
|
437
486
|
if (observedFromStart) exact++; else bounded++;
|
|
438
487
|
}
|
|
439
488
|
const last = usable[usable.length - 1];
|
|
489
|
+
const prevCycle = usable[usable.length - 2];
|
|
440
490
|
return {
|
|
441
491
|
avg: Math.round(sum / usable.length),
|
|
442
492
|
exact, bounded,
|
|
443
493
|
n: usable.length,
|
|
444
|
-
last: Math.round(last.tokens / last.finalUtilization),
|
|
494
|
+
last: Math.round(Math.max(last.tokens / last.finalUtilization, maxDelivered)),
|
|
495
|
+
// PREV in TANK units (tokens ÷ that cycle's own reading) — the page's
|
|
496
|
+
// Prev column used to come from windowStats (delivered tokens), mixing
|
|
497
|
+
// units across one row (2026-10-13 owner report). Same physical floor as
|
|
498
|
+
// the average: a reading implying less than a past delivery is impossible.
|
|
499
|
+
prev: prevCycle != null
|
|
500
|
+
? Math.round(Math.max(prevCycle.tokens / prevCycle.finalUtilization, maxDelivered))
|
|
501
|
+
: null,
|
|
445
502
|
lowerBound: bounded > 0 && exact === 0,
|
|
446
503
|
};
|
|
447
504
|
}
|
package/src/tui.js
CHANGED
|
@@ -271,23 +271,31 @@ function settingsTags(am, a) {
|
|
|
271
271
|
return tags;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
|
|
278
|
-
*
|
|
279
|
-
*
|
|
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
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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.',
|
|
@@ -1236,7 +1252,13 @@ export class TUI {
|
|
|
1236
1252
|
? `${Math.round(existing * 100)}%${existingMode === 'dynamic' ? ' dynamic' : ' fixed'}`
|
|
1237
1253
|
: 'off';
|
|
1238
1254
|
this.mode = 'input';
|
|
1239
|
-
|
|
1255
|
+
// Plain-English prompt (owner 2026-10-13: "I'm not sure how does this work.
|
|
1256
|
+
// I want to keep the dynamic floor, but I also want to raise the basic cap").
|
|
1257
|
+
// Type just a number = the dynamic cap's STARTING level (it holds the account
|
|
1258
|
+
// to NN% early in each window, then rises toward ~95% as the window nears
|
|
1259
|
+
// its reset, so unused capacity isn't stranded). fNN = fixed wall (no rise).
|
|
1260
|
+
// 0 = no cap.
|
|
1261
|
+
this.inputPrompt = `Usage cap for "${current}" — type a % like 75: the account stays free until it hits that, then unlocks gradually as the window refills (dynamic). f75 = never unlock early (fixed). 0 = remove. Now ${nowLabel}`;
|
|
1240
1262
|
this.inputBuf = '';
|
|
1241
1263
|
this.inputSensitive = false;
|
|
1242
1264
|
this.inputCb = value => this._doSetCap(targetIdx, String(value || '').trim());
|
|
@@ -2020,6 +2042,15 @@ export class TUI {
|
|
|
2020
2042
|
lines.push(` ${gray(this.log[i].t)} ${this.log[i].msg}`);
|
|
2021
2043
|
}
|
|
2022
2044
|
|
|
2045
|
+
// The footer is the screen's controls — it must never be pushed off. On a short
|
|
2046
|
+
// terminal cut the BODY from the bottom, and say so.
|
|
2047
|
+
if (lines.length > H - footerH) {
|
|
2048
|
+
const keep = Math.max(0, H - footerH - 1);
|
|
2049
|
+
const hidden = lines.length - keep;
|
|
2050
|
+
lines.length = keep;
|
|
2051
|
+
lines.push(` ${dim(`… ${hidden} more row${hidden === 1 ? '' : 's'} — make the window taller to see them`)}`);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2023
2054
|
// Pad to fill
|
|
2024
2055
|
while (lines.length < H - footerH) lines.push('');
|
|
2025
2056
|
|
|
@@ -2116,10 +2147,10 @@ export class TUI {
|
|
|
2116
2147
|
// relabels an otherwise-active row; exhausted / throttled / auth states keep their
|
|
2117
2148
|
// own (more specific) word. "capped" = the owner's usage cap is holding it;
|
|
2118
2149
|
// "5h full" = its session window is used up until the reset shown on the row.
|
|
2119
|
-
//
|
|
2120
|
-
// _clearExpiredQuotas, which MUTATES the quota (nulls an expired window)
|
|
2121
|
-
//
|
|
2122
|
-
//
|
|
2150
|
+
// Pure predicates on purpose: _weeklyRawState/_isSessionQuotaUnavailable call
|
|
2151
|
+
// _clearExpiredQuotas, which MUTATES the quota (nulls an expired window), so calling
|
|
2152
|
+
// them here blanked this row's own bars mid-render. (_render already runs the
|
|
2153
|
+
// manager's expiry refresh once per frame, before any row is drawn.)
|
|
2123
2154
|
if (a.enabled !== false && effectiveStatus === 'active') {
|
|
2124
2155
|
if (capBenched(this.am, a)) effectiveStatus = 'capped';
|
|
2125
2156
|
else if (sessionFull(this.am, a)) effectiveStatus = '5h full';
|
|
@@ -2483,7 +2514,7 @@ export class TUI {
|
|
|
2483
2514
|
if (win === 'wk' && a.type === 'provider' && a.quota?.weeklyAbsent) {
|
|
2484
2515
|
const sesTank = this.am.capacityTank?.(i, 'ses');
|
|
2485
2516
|
const approx = sesTank && sesTank.n > 0
|
|
2486
|
-
? formatTokens(Math.round(sesTank.avg * (7 * 24) / 5)) : '--';
|
|
2517
|
+
? `≈ ${formatTokens(Math.round(sesTank.avg * (7 * 24) / 5))}` : '--';
|
|
2487
2518
|
const cellsByName = { Current: cyan(approx), Prev: '--', Avg: '--', N: sesTank?.n ? String(sesTank.n) : '--' };
|
|
2488
2519
|
out.push(' ' + name + ' ' + prov + ' '
|
|
2489
2520
|
+ COLS.map(c => cellsByName[c].padStart(CW)).join('')
|
|
@@ -2507,12 +2538,14 @@ export class TUI {
|
|
|
2507
2538
|
cur = formatTokens(Math.round(nowOpen.tokensSoFar / util));
|
|
2508
2539
|
}
|
|
2509
2540
|
const curPct = util != null && cur !== '--' ? dim(`@${Math.round(util * 100)}%`) : '';
|
|
2510
|
-
|
|
2541
|
+
// Every history column is a TANK number (tokens ÷ closing %). Prev/N used to
|
|
2542
|
+
// come from windowStats — delivered tokens and a different cycle set — so one
|
|
2543
|
+
// row mixed units (2026-10-13 owner report; see tankStats' reading-set note).
|
|
2511
2544
|
const cellsByName = {
|
|
2512
2545
|
Current: cur.padStart(CW - (curPct ? curPct.length + 1 : 0)),
|
|
2513
|
-
Prev: (
|
|
2546
|
+
Prev: (tank?.source === 'cycles' && tank.prev != null ? formatTokens(tank.prev) : '--'),
|
|
2514
2547
|
Avg: (tank?.source === 'cycles' ? formatTokens(tank.avg) : '--'),
|
|
2515
|
-
N: (
|
|
2548
|
+
N: (tank?.source === 'cycles' ? String(tank.n) : '--'),
|
|
2516
2549
|
};
|
|
2517
2550
|
const row = ' ' + name + ' ' + prov + ' '
|
|
2518
2551
|
+ COLS.map(c => cellsByName[c].padStart(CW)).join('');
|
|
@@ -2551,7 +2584,7 @@ export class TUI {
|
|
|
2551
2584
|
out.push(` ${dim('Usage cap for ONE account:')} ${bold('Esc')} ${dim('then')} ${bold('a')} ${dim('→')} ${bold('u')} ${dim('(Claude or GLM)')}`);
|
|
2552
2585
|
const capped = this.am.accounts.filter(a => a.capUtilization != null && a.enabled !== false);
|
|
2553
2586
|
if (capped.length) {
|
|
2554
|
-
out.push(` ${dim('
|
|
2587
|
+
out.push(` ${dim('Caps set:')} ${capped.map(a => `${a.name} ${Math.round(a.capUtilization * 100)}%`).join(', ')}`);
|
|
2555
2588
|
}
|
|
2556
2589
|
return out;
|
|
2557
2590
|
}
|