troxy-cli 1.30.22 → 1.30.23

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": "troxy-cli",
3
- "version": "1.30.22",
3
+ "version": "1.30.23",
4
4
  "description": "Make your Claude Code usage go further: lighter requests, less usage, less money",
5
5
  "homepage": "https://troxy.io",
6
6
  "bugs": {
@@ -360,6 +360,22 @@ const COMPACTED = { plugin: 'troxy-guard', key: 'compacted' }; // the band's not
360
360
  const COMPACTED_SHOW_MS = 10 * 60 * 1000; // kept through prompts for this long, so it isn't missed
361
361
  let compactTimer = null;
362
362
 
363
+ // Big-session compaction: a session past 200k compacts right after an answer, between
364
+ // tasks, while its cache is still warm, instead of re-reading the whole thing on every
365
+ // later call. A/B 2026-11-06 at 350k on Sonnet: 4.0 vs 6.0 bar points for the same 9-step
366
+ // job, the compaction included, same results. The short delay lets the turn settle; a
367
+ // prompt typed first cancels it (scorePrompt), so it never holds the user up.
368
+ // Off unless switched on (config bigCompact, or TROXY_BIG_COMPACT=1) until measured live.
369
+ const BIG_COMPACT_TOKENS = 200000;
370
+ const BIG_COMPACT_DELAY_MS = 3000;
371
+ function afterTurnDelay({ tokens, on, off }) {
372
+ return on && !off && tokens >= BIG_COMPACT_TOKENS ? BIG_COMPACT_DELAY_MS : COMPACT_IDLE_MS;
373
+ }
374
+ async function bigCompactOn($) {
375
+ if ((await $.env.get('TROXY_BIG_COMPACT')) === '1') return true;
376
+ return Boolean((await troxyConfig($)).bigCompact);
377
+ }
378
+
363
379
  // What to do when the idle timer fires: skip, compact now, or wait for the meter to reset.
364
380
  function compactPlan({ tokens, rateLimits = [], now, off }) {
365
381
  if (off || !(tokens >= COMPACT_MIN_TOKENS)) return { action: 'skip' };
@@ -434,7 +450,7 @@ async function idleCompact($, { late = false } = {}) {
434
450
  if (r.skip) return;
435
451
  const k = n => `${Math.round(n / 1000)}k`;
436
452
  await $.state.set(COMPACTED, { at: await $.clock.now(), text: r.tokensBefore && r.tokensAfter
437
- ? `compacted this idle session ${k(r.tokensBefore)} → ${k(r.tokensAfter)} tokens` : 'compacted this idle session' });
453
+ ? `compacted this session ${k(r.tokensBefore)} → ${k(r.tokensAfter)} tokens` : 'compacted this session' });
438
454
  }
439
455
 
440
456
  // Where the local daemon looks for this session's routing note (see interceptor.js).
@@ -681,12 +697,16 @@ export function register(on) {
681
697
  if (line) await $.state.set(LINE, line);
682
698
  } catch {} // a missed turn only leaves the band as it was
683
699
  }
684
- let stayAwake = false;
700
+ let stayAwake = false, delay = COMPACT_IDLE_MS;
685
701
  try { stayAwake = await bigSession($); } catch {}
686
- armCompact($, COMPACT_IDLE_MS, { stayAwake, persist: true });
702
+ try {
703
+ delay = afterTurnDelay({ tokens: ((await $.session.usage()).context || {}).tokens || 0,
704
+ on: await bigCompactOn($), off: await $.store.get('compactOff') });
705
+ } catch {}
706
+ armCompact($, delay, { stayAwake: stayAwake && delay === COMPACT_IDLE_MS, persist: true });
687
707
  return r;
688
708
  });
689
709
  }
690
710
 
691
711
  // Exported for tests only - not part of the mod's runtime contract.
692
- export const _internal = { compressedSince, recalledSince, stepEffort, reportRow, noteStep, flushReport, pendingRows: () => pending, troxyConfig, askedFor, rememberAsked, asked, armCompact, cancelCompact, toDomainAndFields, guardBash, failClosed, parseScore, asRequest, pickModel, troxyCommand, modRouteFile, estimateTokens, MODELS, savedLine, sessionLine, addTurn, withKept, statusText, compactPlan, savingsText, fetchSavings, scorePrompt, routeStep, stepModel, lastSent };
712
+ export const _internal = { compressedSince, recalledSince, stepEffort, reportRow, noteStep, flushReport, pendingRows: () => pending, troxyConfig, askedFor, rememberAsked, asked, armCompact, cancelCompact, afterTurnDelay, toDomainAndFields, guardBash, failClosed, parseScore, asRequest, pickModel, troxyCommand, modRouteFile, estimateTokens, MODELS, savedLine, sessionLine, addTurn, withKept, statusText, compactPlan, savingsText, fetchSavings, scorePrompt, routeStep, stepModel, lastSent };
@@ -113,6 +113,15 @@ test('statusText: version, routing, this session, plan usage, where the account
113
113
  assert.match(statusText({ paused: true }), /^Troxy · routing is off\nThis session: nothing moved to a lighter model yet\n/);
114
114
  });
115
115
 
116
+ test('afterTurnDelay: with bigCompact on, a session past 200k compacts right after the answer', () => {
117
+ const { afterTurnDelay } = _internal;
118
+ const IDLE = 55 * 60 * 1000;
119
+ assert.equal(afterTurnDelay({ tokens: 351000, on: true }), 3000);
120
+ assert.equal(afterTurnDelay({ tokens: 199000, on: true }), IDLE); // under 200k: idle path only
121
+ assert.equal(afterTurnDelay({ tokens: 351000, on: false }), IDLE); // flag off
122
+ assert.equal(afterTurnDelay({ tokens: 351000, on: true, off: true }), IDLE); // /troxy compact off wins
123
+ });
124
+
116
125
  test('compactPlan: big idle sessions compact, small ones and a nearly full bar do not', () => {
117
126
  const { compactPlan } = _internal;
118
127
  const now = Date.parse('2026-10-08T15:00:00Z');