switchroom 0.14.80 → 0.14.81

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.
@@ -12286,6 +12286,35 @@ async function fetchQuota(opts) {
12286
12286
  return parsed;
12287
12287
  }
12288
12288
 
12289
+ // src/auth/broker/consumer-quota-sensor.ts
12290
+ var EXHAUSTION_PCT = 99.5;
12291
+ var DEFAULT_CONSUMER_PROBE_INTERVAL_MS = 10 * 60 * 1000;
12292
+ function quotaIndicatesExhaustion(result) {
12293
+ if (!result.ok)
12294
+ return { exhausted: false, until: null };
12295
+ const d = result.data;
12296
+ const fiveBlocked = d.fiveHourUtilizationPct >= EXHAUSTION_PCT;
12297
+ const sevenBlocked = d.sevenDayUtilizationPct >= EXHAUSTION_PCT;
12298
+ if (!fiveBlocked && !sevenBlocked)
12299
+ return { exhausted: false, until: null };
12300
+ const fiveReset = fiveBlocked ? d.fiveHourResetAt?.getTime() ?? null : null;
12301
+ const sevenReset = sevenBlocked ? d.sevenDayResetAt?.getTime() ?? null : null;
12302
+ const candidates = [fiveReset, sevenReset].filter((x) => x != null);
12303
+ const until = candidates.length > 0 ? Math.max(...candidates) : null;
12304
+ return { exhausted: true, until };
12305
+ }
12306
+ function resolveConsumerProbeIntervalMs(env) {
12307
+ if (env.SWITCHROOM_DISABLE_CONSUMER_QUOTA_PROBE === "1")
12308
+ return 0;
12309
+ const raw = env.SWITCHROOM_CONSUMER_QUOTA_PROBE_MS;
12310
+ if (raw !== undefined) {
12311
+ const n = Number(raw);
12312
+ if (Number.isFinite(n) && n >= 0)
12313
+ return n;
12314
+ }
12315
+ return DEFAULT_CONSUMER_PROBE_INTERVAL_MS;
12316
+ }
12317
+
12289
12318
  // src/util/atomic.ts
12290
12319
  import { randomBytes } from "node:crypto";
12291
12320
  import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
@@ -13483,6 +13512,8 @@ class AuthBroker {
13483
13512
  config;
13484
13513
  listeners = new Map;
13485
13514
  refreshTimer = null;
13515
+ consumerProbeTimer = null;
13516
+ fetchQuotaImpl;
13486
13517
  stateDir;
13487
13518
  socketRoot;
13488
13519
  home;
@@ -13506,6 +13537,7 @@ class AuthBroker {
13506
13537
  this.now = opts.now ?? nowMs;
13507
13538
  this.operatorUid = opts.operatorUid;
13508
13539
  this.fetcher = opts.fetcher;
13540
+ this.fetchQuotaImpl = opts._testFetchQuota ?? fetchQuota;
13509
13541
  this.stateDir = opts.stateDir ?? resolve7(this.homeRoot(), ".switchroom", "state", "auth-broker");
13510
13542
  this.socketRoot = opts.socketRoot ?? AUTH_BROKER_ROOT;
13511
13543
  this.providers = new ProviderRegistry;
@@ -13552,6 +13584,16 @@ class AuthBroker {
13552
13584
  });
13553
13585
  }, REFRESH_TICK_INTERVAL_MS);
13554
13586
  this.refreshTimer.unref();
13587
+ const probeMs = resolveConsumerProbeIntervalMs(process.env);
13588
+ const hasConsumers = (this.config.auth?.consumers ?? []).length > 0;
13589
+ if (probeMs > 0 && hasConsumers) {
13590
+ this.consumerProbeTimer = setInterval(() => {
13591
+ this.consumerQuotaProbeTick().catch((err) => {
13592
+ this.logErr(`consumer-quota-probe threw: ${err.message}`);
13593
+ });
13594
+ }, probeMs);
13595
+ this.consumerProbeTimer.unref();
13596
+ }
13555
13597
  }
13556
13598
  const fanned = this.fanoutAll();
13557
13599
  if (fanned.length > 0) {
@@ -13578,6 +13620,10 @@ class AuthBroker {
13578
13620
  clearInterval(this.refreshTimer);
13579
13621
  this.refreshTimer = null;
13580
13622
  }
13623
+ if (this.consumerProbeTimer) {
13624
+ clearInterval(this.consumerProbeTimer);
13625
+ this.consumerProbeTimer = null;
13626
+ }
13581
13627
  for (const [sock, lis] of this.listeners) {
13582
13628
  try {
13583
13629
  lis.server.close();
@@ -13995,7 +14041,7 @@ class AuthBroker {
13995
14041
  this.audit({ op: "probe-quota", identity: identity2, account: label, ok: false, error: "missing-credentials" });
13996
14042
  return { label, result: result2 };
13997
14043
  }
13998
- const result = await fetchQuota({ accessToken: token, timeoutMs });
14044
+ const result = await this.fetchQuotaImpl({ accessToken: token, timeoutMs });
13999
14045
  this.audit({
14000
14046
  op: "probe-quota",
14001
14047
  identity: identity2,
@@ -14019,6 +14065,34 @@ class AuthBroker {
14019
14065
  }));
14020
14066
  socket.write(encodeSuccess(id, { results }));
14021
14067
  }
14068
+ async consumerQuotaProbeTick() {
14069
+ const accounts = Array.from(new Set((this.config.auth?.consumers ?? []).map((c) => c.account)));
14070
+ for (const label of accounts) {
14071
+ const creds = readAccountCredentials(label, this.home);
14072
+ const token = creds?.claudeAiOauth?.accessToken;
14073
+ if (!token)
14074
+ continue;
14075
+ let result;
14076
+ try {
14077
+ result = await this.fetchQuotaImpl({ accessToken: token });
14078
+ } catch (err) {
14079
+ this.logErr(`consumer-quota-probe ${label}: ${err.message}`);
14080
+ continue;
14081
+ }
14082
+ const decision = quotaIndicatesExhaustion(result);
14083
+ if (!decision.exhausted)
14084
+ continue;
14085
+ const exhaustedUntil = decision.until ?? this.now() + MARK_EXHAUSTED_DEFAULT_MS;
14086
+ const existing = this.quota[label]?.exhausted_until;
14087
+ if (existing !== undefined && existing >= exhaustedUntil)
14088
+ continue;
14089
+ this.quota[label] = { exhausted_until: exhaustedUntil };
14090
+ this.persistQuota();
14091
+ this.audit({ op: "mark-exhausted", identity: { kind: "operator" }, account: label, ok: true });
14092
+ process.stdout.write(`auth-broker: consumer-quota-sensor marked ${label} exhausted until ${new Date(exhaustedUntil).toISOString()} — consumer(s) fail over
14093
+ `);
14094
+ }
14095
+ }
14022
14096
  async opSetActive(socket, id, identity2, account) {
14023
14097
  if (!this.isAdmin(identity2)) {
14024
14098
  this.audit({ op: "set-active", identity: identity2, account, ok: false, error: "FORBIDDEN" });
@@ -49700,8 +49700,8 @@ var {
49700
49700
  } = import__.default;
49701
49701
 
49702
49702
  // src/build-info.ts
49703
- var VERSION = "0.14.80";
49704
- var COMMIT_SHA = "1198bdb5";
49703
+ var VERSION = "0.14.81";
49704
+ var COMMIT_SHA = "4ac9cc7d";
49705
49705
 
49706
49706
  // src/cli/agent.ts
49707
49707
  init_source();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.14.80",
3
+ "version": "0.14.81",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52810,11 +52810,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
52810
52810
  }
52811
52811
 
52812
52812
  // ../src/build-info.ts
52813
- var VERSION = "0.14.80";
52814
- var COMMIT_SHA = "1198bdb5";
52815
- var COMMIT_DATE = "2026-06-07T09:36:25+10:00";
52813
+ var VERSION = "0.14.81";
52814
+ var COMMIT_SHA = "4ac9cc7d";
52815
+ var COMMIT_DATE = "2026-06-07T10:43:55+10:00";
52816
52816
  var LATEST_PR = null;
52817
- var COMMITS_AHEAD_OF_TAG = 1;
52817
+ var COMMITS_AHEAD_OF_TAG = 2;
52818
52818
 
52819
52819
  // gateway/boot-version.ts
52820
52820
  function formatRelativeAgo(iso) {