maxpool 1.5.74 → 1.5.76
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 +12 -0
- package/src/server.js +25 -15
- package/src/tui.js +9 -1
package/package.json
CHANGED
package/src/account-manager.js
CHANGED
|
@@ -2370,6 +2370,18 @@ export class AccountManager {
|
|
|
2370
2370
|
// worth having; the diagnosis that motivated it was wrong.)
|
|
2371
2371
|
q.consecutiveProbeFailures = (q.consecutiveProbeFailures || 0) + 1;
|
|
2372
2372
|
const n = q.consecutiveProbeFailures;
|
|
2373
|
+
// A SUSTAINED 401 is dead credentials, not a blip. Latch refreshDead so (a) the
|
|
2374
|
+
// prober stops re-POSTing a rejected token every 60s forever — measured 2026-08-10:
|
|
2375
|
+
// 8 disabled accounts each past 20 consecutive 401s, hammering Anthropic's OAuth
|
|
2376
|
+
// endpoint for nothing — and (b) the TUI can SHOW that the account needs re-login
|
|
2377
|
+
// instead of leaving the user to guess. Cleared on a successful re-auth
|
|
2378
|
+
// (updateAccountTokens) exactly like a refresh-path invalid_grant.
|
|
2379
|
+
// Three strikes, not one: a single 401 can be a transient edge/token-rotation race.
|
|
2380
|
+
if (status === 401 && n >= 3 && !account.refreshDead) {
|
|
2381
|
+
account.refreshDead = true;
|
|
2382
|
+
console.error(`[Maxpool] "${account.name}" credentials rejected ${n}x (HTTP 401) — marking it needs re-login. `
|
|
2383
|
+
+ 'Re-authenticate via the TUI (a → l). Probing stops until then.');
|
|
2384
|
+
}
|
|
2373
2385
|
// Once, at a threshold that cannot be a blip, then every 100th so it stays visible
|
|
2374
2386
|
// without walling the log.
|
|
2375
2387
|
if (n === PROBE_FAILURE_ALERT_AT || (n > PROBE_FAILURE_ALERT_AT && n % 100 === 0)) {
|
package/src/server.js
CHANGED
|
@@ -1093,8 +1093,14 @@ async function forwardRequest(
|
|
|
1093
1093
|
// its life. Verified against the real API — the stripped history returns 200, with
|
|
1094
1094
|
// text + tool_use preserved. Tried once per request (thinkingStripped guard); if it
|
|
1095
1095
|
// still fails, the provider pin below is the fallback.
|
|
1096
|
-
|
|
1097
|
-
|
|
1096
|
+
// NOT gated on canRetryBufferedBody. That limit exists to stop a huge body being
|
|
1097
|
+
// re-sent across accounts on a FAILOVER — but a repair SHRINKS the body (measured:
|
|
1098
|
+
// 11.5MB → 5.7MB in 20ms) and is the only thing standing between the user and a
|
|
1099
|
+
// dead session. The body is already fully in memory by this point, so refusing to
|
|
1100
|
+
// rewrite it saves nothing; it just guarantees the 400 surfaces. Reported
|
|
1101
|
+
// 2026-08-10: "history too large to rewrite automatically" on a session the strip
|
|
1102
|
+
// would have fixed in 20ms. The retry it schedules re-checks the SHRUNK size.
|
|
1103
|
+
if (isSignatureRejection && !requestInfo.thinkingStripped && canRepairBody) {
|
|
1098
1104
|
console.log(`[Maxpool] Anthropic rejected a block: ${describeRejectedBlock(body, errorBody)}`);
|
|
1099
1105
|
const { body: cleanBody, removed, converted } = stripForeignThinkingBlocks(body);
|
|
1100
1106
|
if (cleanBody) {
|
|
@@ -1102,10 +1108,14 @@ async function forwardRequest(
|
|
|
1102
1108
|
// rejected round-trip (the client resends the full poisoned history each turn).
|
|
1103
1109
|
accountManager.markSessionThinkingContaminated?.(requestInfo.sessionKey);
|
|
1104
1110
|
console.log(`[Maxpool] Recovering session on Claude: stripped ${removed} provider thinking block(s), converted ${converted} provider search block(s) to text`);
|
|
1111
|
+
// Re-derive the retry budget from the SHRUNK body. Inheriting the old flag
|
|
1112
|
+
// would leave a now-5.7MB body permanently marked "too big to retry" because
|
|
1113
|
+
// it was 11.5MB before the repair — barring the very failover the repair
|
|
1114
|
+
// exists to enable.
|
|
1105
1115
|
return forwardRequest(
|
|
1106
1116
|
req, res, cleanBody, accountManager, upstream, retryCount + 1, hooks, reqId, ctx, logDir,
|
|
1107
1117
|
retryConfig, queueConfig, { ...requestInfo, thinkingStripped: true, repairCount: repairCount + 1 },
|
|
1108
|
-
|
|
1118
|
+
cleanBody.length <= retryConfig.maxRetryBufferBytes, canQueueBufferedBody, excludedIndexes,
|
|
1109
1119
|
);
|
|
1110
1120
|
}
|
|
1111
1121
|
}
|
|
@@ -1113,7 +1123,9 @@ async function forwardRequest(
|
|
|
1113
1123
|
// COORDINATE REPAIR (runs after the broad strip found nothing, or found the wrong
|
|
1114
1124
|
// thing). Anthropic pointed at an exact block index; trust that over our own shape
|
|
1115
1125
|
// model. Its own flag, so it still fires on a request whose broad strip already ran.
|
|
1116
|
-
|
|
1126
|
+
// Same reasoning as the broad strip above: a repair SHRINKS the body, so the
|
|
1127
|
+
// re-send limit must not bar it.
|
|
1128
|
+
if (isSignatureRejection && !requestInfo.rejectedBlockStripped && canRepairBody) {
|
|
1117
1129
|
const { body: fixedBody, removed, type } = stripRejectedBlockClass(body, errorBody);
|
|
1118
1130
|
if (fixedBody) {
|
|
1119
1131
|
// Latch the session ONLY for the class the pre-strip can actually repair up
|
|
@@ -1133,7 +1145,7 @@ async function forwardRequest(
|
|
|
1133
1145
|
thinkingStripped: preStripCanRepeat || requestInfo.thinkingStripped,
|
|
1134
1146
|
repairCount: repairCount + 1,
|
|
1135
1147
|
},
|
|
1136
|
-
|
|
1148
|
+
fixedBody.length <= retryConfig.maxRetryBufferBytes, canQueueBufferedBody, excludedIndexes,
|
|
1137
1149
|
);
|
|
1138
1150
|
}
|
|
1139
1151
|
}
|
|
@@ -1198,16 +1210,14 @@ async function forwardRequest(
|
|
|
1198
1210
|
// and without it the two surviving explanations (a block the strip cannot see
|
|
1199
1211
|
// vs. a body over the retry buffer) are indistinguishable in the log.
|
|
1200
1212
|
console.log(`[Maxpool] Unrepaired signature 400: ${describeRejectedBlock(body, errorBody)} bufferable=${canRetryBufferedBody} stripped=${!!requestInfo.thinkingStripped}`);
|
|
1201
|
-
//
|
|
1202
|
-
//
|
|
1203
|
-
//
|
|
1204
|
-
//
|
|
1205
|
-
|
|
1206
|
-
//
|
|
1207
|
-
|
|
1208
|
-
const what =
|
|
1209
|
-
? `This session's history is too large for maxpool to rewrite automatically (over ${Math.round(retryConfig.maxRetryBufferBytes / (1024 * 1024))}MB). Run /compact and it will keep going.`
|
|
1210
|
-
: rejectedType && rejectedType !== 'thinking' && rejectedType !== 'redacted_thinking'
|
|
1213
|
+
// `peek` (not the full class-strip) — reads only `.type`, no parse+rebuild.
|
|
1214
|
+
// Read it regardless of body size: the repairs are no longer size-gated, so a
|
|
1215
|
+
// large body reaches here for a REAL reason (an unremovable block type) and the
|
|
1216
|
+
// user deserves to be told which one.
|
|
1217
|
+
const rejectedType = peekRejectedBlockType(body, errorBody);
|
|
1218
|
+
// NOTE: no "too large to rewrite" branch any more — the repairs above are no
|
|
1219
|
+
// longer gated on canRetryBufferedBody, so size never blocks a repair attempt.
|
|
1220
|
+
const what = rejectedType && rejectedType !== 'thinking' && rejectedType !== 'redacted_thinking'
|
|
1211
1221
|
? `Anthropic rejected a "${rejectedType}" block in this session's history, which maxpool cannot remove without losing conversation content.`
|
|
1212
1222
|
: requestInfo.thinkingStripped || requestInfo.rejectedBlockStripped
|
|
1213
1223
|
? 'This session ran on GLM/Kimi earlier, and Anthropic will not accept parts of what they wrote. Maxpool repaired what it could and retried on Claude, but Anthropic still rejected the history.'
|
package/src/tui.js
CHANGED
|
@@ -1557,7 +1557,12 @@ export class TUI {
|
|
|
1557
1557
|
// A dead refresh token surfaces as "reauth" (re-login needed) rather than a
|
|
1558
1558
|
// generic "error", so the user knows the fix. Display-only — account.status
|
|
1559
1559
|
// stays 'error' so routing/eligibility still exclude it.
|
|
1560
|
-
|
|
1560
|
+
// A DISABLED account still shows its auth state. Suppressing it meant disabling an
|
|
1561
|
+
// account hid the fact that it needs re-login: the row read "✕ disabled" whether the
|
|
1562
|
+
// credentials were fine or long dead, so re-enabling it later silently produced a
|
|
1563
|
+
// broken account. Reported 2026-08-10 with all 8 disabled accounts sitting on dead
|
|
1564
|
+
// credentials (HTTP 401) and no way to see it.
|
|
1565
|
+
if (a.refreshDead) effectiveStatus = a.enabled === false ? 'disabled-reauth' : 'reauth';
|
|
1561
1566
|
switch (effectiveStatus) {
|
|
1562
1567
|
case 'active': status = isCur ? green('active') : 'active'; break;
|
|
1563
1568
|
case 'reauth': status = yellow('reauth'); break;
|
|
@@ -1566,6 +1571,9 @@ export class TUI {
|
|
|
1566
1571
|
case 'waiting': status = yellow('waiting'); break;
|
|
1567
1572
|
case 'paused': status = yellow('paused'); break;
|
|
1568
1573
|
case 'disabled': status = red('✕ disabled'); break;
|
|
1574
|
+
// Disabled AND needs re-login — both facts matter: it won't serve because you
|
|
1575
|
+
// switched it off, and it CAN'T serve until you log in again.
|
|
1576
|
+
case 'disabled-reauth': status = red('✕ reauth'); break;
|
|
1569
1577
|
case 'throttled': {
|
|
1570
1578
|
// A transient auto-recovering cooldown — show the remaining time (from
|
|
1571
1579
|
// rateLimitedUntil) so it reads as "recovering in Ns", not stuck.
|