maxpool 1.24.24 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.24.24",
3
+ "version": "1.24.25",
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",
@@ -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
  }
@@ -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 rec[w].open.complete = false;
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 >= 0) || !(m.utilization < 1)) continue;
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
- c.complete && !c.disabledDuring && (c.endedAt - c.startedAt) >= floor - 1_000);
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
- const maxDelivered = closed.reduce((m, c) => Math.max(m, c.tokens), 0);
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
@@ -1252,7 +1252,13 @@ export class TUI {
1252
1252
  ? `${Math.round(existing * 100)}%${existingMode === 'dynamic' ? ' dynamic' : ' fixed'}`
1253
1253
  : 'off';
1254
1254
  this.mode = 'input';
1255
- this.inputPrompt = `Usage cap for "${current}" — NN = dynamic floor (rises near reset), fNN = fixed, 0 = off, now ${nowLabel}`;
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}`;
1256
1262
  this.inputBuf = '';
1257
1263
  this.inputSensitive = false;
1258
1264
  this.inputCb = value => this._doSetCap(targetIdx, String(value || '').trim());
@@ -2508,7 +2514,7 @@ export class TUI {
2508
2514
  if (win === 'wk' && a.type === 'provider' && a.quota?.weeklyAbsent) {
2509
2515
  const sesTank = this.am.capacityTank?.(i, 'ses');
2510
2516
  const approx = sesTank && sesTank.n > 0
2511
- ? formatTokens(Math.round(sesTank.avg * (7 * 24) / 5)) : '--';
2517
+ ? `≈ ${formatTokens(Math.round(sesTank.avg * (7 * 24) / 5))}` : '--';
2512
2518
  const cellsByName = { Current: cyan(approx), Prev: '--', Avg: '--', N: sesTank?.n ? String(sesTank.n) : '--' };
2513
2519
  out.push(' ' + name + ' ' + prov + ' '
2514
2520
  + COLS.map(c => cellsByName[c].padStart(CW)).join('')
@@ -2532,12 +2538,14 @@ export class TUI {
2532
2538
  cur = formatTokens(Math.round(nowOpen.tokensSoFar / util));
2533
2539
  }
2534
2540
  const curPct = util != null && cur !== '--' ? dim(`@${Math.round(util * 100)}%`) : '';
2535
- const st = ledger.windowStats(a.name, win);
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).
2536
2544
  const cellsByName = {
2537
2545
  Current: cur.padStart(CW - (curPct ? curPct.length + 1 : 0)),
2538
- Prev: (st?.prev != null ? formatTokens(st.prev) : '--'),
2546
+ Prev: (tank?.source === 'cycles' && tank.prev != null ? formatTokens(tank.prev) : '--'),
2539
2547
  Avg: (tank?.source === 'cycles' ? formatTokens(tank.avg) : '--'),
2540
- N: (st ? String(st.cycles) : '--'),
2548
+ N: (tank?.source === 'cycles' ? String(tank.n) : '--'),
2541
2549
  };
2542
2550
  const row = ' ' + name + ' ' + prov + ' '
2543
2551
  + COLS.map(c => cellsByName[c].padStart(CW)).join('');