maxpool 1.10.4 → 1.11.1
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 +165 -5
- package/src/tui.js +27 -6
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -111,6 +111,31 @@ const DEFAULT_SCHEDULER = {
|
|
|
111
111
|
// it's essentially full (a near-guaranteed-waste hard-429). A real 429 sets util≈1.0
|
|
112
112
|
// and still benches here. Critical (0.95-0.999) stays last-resort-only (pass 2).
|
|
113
113
|
weeklyExhaustedThreshold: 0.999,
|
|
114
|
+
// ── Situational CRITICAL unlock (2026-08-24) ────────────────────────────────
|
|
115
|
+
// An account at critical (≥0.95 weekly) used to be pass-2-only: spent ONLY during
|
|
116
|
+
// a total-fleet outage for the exact request, so its last ~5% usually died unused
|
|
117
|
+
// at reset. Three situational lifts make critical reachable without an outage.
|
|
118
|
+
// preReset: a critical account whose weekly reset is < this many hours away is
|
|
119
|
+
// unlocked, and its cost decays to NEGATIVE (below an idle healthy account — a
|
|
120
|
+
// decay to 0 only TIES, which rotation splits ~1/N; the drain must actually win).
|
|
121
|
+
// Stamp-freshness required (probe or header seen recently). 0 disables.
|
|
122
|
+
criticalPreResetHours: 2,
|
|
123
|
+
// pressure: unlock when at most this many healthy+reserve routes still have
|
|
124
|
+
// CONCURRENCY HEADROOM for the request (an at-cap route cannot serve without
|
|
125
|
+
// deepening the congestion). Congestion-based, not presence-based: an idle provider
|
|
126
|
+
// or reserve account keeps serving and no unlock fires — critical becomes relief
|
|
127
|
+
// exactly when the fleet is out of headroom, and never preempts an idle route
|
|
128
|
+
// (cost is also ABOVE reserve's attainable max ~19, pinning the ordering).
|
|
129
|
+
// -1 disables; 0 = unlock only when every route is at cap.
|
|
130
|
+
criticalPressureUnlockRoutes: 0,
|
|
131
|
+
criticalPressureCost: 21,
|
|
132
|
+
// Cost at the OPEN of the preReset window, decaying linearly to
|
|
133
|
+
// -(reserveFloorCost+1) at reset — below every healthy account, bounded only by
|
|
134
|
+
// the concurrency cap.
|
|
135
|
+
criticalDrainFloorCost: 8,
|
|
136
|
+
// peak: unlock critical Claude accounts while a provider peak de-preference is
|
|
137
|
+
// ACTIVE (_peakTier ≥ 1). Default OFF — mechanism shipped, immediate use declined.
|
|
138
|
+
criticalPeakUnlock: false,
|
|
114
139
|
weeklyBurnDebtWeight: 0.6,
|
|
115
140
|
// Routing-cost tuning (lower cost = preferred). The goal is to AVOID
|
|
116
141
|
// short-term (rate/concurrency) throttling by spreading load across healthy
|
|
@@ -212,6 +237,10 @@ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
212
237
|
// `priority < bestPriority` is false for Infinity vs Infinity and an all-peaked pool
|
|
213
238
|
// would select nothing.
|
|
214
239
|
const PEAK_TIER_STRIDE = 1_000_000;
|
|
240
|
+
|
|
241
|
+
// Memo for _pressureEligibleRoutes, keyed on the requestInfo object (one selection
|
|
242
|
+
// pass = one requestInfo instance).
|
|
243
|
+
const _pressureCache = new WeakMap();
|
|
215
244
|
const FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
|
|
216
245
|
|
|
217
246
|
// Quota fields that survive a restart: utilization levels and their reset
|
|
@@ -1734,6 +1763,7 @@ export class AccountManager {
|
|
|
1734
1763
|
{ allowWeeklyReserve: true, allowWeeklyCritical: true },
|
|
1735
1764
|
];
|
|
1736
1765
|
|
|
1766
|
+
const bestUnlockMap = new Map();
|
|
1737
1767
|
for (const weeklyOptions of weeklyPasses) {
|
|
1738
1768
|
best = null;
|
|
1739
1769
|
bestScore = Infinity;
|
|
@@ -1744,14 +1774,20 @@ export class AccountManager {
|
|
|
1744
1774
|
const account = this.accounts[idx];
|
|
1745
1775
|
if (excludedIndexes.has(account.index)) continue;
|
|
1746
1776
|
if (!this._matchesRequest(account, profile, requestInfo)) continue;
|
|
1747
|
-
|
|
1777
|
+
// Situational critical unlock: computed per-account per-request (pressure
|
|
1778
|
+
// memoized by requestInfo). pass 2 keeps its unconditional fallback.
|
|
1779
|
+
const critUnlock = weeklyOptions.allowWeeklyCritical
|
|
1780
|
+
? null
|
|
1781
|
+
: this._criticalUnlock(account, requestInfo, excludedIndexes, null, now);
|
|
1782
|
+
if (!this._isAvailable(account, { ...weeklyOptions, allowWeeklyCritical: weeklyOptions.allowWeeklyCritical || !!critUnlock, model: requestInfo.model, now })) continue;
|
|
1748
1783
|
|
|
1749
1784
|
const priority = this._effectivePriority(account, requestInfo, now);
|
|
1750
|
-
const score = this._scoreAccount(account, requestInfo, scoringCtx);
|
|
1785
|
+
const score = this._scoreAccount(account, requestInfo, scoringCtx, critUnlock);
|
|
1751
1786
|
if (priority < bestPriority || (priority === bestPriority && score < bestScore)) {
|
|
1752
1787
|
bestPriority = priority;
|
|
1753
1788
|
bestScore = score;
|
|
1754
1789
|
best = account;
|
|
1790
|
+
if (critUnlock) bestUnlockMap.set(account.name, critUnlock); else bestUnlockMap.delete(account.name);
|
|
1755
1791
|
}
|
|
1756
1792
|
}
|
|
1757
1793
|
|
|
@@ -1759,6 +1795,19 @@ export class AccountManager {
|
|
|
1759
1795
|
const switched = best.index !== this.currentIndex;
|
|
1760
1796
|
this.currentIndex = best.index;
|
|
1761
1797
|
this.nextIndex = (best.index + 1) % this.accounts.length;
|
|
1798
|
+
// Unlock visibility: ONE line per account+reason when a critical account
|
|
1799
|
+
// first serves via a situational unlock (per-request would spam the log).
|
|
1800
|
+
const usedUnlock = bestUnlockMap.get(best.name);
|
|
1801
|
+
if (usedUnlock) {
|
|
1802
|
+
const key = usedUnlock.reason;
|
|
1803
|
+
if (best._lastUnlockLogged !== key) {
|
|
1804
|
+
best._lastUnlockLogged = key;
|
|
1805
|
+
const eta = usedUnlock.etaMs != null ? ` (reset in ${(usedUnlock.etaMs / 60000).toFixed(0)}m)` : '';
|
|
1806
|
+
console.log(`[Maxpool] Critical unlock "${key}": routing to "${best.name}"${eta}`);
|
|
1807
|
+
}
|
|
1808
|
+
} else if (best._lastUnlockLogged) {
|
|
1809
|
+
best._lastUnlockLogged = null; // state left critical — re-log next unlock
|
|
1810
|
+
}
|
|
1762
1811
|
// If we switched to an account whose weekly quota is still unknown, flag
|
|
1763
1812
|
// it so we re-evaluate once that quota is learned (see updateQuota).
|
|
1764
1813
|
best.probing = best.quota.unified7dReset == null;
|
|
@@ -1976,6 +2025,11 @@ export class AccountManager {
|
|
|
1976
2025
|
// ranks strictly below every non-peak account in EVERY routing mode with no
|
|
1977
2026
|
// per-mode branch. Tier 0 returns base IDENTICALLY — off-peak behaviour is
|
|
1978
2027
|
// byte-identical to the pre-peak implementation (SC2 by construction).
|
|
2028
|
+
// The critical unlock deliberately does NOT touch this axis: priority dominates
|
|
2029
|
+
// score, so any unlock tier here would block same-family relief entirely. The
|
|
2030
|
+
// unlock's cross-class posture is enforced by the CONGESTION gate instead (an
|
|
2031
|
+
// idle provider has headroom → no pressure unlock → provider keeps serving), and
|
|
2032
|
+
// by the score cost (21 > reserve's max 19).
|
|
1979
2033
|
const base = this._basePriority(account, requestInfo);
|
|
1980
2034
|
const tier = this._peakTier(account, now);
|
|
1981
2035
|
return tier === 0 ? base : base + tier * PEAK_TIER_STRIDE;
|
|
@@ -2406,7 +2460,7 @@ export class AccountManager {
|
|
|
2406
2460
|
* - ramp: ease a just-recovered account back in instead of slamming it
|
|
2407
2461
|
* - failures: direct per-account backoff after errors
|
|
2408
2462
|
*/
|
|
2409
|
-
_scoreAccount(account, requestInfo = {}, ctx = null) {
|
|
2463
|
+
_scoreAccount(account, requestInfo = {}, ctx = null, criticalUnlock = null) {
|
|
2410
2464
|
const now = ctx?.now ?? Date.now();
|
|
2411
2465
|
const reqWeight = Math.max(1, requestInfo.weight || 1);
|
|
2412
2466
|
const inflight = account.activeWeight + reqWeight;
|
|
@@ -2422,7 +2476,10 @@ export class AccountManager {
|
|
|
2422
2476
|
// bites sooner — load fans out across the fleet before a low-quota account is
|
|
2423
2477
|
// dogpiled toward a 429.
|
|
2424
2478
|
const weeklyState = this._weeklyRawState(account);
|
|
2425
|
-
|
|
2479
|
+
// An UNLOCKED critical account is the lowest-headroom tier of all — it gets the
|
|
2480
|
+
// same tight target as reserve (red team finding A: inheriting the looser
|
|
2481
|
+
// per-account target removed the anti-dogpile cap exactly where quota is lowest).
|
|
2482
|
+
const concTarget = (weeklyState === 'reserve' || (weeklyState === 'critical' && criticalUnlock))
|
|
2426
2483
|
? this.scheduler.reserveConcurrencyTarget
|
|
2427
2484
|
: this.scheduler.perAccountConcurrencyTarget;
|
|
2428
2485
|
const capPenalty = this.scheduler.capPenaltyWeight
|
|
@@ -2457,6 +2514,7 @@ export class AccountManager {
|
|
|
2457
2514
|
|
|
2458
2515
|
const ramp = this._recoveryRamp(account, now);
|
|
2459
2516
|
const reserveCost = this._reserveCost(account, now, weeklyState);
|
|
2517
|
+
const criticalCost = this._criticalCost(account, now, criticalUnlock, weeklyState);
|
|
2460
2518
|
const failurePenalty = account.consecutiveFailures * 5;
|
|
2461
2519
|
// NO unknown-quota bonus. An account whose quota we cannot see must never be
|
|
2462
2520
|
// MORE attractive than a known-healthy one — the old -0.5 nudge (safe only
|
|
@@ -2466,7 +2524,7 @@ export class AccountManager {
|
|
|
2466
2524
|
// default) learns the real number within a cycle. `probing`/requalify still
|
|
2467
2525
|
// flags a never-seen account for learning — that path is unchanged.
|
|
2468
2526
|
|
|
2469
|
-
return concurrency + capPenalty + paceCost + utilizationCost + scopedPace + spread + ramp + reserveCost + failurePenalty;
|
|
2527
|
+
return concurrency + capPenalty + paceCost + utilizationCost + scopedPace + spread + ramp + reserveCost + criticalCost + failurePenalty;
|
|
2470
2528
|
}
|
|
2471
2529
|
|
|
2472
2530
|
/**
|
|
@@ -2562,6 +2620,108 @@ export class AccountManager {
|
|
|
2562
2620
|
* above a lightly-loaded reserve one (its capPenalty is unbounded) — that's intended
|
|
2563
2621
|
* load-spread, not a violation of "healthy first".
|
|
2564
2622
|
*/
|
|
2623
|
+
/**
|
|
2624
|
+
* Situational CRITICAL unlock (2026-08-24). Returns null or { reason, etaMs }:
|
|
2625
|
+
* - 'prereset' — the account's weekly reset lands inside criticalPreResetHours.
|
|
2626
|
+
* That capacity is FREE: it dies at reset regardless, so it is
|
|
2627
|
+
* drained FIRST (cost decays negative) while every other account's
|
|
2628
|
+
* weekly budget survives. Requires a FRESH reset stamp (a stale
|
|
2629
|
+
* future stamp with probing off must not fire the window early).
|
|
2630
|
+
* - 'pressure' — ≤ criticalPressureUnlockRoutes healthy+reserve routes remain
|
|
2631
|
+
* for THIS request (full pre-pass incl. _matchesRequest and
|
|
2632
|
+
* excludedIndexes — an inline count is rotation-order-dependent).
|
|
2633
|
+
* Relief one step BEFORE the stall, not only during it.
|
|
2634
|
+
* - 'peak' — a provider peak de-preference is ACTIVE (tier ≥ 1, not merely
|
|
2635
|
+
* in-window) and criticalPeakUnlock is enabled. Default off.
|
|
2636
|
+
* Precedence prereset > pressure > peak: the cheaper drain wins.
|
|
2637
|
+
*/
|
|
2638
|
+
_criticalUnlock(account, requestInfo = {}, excludedIndexes = new Set(), pressureCache = null, now = Date.now()) {
|
|
2639
|
+
const state = this._weeklyRawState(account);
|
|
2640
|
+
if (state !== 'critical') return null;
|
|
2641
|
+
|
|
2642
|
+
const hours = this.scheduler.criticalPreResetHours ?? 0;
|
|
2643
|
+
if (hours > 0) {
|
|
2644
|
+
const q = account.quota;
|
|
2645
|
+
const resetAt = account.type === 'provider' ? q.providerWkReset : q.unified7dReset;
|
|
2646
|
+
const etaMs = resetAt != null ? resetAt - now : null;
|
|
2647
|
+
// Stamp freshness: a header or probe reading within max(2 probe intervals,
|
|
2648
|
+
// the window itself). Without this, quotaProbeSeconds=0 + an idle account can
|
|
2649
|
+
// carry a days-old future stamp and fire the drain far too early.
|
|
2650
|
+
const probeInterval = this.quotaProbeIntervalMs || 60_000;
|
|
2651
|
+
const freshBy = q.lastProbeOkAt != null && (now - q.lastProbeOkAt) < Math.max(2 * probeInterval, hours * 3600_000);
|
|
2652
|
+
if (etaMs != null && etaMs > 0 && etaMs <= hours * 3600_000 && freshBy) {
|
|
2653
|
+
return { reason: 'prereset', etaMs };
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
const routes = this.scheduler.criticalPressureUnlockRoutes;
|
|
2658
|
+
if (routes != null && routes >= 0 && this._pressureEligibleRoutes(requestInfo, excludedIndexes, now) <= routes) {
|
|
2659
|
+
return { reason: 'pressure' };
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
if (this.scheduler.criticalPeakUnlock && this.accounts.some(a => a.type === 'provider' && this._peakTier(a, now) >= 1)) {
|
|
2663
|
+
return { reason: 'peak' };
|
|
2664
|
+
}
|
|
2665
|
+
return null;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
/** Full pre-pass: how many healthy+reserve routes can take THIS request NOW —
|
|
2669
|
+
* pass-1-eligible AND under their concurrency target (an at-cap route cannot serve
|
|
2670
|
+
* without joining the congestion). Congestion-based, deliberately: a presence-only
|
|
2671
|
+
* count forced the unlock onto the PRIORITY axis, which then blocked same-family
|
|
2672
|
+
* relief entirely (priority dominates score — a slammed healthy account at priority
|
|
2673
|
+
* 0 always beat the unlocked critical at 0+21). Counting only routes with headroom
|
|
2674
|
+
* keeps the unlock on the score axis where reserve/critical ordering lives.
|
|
2675
|
+
* Memoized per requestInfo via a module WeakMap. */
|
|
2676
|
+
_pressureEligibleRoutes(requestInfo = {}, excludedIndexes = new Set(), now = Date.now()) {
|
|
2677
|
+
let cached = _pressureCache.get(requestInfo);
|
|
2678
|
+
if (cached != null) return cached;
|
|
2679
|
+
const profile = requestInfo.profile || 'claude';
|
|
2680
|
+
let n = 0;
|
|
2681
|
+
for (const a of this.accounts) {
|
|
2682
|
+
if (excludedIndexes.has(a.index)) continue;
|
|
2683
|
+
if (!this._matchesRequest(a, profile, requestInfo)) continue;
|
|
2684
|
+
if (!this._isAvailable(a, { allowWeeklyReserve: true, allowWeeklyCritical: false, model: requestInfo.model, now })) continue;
|
|
2685
|
+
const target = this._weeklyRawState(a) === 'reserve'
|
|
2686
|
+
? this.scheduler.reserveConcurrencyTarget
|
|
2687
|
+
: this.scheduler.perAccountConcurrencyTarget;
|
|
2688
|
+
if (a.activeWeight + 1 > target) continue; // at/over cap — cannot take more
|
|
2689
|
+
n++;
|
|
2690
|
+
}
|
|
2691
|
+
_pressureCache.set(requestInfo, n);
|
|
2692
|
+
return n;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
/**
|
|
2696
|
+
* Score cost for a situationally-unlocked critical account. State-gated exactly like
|
|
2697
|
+
* _reserveCost (0 for any non-critical state) so the cost cannot linger past the
|
|
2698
|
+
* weekly reset into the fresh 'unknown' state.
|
|
2699
|
+
* - prereset: decays linearly from criticalDrainFloorCost (window open) to
|
|
2700
|
+
* -(reserveFloorCost+1) AT reset — BELOW an idle healthy account, so dying
|
|
2701
|
+
* capacity is drained first (a decay to 0 only ties; rotation then splits the
|
|
2702
|
+
* drain ~1/N and the point is lost). Bounded by the concurrency cap, which the
|
|
2703
|
+
* shared target-2 keeps tight.
|
|
2704
|
+
* - pressure/peak: a flat cost ABOVE reserve's attainable max, so critical is
|
|
2705
|
+
* relief for a LOADED last route and never preempts an idle reserve.
|
|
2706
|
+
*/
|
|
2707
|
+
_criticalCost(account, now = Date.now(), unlock = null, weeklyState = this._weeklyRawState(account)) {
|
|
2708
|
+
if (weeklyState !== 'critical') return 0;
|
|
2709
|
+
if (!unlock) return 0;
|
|
2710
|
+
if (unlock.reason === 'prereset') {
|
|
2711
|
+
const hours = this.scheduler.criticalPreResetHours || 1;
|
|
2712
|
+
const floor = this.scheduler.criticalDrainFloorCost;
|
|
2713
|
+
const bottom = -(this.scheduler.reserveFloorCost + 1);
|
|
2714
|
+
const frac = unlock.etaMs != null ? Math.max(0, Math.min(1, 1 - unlock.etaMs / (hours * 3600_000))) : 1;
|
|
2715
|
+
return floor + (bottom - floor) * frac;
|
|
2716
|
+
}
|
|
2717
|
+
return this.scheduler.criticalPressureCost;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
/** TUI/status summary: { reason, etaMs } or null, without request context. */
|
|
2721
|
+
criticalUnlockSummary(account, now = Date.now()) {
|
|
2722
|
+
return this._criticalUnlock(account, { profile: 'claude' }, new Set(), null, now);
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2565
2725
|
_reserveCost(account, now = Date.now(), weeklyState = this._weeklyRawState(account)) {
|
|
2566
2726
|
if (weeklyState !== 'reserve') return 0;
|
|
2567
2727
|
const q = account.quota;
|
package/src/tui.js
CHANGED
|
@@ -180,7 +180,7 @@ function loadText(load) {
|
|
|
180
180
|
return `${now} 15m ${recent}${recentAvg}${fails} 1h ${hour}`;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
function weeklyPolicyText(am, account) {
|
|
183
|
+
export function weeklyPolicyText(am, account) {
|
|
184
184
|
if (!am?._weeklyState || !account || account.type === 'provider') return '';
|
|
185
185
|
const state = am._weeklyState(account);
|
|
186
186
|
if (!state || state === 'unknown' || state === 'normal') return '';
|
|
@@ -191,7 +191,19 @@ function weeklyPolicyText(am, account) {
|
|
|
191
191
|
: '';
|
|
192
192
|
const paceOnly = state !== rawState && rawState !== 'exhausted';
|
|
193
193
|
const label = paceOnly ? `Pace ${state}` : `Wk ${state}`;
|
|
194
|
-
|
|
194
|
+
let text = paceOnly ? label : `${label}${pct}`;
|
|
195
|
+
// Critical-unlock visibility (2026-08-24): a red "Wk critical 96%" row that is
|
|
196
|
+
// ACTIVELY ROUTING must say why, or the user watches a benched-looking account
|
|
197
|
+
// hoover traffic with no explanation (the peak-shipped-invisible lesson).
|
|
198
|
+
if (state === 'critical') {
|
|
199
|
+
const u = am.criticalUnlockSummary?.(account);
|
|
200
|
+
if (u) {
|
|
201
|
+
const eta = u.etaMs != null ? ` ${(u.etaMs / 60000).toFixed(0)}m` : '';
|
|
202
|
+
text += u.reason === 'prereset'
|
|
203
|
+
? ` ·drain${eta}`
|
|
204
|
+
: u.reason === 'pressure' ? ' ·relief' : ' ·peak-relief';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
195
207
|
if (state === 'critical' || state === 'exhausted') return state !== rawState ? yellow(text) : red(text);
|
|
196
208
|
if (state === 'reserve') return yellow(text);
|
|
197
209
|
return cyan(text);
|
|
@@ -2070,6 +2082,12 @@ export class TUI {
|
|
|
2070
2082
|
// being an estimate. `~` marks it; a measured column replaces it after the first
|
|
2071
2083
|
// full window. A stale-util caveat only when we cannot prove same-window.
|
|
2072
2084
|
const est = this.am.capacityEstimate?.(i, win);
|
|
2085
|
+
// LIVE now-column: the open cycle is the only number that moves between window
|
|
2086
|
+
// closes, and without it the page read as frozen (reported 2026-08-25: "they
|
|
2087
|
+
// don't seem to be updating at all"). Rendered on every row that has one.
|
|
2088
|
+
const nowOpen = ledger.openCycle(a.name, win);
|
|
2089
|
+
const nowTag = nowOpen && nowOpen.tokensSoFar > 0
|
|
2090
|
+
? ' ' + yellow(`▸ ${formatTokens(nowOpen.tokensSoFar)} this window`) : '';
|
|
2073
2091
|
if (est) {
|
|
2074
2092
|
anyData = true;
|
|
2075
2093
|
const caveat = est.fresh ? '' : ' (utilization reading may be from the previous window)';
|
|
@@ -2080,16 +2098,19 @@ export class TUI {
|
|
|
2080
2098
|
const via = est.method === 'delta'
|
|
2081
2099
|
? `Δ ${(est.utilization * 100).toFixed(0)}% full` : `${(est.utilization * 100).toFixed(0)}% full`;
|
|
2082
2100
|
out.push(' ' + name + ' ' + prov + ' ' + cyan(op + formatTokens(est.tokens).padStart(CW - 1))
|
|
2083
|
-
+ dim(` est from ${via}${caveat} — measured after this window completes`));
|
|
2101
|
+
+ dim(` est from ${via}${caveat} — measured after this window completes`) + nowTag);
|
|
2084
2102
|
} else {
|
|
2085
|
-
out.push(' ' + name + ' ' + prov + ' ' + dim('no completed cycle yet'));
|
|
2103
|
+
out.push(' ' + name + ' ' + prov + ' ' + dim('no completed cycle yet') + nowTag);
|
|
2086
2104
|
}
|
|
2087
2105
|
continue;
|
|
2088
2106
|
}
|
|
2089
2107
|
anyData = true;
|
|
2090
2108
|
const all = { Last: st.last, Prev: st.prev, 'Prev-1': st.prev1, 'Avg 3': st.avg3, 'Avg 10': st.avg10, 'All time': st.allTime };
|
|
2091
2109
|
const cells = COLS.map(c => formatTokens(all[c]).padStart(CW)).join('');
|
|
2092
|
-
|
|
2110
|
+
const nowOpen = ledger.openCycle(a.name, win);
|
|
2111
|
+
const nowTag = nowOpen && nowOpen.tokensSoFar > 0
|
|
2112
|
+
? ' ' + yellow(`▸ ${formatTokens(nowOpen.tokensSoFar)}`) : '';
|
|
2113
|
+
out.push(' ' + name + ' ' + prov + ' ' + cells + ' ' + dim(String(st.cycles)) + nowTag);
|
|
2093
2114
|
}
|
|
2094
2115
|
|
|
2095
2116
|
out.push('');
|
|
@@ -2102,7 +2123,7 @@ export class TUI {
|
|
|
2102
2123
|
: ' A session figure appears after an account\'s 5h window resets once.'));
|
|
2103
2124
|
}
|
|
2104
2125
|
out.push(' ' + dim('A cycle counts only if maxpool ran for all of it and the account stayed enabled.'));
|
|
2105
|
-
out.push(' ' + dim('~ = estimated
|
|
2126
|
+
out.push(' ' + dim('~ = estimated; ≥ = at least; Δ = exact-by-difference; ≈/wk = session-rate ceiling; ▸ = live this window.'));
|
|
2106
2127
|
return out;
|
|
2107
2128
|
}
|
|
2108
2129
|
|