maxpool 1.24.25 → 1.24.27

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.25",
3
+ "version": "1.24.27",
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",
@@ -280,6 +280,7 @@ export class CapacityLedger {
280
280
  // (we only counted the tokens that flowed through maxpool, while the vendor's
281
281
  // percentage counts everything). Dropping it here made every tank read "bounded".
282
282
  ...(open.windowStartedAt != null ? { windowStartedAt: open.windowStartedAt } : {}),
283
+ ...(open.peakReset ? { spansRefill: true } : {}),
283
284
  resetAt,
284
285
  });
285
286
  if (rec[window].closed.length > MAX_CYCLES_PER_WINDOW) rec[window].closed.shift();
@@ -361,10 +362,19 @@ export class CapacityLedger {
361
362
  if (!open) continue;
362
363
  // PEAK: fullness only rises inside one window, so the highest reading a cycle
363
364
  // 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
365
  if (!(m.utilization < 1)) continue;
366
+ // A reading of 1.0 is the wall (throttled), not a fullness — the same reason
367
+ // estimateFromUtilization refuses u>=1 — so it never becomes the peak.
368
+ // A reading BELOW the peak by >=10pp means the tank was refilled under this
369
+ // open cycle (a redeemed reset, or a rollover whose stamp arrived late): the
370
+ // old peak no longer describes this window, so it is dropped rather than
371
+ // left to divide the new window's tokens (red-team 2026-10-13, finding 2).
372
+ if (Number.isFinite(open.peakUtilization) && open.peakUtilization - m.utilization >= 0.10) {
373
+ open.peakUtilization = m.utilization;
374
+ open.peakReset = true;
375
+ } else if (!Number.isFinite(open.peakUtilization) || m.utilization > open.peakUtilization) {
376
+ open.peakUtilization = m.utilization;
377
+ }
368
378
  const key = `${m.name}:${m.window}`;
369
379
  const prev = this._utilMarks.get(key);
370
380
  // Keep the OLDEST usable mark within this cycle: a wider span means a larger
@@ -452,6 +462,8 @@ export class CapacityLedger {
452
462
  const closed = (rec?.[window]?.closed || []).filter(c =>
453
463
  !c.disabledDuring
454
464
  && c.partialReason !== 'pre-v3-unverified'
465
+ && c.partialReason !== 'no-weekly-plan'
466
+ && !c.spansRefill
455
467
  && (c.endedAt - c.startedAt) >= floor - 1_000
456
468
  // CEILING: a cycle far longer than its window swallowed a rollover it never
457
469
  // saw — two tanks' tokens over one tank's percentage (see WINDOW_CEIL_FACTOR).
@@ -471,10 +483,12 @@ export class CapacityLedger {
471
483
  // floor rather than being thrown away — discarding left an account whose
472
484
  // every reading was contaminated with NOTHING to show. After a plan
473
485
  // downgrade the floor over-states until old cycles age out — conservative.
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);
486
+ // The floor is taken over EVERY admitted cycle, partial ones included: a
487
+ // partial cycle's token count is still tokens the vendor really delivered,
488
+ // so it is still a hard lower bound on the tank. Restricting it to complete
489
+ // cycles left it inert on weekly history, where nearly every cycle is partial
490
+ // (red-team 2026-10-13, finding 4).
491
+ const maxDelivered = closed.reduce((m, c) => Math.max(m, c.tokens), 0);
478
492
  const usable = closed.filter(c =>
479
493
  Number.isFinite(c.finalUtilization) && c.finalUtilization >= TANK_MIN_UTIL);
480
494
  if (!usable.length) return null;
package/src/tui.js CHANGED
@@ -1254,11 +1254,12 @@ export class TUI {
1254
1254
  this.mode = 'input';
1255
1255
  // Plain-English prompt (owner 2026-10-13: "I'm not sure how does this work.
1256
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}`;
1257
+ // Type just a number = the dynamic cap's STARTING level: routing USES the
1258
+ // account up to NN% of each window, and the allowance rises toward the
1259
+ // switch threshold as the window nears its reset (by elapsed time —
1260
+ // _effectiveCap), so unused capacity isn't stranded. fNN = fixed stop at NN%.
1261
+ // 0 / off = no cap. Kept short: the typed value must stay on an 80-col line.
1262
+ this.inputPrompt = `Cap ${current}, now ${nowLabel}. 75=use to 75%+rises near reset f75=hard 0=off`;
1262
1263
  this.inputBuf = '';
1263
1264
  this.inputSensitive = false;
1264
1265
  this.inputCb = value => this._doSetCap(targetIdx, String(value || '').trim());
@@ -2509,12 +2510,13 @@ export class TUI {
2509
2510
  const prov = gray(providerLabel(a).padEnd(PROVIDER_W));
2510
2511
 
2511
2512
  // The no-weekly plan has NO weekly window to complete — its weekly "capacity" is
2512
- // derived: avg session tank × 33.6 (the number of 5h windows in 7 days). Shown as
2513
- // ≈ to mark the derivation; N is the count of session cycles it rests on.
2513
+ // derived: avg session tank × 33.6 (the number of 5h windows in 7 days). Shown
2514
+ // without a marker (owner 2026-08-26: the row's text states the derivation);
2515
+ // N is the count of session cycles it rests on.
2514
2516
  if (win === 'wk' && a.type === 'provider' && a.quota?.weeklyAbsent) {
2515
2517
  const sesTank = this.am.capacityTank?.(i, 'ses');
2516
2518
  const approx = sesTank && sesTank.n > 0
2517
- ? `≈ ${formatTokens(Math.round(sesTank.avg * (7 * 24) / 5))}` : '--';
2519
+ ? formatTokens(Math.round(sesTank.avg * (7 * 24) / 5)) : '--';
2518
2520
  const cellsByName = { Current: cyan(approx), Prev: '--', Avg: '--', N: sesTank?.n ? String(sesTank.n) : '--' };
2519
2521
  out.push(' ' + name + ' ' + prov + ' '
2520
2522
  + COLS.map(c => cellsByName[c].padStart(CW)).join('')
@@ -2645,8 +2647,23 @@ export class TUI {
2645
2647
  : 'delete';
2646
2648
  return ` ${dim('↑↓')} select ${bold('Enter')} ${act} ${bold('Esc')} cancel`;
2647
2649
  }
2648
- case 'input':
2649
- return ` ${this.inputPrompt}: ${this.inputSensitive ? '•'.repeat(this.inputBuf.length) : this.inputBuf}█`;
2650
+ case 'input': {
2651
+ // The typed value and the cursor are the whole point of this line — they are
2652
+ // NEVER truncated. On a narrow terminal the PROMPT gives way instead, keeping
2653
+ // its last words (nearest the value). Red-team 2026-10-13: the long cap
2654
+ // prompt pushed the input off a 200-column terminal entirely.
2655
+ const shown = this.inputSensitive ? '•'.repeat(this.inputBuf.length) : this.inputBuf;
2656
+ const full = ` ${this.inputPrompt}: ${shown}█`;
2657
+ const visible = full.replace(ANSI_RE, '');
2658
+ const budget = (process.stdout.columns || 80) - 2;
2659
+ if (visible.length <= budget) return full;
2660
+ const keep = Math.max(0, budget - (shown.length + 2)); // ' …' + value + cursor
2661
+ const cut = visible.length - budget;
2662
+ const trimmed = this.inputPrompt.length > cut + 1
2663
+ ? this.inputPrompt.slice(0, Math.max(1, keep - 1)) + '…'
2664
+ : this.inputPrompt;
2665
+ return ` ${trimmed}: ${shown}█`;
2666
+ }
2650
2667
  case 'confirm':
2651
2668
  return ` ${bold(this.confirmTitle)} ${bold('y')} Yes ${bold('n')} No`;
2652
2669
  default: