maxpool 1.16.0 → 1.17.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/prober.js +9 -0
- package/src/tui.js +88 -36
package/package.json
CHANGED
package/src/prober.js
CHANGED
|
@@ -68,6 +68,11 @@ export class Prober {
|
|
|
68
68
|
if (this._running) return this._inflight || Promise.resolve();
|
|
69
69
|
this._running = true;
|
|
70
70
|
this._stopping = false;
|
|
71
|
+
// Publish the sweep's liveness so the UI can answer "what happens next?"
|
|
72
|
+
// rather than printing a bare "stale". Reported 2026-08-27: a stale marker with
|
|
73
|
+
// no next step reads as a problem the user must fix, when the prober is already
|
|
74
|
+
// retrying on its own.
|
|
75
|
+
this.am.quotaProbeSweeping = true;
|
|
71
76
|
this._inflight = (async () => {
|
|
72
77
|
try {
|
|
73
78
|
// CAPACITY LEDGER: close any open cycle whose window's reset stamp has
|
|
@@ -110,6 +115,10 @@ export class Prober {
|
|
|
110
115
|
} finally {
|
|
111
116
|
this._running = false;
|
|
112
117
|
this._inflight = null;
|
|
118
|
+
this.am.quotaProbeSweeping = false;
|
|
119
|
+
// When the NEXT sweep starts. setInterval fires every intervalMs from the
|
|
120
|
+
// last tick, so "now + interval" is the honest estimate for the UI.
|
|
121
|
+
this.am.quotaProbeNextSweepAt = this.intervalMs > 0 ? Date.now() + this.intervalMs : null;
|
|
113
122
|
}
|
|
114
123
|
})();
|
|
115
124
|
return this._inflight;
|
package/src/tui.js
CHANGED
|
@@ -139,6 +139,18 @@ function formatMs(ms) {
|
|
|
139
139
|
return `${min}m${String(rem).padStart(2, '0')}s`;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/** Coarse human duration for AGE and NEXT-REFRESH text: "45s", "12m", "2h".
|
|
143
|
+
* Deliberately not formatMs — "quota 12m03s old" spends precision on a number
|
|
144
|
+
* nobody reads to the second, and the extra digits are what made the old cell
|
|
145
|
+
* look like machine output. */
|
|
146
|
+
function formatAge(ms) {
|
|
147
|
+
if (ms == null || isNaN(ms) || ms < 0) return '';
|
|
148
|
+
if (ms < 60_000) return `${Math.max(1, Math.round(ms / 1000))}s`;
|
|
149
|
+
if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`;
|
|
150
|
+
if (ms < 86_400_000) return `${Math.round(ms / 3_600_000)}h`;
|
|
151
|
+
return `${Math.round(ms / 86_400_000)}d`;
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
function statusColor(status) {
|
|
143
155
|
if (status == null) return '-';
|
|
144
156
|
if (status >= 200 && status < 300) return green(String(status));
|
|
@@ -162,17 +174,15 @@ function countdown(ts) {
|
|
|
162
174
|
return `${Math.ceil(ms / 86_400_000)}d`;
|
|
163
175
|
}
|
|
164
176
|
|
|
165
|
-
/** ACTIVITY cell — "is this account working, and is it healthy?" in
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* NOTHING, and a working one renders live · rate · latency.
|
|
177
|
+
/** ACTIVITY cell — "is this account working, and is it healthy?" in plain words.
|
|
178
|
+
* Rewritten twice on owner feedback (2026-08-27): first to symbols ("▶2 · 17/h ·
|
|
179
|
+
* 8.5s"), which read as secret code; now to words. Average latency is GONE —
|
|
180
|
+
* "how long each request takes" is not something the owner acts on. An idle
|
|
181
|
+
* account renders NOTHING.
|
|
171
182
|
*
|
|
172
|
-
*
|
|
173
|
-
* 17/
|
|
174
|
-
*
|
|
175
|
-
* (blank) resting — nothing to say
|
|
183
|
+
* 2 live · 17 req/hr · 1 failed
|
|
184
|
+
* 17 req/hr working, idle this second
|
|
185
|
+
* (blank) resting — nothing to say
|
|
176
186
|
*/
|
|
177
187
|
function loadText(load) {
|
|
178
188
|
const inflight = load?.current?.inFlight || 0;
|
|
@@ -180,13 +190,10 @@ function loadText(load) {
|
|
|
180
190
|
const failed = load?.last15m?.failed || 0;
|
|
181
191
|
if (!inflight && !hourReq && !failed) return '';
|
|
182
192
|
const parts = [];
|
|
183
|
-
if (inflight) parts.push(
|
|
184
|
-
if (hourReq) parts.push(`${hourReq}/
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
let s = parts.join(' · ');
|
|
188
|
-
if (failed) s += ` ${red(`${failed}f`)}`;
|
|
189
|
-
return s;
|
|
193
|
+
if (inflight) parts.push(`${inflight} live`);
|
|
194
|
+
if (hourReq) parts.push(`${hourReq} req/hr`);
|
|
195
|
+
if (failed) parts.push(red(`${failed} failed`));
|
|
196
|
+
return parts.join(' · ');
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
/** The usage CAP is an account PROPERTY, not a state — so it renders whenever one
|
|
@@ -201,6 +208,26 @@ function capText(a, benched) {
|
|
|
201
208
|
return benched ? yellow(t) : dim(t);
|
|
202
209
|
}
|
|
203
210
|
|
|
211
|
+
/** PER-ACCOUNT SETTINGS the user set by hand — the last column's whole job
|
|
212
|
+
* (owner, 2026-08-27: "the last column should contain any and all settings that
|
|
213
|
+
* are custom per account"). Fleet-wide settings (routing mode, peak policy) stay
|
|
214
|
+
* in the top header where they already live; only what is scoped to THIS account
|
|
215
|
+
* belongs on THIS row.
|
|
216
|
+
* preferred this account is the manual routing pin (the 'p' key)
|
|
217
|
+
* cap NN% its reserved-capacity ceiling (the 'c' key)
|
|
218
|
+
* Deliberately NOT here: automatic routing policies (peak, fast-refill) — those
|
|
219
|
+
* are the system's behaviour, not the user's settings; they keep their own tags.
|
|
220
|
+
*/
|
|
221
|
+
function settingsTags(am, a) {
|
|
222
|
+
const tags = [];
|
|
223
|
+
if (am?.routingMode === 'preferred' && a?.name === am.preferredAccountName) {
|
|
224
|
+
tags.push(cyan('preferred'));
|
|
225
|
+
}
|
|
226
|
+
const capTag = capText(a, capBenched(am, a));
|
|
227
|
+
if (capTag) tags.push(capTag);
|
|
228
|
+
return tags;
|
|
229
|
+
}
|
|
230
|
+
|
|
204
231
|
/** True when the reservation is what is currently keeping traffic off this account
|
|
205
232
|
* — either window at or past the cap. Reads the manager's own predicate so the
|
|
206
233
|
* label can never disagree with routing. */
|
|
@@ -1775,11 +1802,11 @@ export class TUI {
|
|
|
1775
1802
|
if (hidden > 0) {
|
|
1776
1803
|
lines.push(` ${dim(`… ${hidden} disabled account${hidden === 1 ? '' : 's'} hidden — press h to show`)}`);
|
|
1777
1804
|
}
|
|
1778
|
-
//
|
|
1779
|
-
//
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1805
|
+
// The glossary FOOTER is gone (owner, 2026-08-27). A legend is a symptom: it
|
|
1806
|
+
// exists to decode a row that could not be read on its own. Every cell now
|
|
1807
|
+
// says what it means in words ("2 live · 17 req/hr", "quota 12m old ·
|
|
1808
|
+
// refreshing in 45s"), so there is nothing left to decode — and one fewer
|
|
1809
|
+
// line of chrome between the operator and the data.
|
|
1783
1810
|
}
|
|
1784
1811
|
|
|
1785
1812
|
// ── Activity header
|
|
@@ -1979,13 +2006,14 @@ export class TUI {
|
|
|
1979
2006
|
}
|
|
1980
2007
|
const weekly = weeklyPolicyText(this.am, a);
|
|
1981
2008
|
if (weekly) line += ` ${weekly}`;
|
|
1982
|
-
//
|
|
1983
|
-
// "Cap 50%" only while the cap is the ACTIVE
|
|
1984
|
-
//
|
|
1985
|
-
//
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
if (
|
|
2009
|
+
// Per-account SETTINGS (preferred pin, usage cap), whatever the account is
|
|
2010
|
+
// doing. weeklyPolicyText renders "Cap 50%" only while the cap is the ACTIVE
|
|
2011
|
+
// weekly state; these are the standing properties, so a capped account that is
|
|
2012
|
+
// exhausted, throttled or idle still says so. The cap is suppressed when the
|
|
2013
|
+
// weekly tag already IS the Cap label (no double tag).
|
|
2014
|
+
for (const tag of settingsTags(this.am, a)) {
|
|
2015
|
+
if (weekly.includes('Cap ') && tag.includes('cap ')) continue;
|
|
2016
|
+
line += ` ${tag}`;
|
|
1989
2017
|
}
|
|
1990
2018
|
// Per-model weekly caps (e.g. Fable, while the unified weekly still has
|
|
1991
2019
|
// headroom). Show the ACTUAL utilization — "Fable 90%" (yellow) while high but
|
|
@@ -2042,10 +2070,35 @@ export class TUI {
|
|
|
2042
2070
|
const headerFresh = q.lastHeaderQuotaAt && (Date.now() - q.lastHeaderQuotaAt) <= Math.max(3 * interval, 180_000);
|
|
2043
2071
|
if (headerFresh) return '';
|
|
2044
2072
|
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2073
|
+
// SAY WHAT HAPPENS NEXT (owner, 2026-08-27): "stale" / "stale·probe throttled"
|
|
2074
|
+
// named an internal mechanism and left the user with nothing to do. Nothing IS
|
|
2075
|
+
// the correct action — the prober retries on its own schedule and backs off
|
|
2076
|
+
// automatically when Anthropic rate-limits it — so the cell now states how old
|
|
2077
|
+
// the numbers are AND when they refresh, in that order.
|
|
2078
|
+
const age = q.lastProbeOkAt ? formatAge(Date.now() - q.lastProbeOkAt) : null;
|
|
2079
|
+
const ageText = age ? `quota ${age} old` : 'quota not read yet';
|
|
2080
|
+
const next = this._probeNextText();
|
|
2081
|
+
const throttled = q.lastProbeErrorStatus === 429;
|
|
2082
|
+
// Throttled is the ONE case worth colouring: it is why the refresh is late, and
|
|
2083
|
+
// it self-clears. Everything else is a plain dim statement of fact.
|
|
2084
|
+
const body = throttled
|
|
2085
|
+
? `${ageText} · rate-limited, retrying ${next}`
|
|
2086
|
+
: `${ageText} · refreshing ${next}`;
|
|
2087
|
+
return ` ${throttled ? yellow(body) : dim(body)}`;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
/** "now" while a sweep is in flight, else "in 45s" from the prober's next-tick
|
|
2091
|
+
* stamp. Falls back to the configured interval when no sweep has completed yet
|
|
2092
|
+
* (fresh boot), and to a bare "shortly" when the probe is manual/off. */
|
|
2093
|
+
_probeNextText() {
|
|
2094
|
+
if (this.am.quotaProbeSweeping) return 'now';
|
|
2095
|
+
const at = this.am.quotaProbeNextSweepAt;
|
|
2096
|
+
if (at) {
|
|
2097
|
+
const ms = at - Date.now();
|
|
2098
|
+
return ms > 0 ? `in ${formatAge(ms)}` : 'now';
|
|
2099
|
+
}
|
|
2100
|
+
const interval = this.am.quotaProbeIntervalMs;
|
|
2101
|
+
return interval > 0 ? `every ${formatAge(interval)}` : 'shortly';
|
|
2049
2102
|
}
|
|
2050
2103
|
|
|
2051
2104
|
_renderProviderAcct(sel, cur, name, type, status, a, bw = 11, showBoth = true) {
|
|
@@ -2091,11 +2144,10 @@ export class TUI {
|
|
|
2091
2144
|
sesCell = emptyBar('probing', bw);
|
|
2092
2145
|
wkCell = emptyBar('probing', bw);
|
|
2093
2146
|
}
|
|
2094
|
-
//
|
|
2147
|
+
// Settings ride OUTSIDE the quota-readable branch: an account with an
|
|
2095
2148
|
// unreadable or not-yet-probed quota still HAS its reservation, and hiding the
|
|
2096
2149
|
// setting whenever the probe is quiet is how a shipped feature reads as absent.
|
|
2097
|
-
const
|
|
2098
|
-
if (capTag) note += ` ${capTag}`;
|
|
2150
|
+
for (const tag of settingsTags(this.am, a)) note += ` ${tag}`;
|
|
2099
2151
|
|
|
2100
2152
|
let line = ` ${sel}${cur} ${name} ${type} ${status} Ses ${sesCell}`;
|
|
2101
2153
|
if (showBoth) line += ` Wk ${wkCell}`;
|