maxpool 1.10.1 → 1.10.3

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.10.1",
3
+ "version": "1.10.3",
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",
@@ -3010,7 +3010,11 @@ export class AccountManager {
3010
3010
  * cycle is no longer a truthful capacity observation — flag it partial (B2/SC6).
3011
3011
  * It still displays; it is excluded from averages. */
3012
3012
  restoreCapacityState(payload, now = Date.now(), downtimeMs = null) {
3013
+ // Preserve the test seam across the ledger swap (fromSerialized returns a fresh
3014
+ // instance; without this the zero-floor harness override dies on any restore).
3015
+ const floorOverride = this.capacity?._readFloorOverride ?? null;
3013
3016
  this.capacity = CapacityLedger.fromSerialized(payload);
3017
+ this.capacity._readFloorOverride = floorOverride;
3014
3018
  // Partial is keyed on MAXPOOL'S OWN downtime, NEVER on the account's last request:
3015
3019
  // an account parked >10min mid-cycle is normal fleet rotation, and keying on its
3016
3020
  // last request discarded valid observations on every reload (red-team F3). The
@@ -31,6 +31,10 @@ const SCHEMA_VERSION = 3;
31
31
  // Two closes this far apart are the SAME boundary observed twice (a clock-close and a
32
32
  // stamp-advance racing across the boundary second), not two windows.
33
33
  const SAME_BOUNDARY_MS = 5_000;
34
+
35
+ // The minimum span of a COMPLETE cycle per window (80% of nominal): a real one is
36
+ // flagged partial by the writer, so a complete cycle below this is corrupt history.
37
+ const READ_FLOOR_MS = { ses: 4 * 3600_000, wk: 5 * 86400_000 };
34
38
  const MAX_CYCLES_PER_WINDOW = 50;
35
39
  const MAX_DAY_BUCKETS = 10;
36
40
 
@@ -54,6 +58,9 @@ export class CapacityLedger {
54
58
  // Cleared per account+window once that window's first boundary is behind us.
55
59
  this._joinedMidWindow = false;
56
60
  this._utilObservedAt = 0; // last utilization-reading arrival (see estimateFromUtilization)
61
+ // Test seam ONLY: real-window fixtures are laborious for every close-cycle test,
62
+ // so tests may zero the read floor. Production never touches this.
63
+ this._readFloorOverride = null;
57
64
  }
58
65
 
59
66
  /** Restore from a serialized payload (state.json). Tolerant: unknown schemaVersion →
@@ -324,7 +331,20 @@ export class CapacityLedger {
324
331
  * are observations, not capacity). */
325
332
  windowStats(name, window) {
326
333
  const rec = this._accounts.get(name);
327
- const closed = (rec?.[window]?.closed || []).filter(c => c.complete && !c.disabledDuring);
334
+ // READ-TIME FLOOR. A cycle marked complete + never-disabled but spanning far less
335
+ // than its window is junk from a writer bug — the live ledger held a 0.5-second /
336
+ // 588-token "cycle" (clock-close and stamp-advance racing at one boundary, the fold
337
+ // refused on an endedAt mismatch) and it dragged this account's Avg3 down ~188k.
338
+ // Unlike the WRITE-time floor I tried first (reverted: it punished accounts that
339
+ // were merely idle early in their window), at read time a genuinely short cycle is
340
+ // already flagged complete:false by the writer — so a complete sub-floor cycle is
341
+ // definitionally corrupt, never a real observation.
342
+ const floor = (this._readFloorOverride ?? READ_FLOOR_MS)[window] ?? 0;
343
+ // >= floor, with a 1s tolerance for the accrue-vs-close clock race (an accrue can
344
+ // stamp Date.now() a tick AFTER the close computed its boundary, making a 0-span
345
+ // cycle read as -1ms — found by debugging a D2 failure that only reproduced in-file).
346
+ const closed = (rec?.[window]?.closed || []).filter(c =>
347
+ c.complete && !c.disabledDuring && (c.endedAt - c.startedAt) >= floor - 1_000);
328
348
  if (!closed.length) return null;
329
349
  const avg = (arr) => arr.length ? Math.round(arr.reduce((a, b) => a + b, 0) / arr.length) : null;
330
350
  const tokens = closed.map(c => c.tokens);
package/src/server.js CHANGED
@@ -1991,7 +1991,23 @@ function stripRejectedBlockClass(body, errorBody) {
1991
1991
  // and keeping the original would resend the exact body that just 400'd. Except
1992
1992
  // messages[0], which must survive as a `user` turn (see the same guard above).
1993
1993
  if (kept.length === 0) {
1994
- if (messages.length === 0) { messages.push({ ...msg, content: [{ type: 'text', text: '(content removed)' }] }); }
1994
+ // messages[0] must survive AS A USER TURN — Anthropic requires the first message
1995
+ // to be `user`, and preserving the original role (what this did until
1996
+ // 2026-08-24) left a system-first transcript system-first: the guard silently
1997
+ // not doing the one thing it exists for.
1998
+ if (messages.length === 0) {
1999
+ messages.push({ role: 'user', content: [{ type: 'text', text: '(content removed)' }] });
2000
+ continue;
2001
+ }
2002
+ // A SYSTEM turn is never dropped. Every other role can go — the turn carried
2003
+ // nothing but a poisoned block, and a gap is harmless. A system is an
2004
+ // INSTRUCTION channel: deleting it changes how the session behaves with no
2005
+ // signal to anyone, which is worse than the loud 400 the repair is fixing.
2006
+ // Keep the turn as a placeholder so its position and presence survive.
2007
+ if (msg?.role === 'system') {
2008
+ messages.push({ ...msg, content: [{ type: 'text', text: '(content removed)' }] });
2009
+ continue;
2010
+ }
1995
2011
  continue;
1996
2012
  }
1997
2013
  messages.push({ ...msg, content: kept });
@@ -2169,7 +2185,23 @@ function stripForeignThinkingBlocks(body) {
2169
2185
  // Reachable only since the role gate was removed — before that, non-assistant
2170
2186
  // turns were never dropped at all.
2171
2187
  if (kept.length === 0) {
2172
- if (messages.length === 0) { messages.push({ ...msg, content: [{ type: 'text', text: '(content removed)' }] }); }
2188
+ // messages[0] must survive AS A USER TURN — Anthropic requires the first message
2189
+ // to be `user`, and preserving the original role (what this did until
2190
+ // 2026-08-24) left a system-first transcript system-first: the guard silently
2191
+ // not doing the one thing it exists for.
2192
+ if (messages.length === 0) {
2193
+ messages.push({ role: 'user', content: [{ type: 'text', text: '(content removed)' }] });
2194
+ continue;
2195
+ }
2196
+ // A SYSTEM turn is never dropped. Every other role can go — the turn carried
2197
+ // nothing but a poisoned block, and a gap is harmless. A system is an
2198
+ // INSTRUCTION channel: deleting it changes how the session behaves with no
2199
+ // signal to anyone, which is worse than the loud 400 the repair is fixing.
2200
+ // Keep the turn as a placeholder so its position and presence survive.
2201
+ if (msg?.role === 'system') {
2202
+ messages.push({ ...msg, content: [{ type: 'text', text: '(content removed)' }] });
2203
+ continue;
2204
+ }
2173
2205
  continue;
2174
2206
  }
2175
2207
  messages.push({ ...msg, content: kept });