switchroom 0.14.93 → 0.15.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.
@@ -13560,6 +13560,7 @@ class AuthBroker {
13560
13560
  listeners = new Map;
13561
13561
  refreshTimer = null;
13562
13562
  consumerProbeTimer = null;
13563
+ fleetProbeTimer = null;
13563
13564
  fetchQuotaImpl;
13564
13565
  stateDir;
13565
13566
  socketRoot;
@@ -13641,6 +13642,17 @@ class AuthBroker {
13641
13642
  }, probeMs);
13642
13643
  this.consumerProbeTimer.unref();
13643
13644
  }
13645
+ if (probeMs > 0 && process.env.SWITCHROOM_DISABLE_FLEET_QUOTA_PROBE !== "1") {
13646
+ this.fleetQuotaProbeTick().catch((err) => {
13647
+ this.logErr(`fleet-quota-probe (boot) threw: ${err.message}`);
13648
+ });
13649
+ this.fleetProbeTimer = setInterval(() => {
13650
+ this.fleetQuotaProbeTick().catch((err) => {
13651
+ this.logErr(`fleet-quota-probe threw: ${err.message}`);
13652
+ });
13653
+ }, probeMs);
13654
+ this.fleetProbeTimer.unref();
13655
+ }
13644
13656
  }
13645
13657
  const fanned = this.fanoutAll();
13646
13658
  if (fanned.length > 0) {
@@ -13671,6 +13683,10 @@ class AuthBroker {
13671
13683
  clearInterval(this.consumerProbeTimer);
13672
13684
  this.consumerProbeTimer = null;
13673
13685
  }
13686
+ if (this.fleetProbeTimer) {
13687
+ clearInterval(this.fleetProbeTimer);
13688
+ this.fleetProbeTimer = null;
13689
+ }
13674
13690
  for (const [sock, lis] of this.listeners) {
13675
13691
  try {
13676
13692
  lis.server.close();
@@ -14096,22 +14112,42 @@ class AuthBroker {
14096
14112
  ok: result.ok,
14097
14113
  error: result.ok ? undefined : result.reason
14098
14114
  });
14099
- if (result.ok) {
14100
- this.lastQuotaCache[label] = {
14101
- fiveHourUtilizationPct: result.data.fiveHourUtilizationPct,
14102
- sevenDayUtilizationPct: result.data.sevenDayUtilizationPct,
14103
- fiveHourResetAt: result.data.fiveHourResetAt?.toISOString() ?? null,
14104
- sevenDayResetAt: result.data.sevenDayResetAt?.toISOString() ?? null,
14105
- representativeClaim: result.data.representativeClaim,
14106
- overageStatus: result.data.overageStatus,
14107
- overageDisabledReason: result.data.overageDisabledReason,
14108
- capturedAt: this.now()
14109
- };
14110
- }
14115
+ if (result.ok)
14116
+ this.cacheQuotaSnapshot(label, result);
14111
14117
  return { label, result };
14112
14118
  }));
14113
14119
  socket.write(encodeSuccess(id, { results }));
14114
14120
  }
14121
+ cacheQuotaSnapshot(label, result) {
14122
+ if (!result.ok)
14123
+ return;
14124
+ this.lastQuotaCache[label] = {
14125
+ fiveHourUtilizationPct: result.data.fiveHourUtilizationPct,
14126
+ sevenDayUtilizationPct: result.data.sevenDayUtilizationPct,
14127
+ fiveHourResetAt: result.data.fiveHourResetAt?.toISOString() ?? null,
14128
+ sevenDayResetAt: result.data.sevenDayResetAt?.toISOString() ?? null,
14129
+ representativeClaim: result.data.representativeClaim,
14130
+ overageStatus: result.data.overageStatus,
14131
+ overageDisabledReason: result.data.overageDisabledReason,
14132
+ capturedAt: this.now()
14133
+ };
14134
+ }
14135
+ async fleetQuotaProbeTick() {
14136
+ for (const label of listAccounts(this.home)) {
14137
+ const creds = readAccountCredentials(label, this.home);
14138
+ const token = creds?.claudeAiOauth?.accessToken;
14139
+ if (!token)
14140
+ continue;
14141
+ let result;
14142
+ try {
14143
+ result = await this.fetchQuotaImpl({ accessToken: token });
14144
+ } catch (err) {
14145
+ this.logErr(`fleet-quota-probe ${label}: ${err.message}`);
14146
+ continue;
14147
+ }
14148
+ this.cacheQuotaSnapshot(label, result);
14149
+ }
14150
+ }
14115
14151
  async consumerQuotaProbeTick() {
14116
14152
  const accounts = Array.from(new Set((this.config.auth?.consumers ?? []).map((c) => c.account)));
14117
14153
  for (const label of accounts) {
@@ -49937,8 +49937,8 @@ var {
49937
49937
  } = import__.default;
49938
49938
 
49939
49939
  // src/build-info.ts
49940
- var VERSION = "0.14.93";
49941
- var COMMIT_SHA = "87b62902";
49940
+ var VERSION = "0.15.0";
49941
+ var COMMIT_SHA = "5841c1d5";
49942
49942
 
49943
49943
  // src/cli/agent.ts
49944
49944
  init_source();
@@ -440,15 +440,20 @@
440
440
  return h;
441
441
  }
442
442
 
443
+ // Tolerate a single transient failure on the 10s auto-refresh: one blip
444
+ // (e.g. the web container restarting) shouldn't flash a scary error over a
445
+ // healthy dashboard. Only surface the error after two in a row.
446
+ let agentFetchFails = 0;
443
447
  async function fetchAgents() {
444
448
  try {
445
449
  const res = await fetch(`${API}/api/agents`, { headers: authHeaders() });
446
450
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
447
451
  agents = await res.json();
448
452
  render();
453
+ agentFetchFails = 0;
449
454
  clearError();
450
455
  } catch (err) {
451
- showError(`Failed to fetch agents: ${err.message}`);
456
+ if (++agentFetchFails >= 2) showError(`Failed to fetch agents: ${err.message}`);
452
457
  }
453
458
  }
454
459
 
@@ -494,12 +499,18 @@
494
499
  }
495
500
 
496
501
  async function fetchConnections() {
502
+ // Each fetch falls back independently (.catch → default). A single
503
+ // network blip — e.g. one endpoint momentarily unreachable — must NOT
504
+ // reject the whole batch and blank the connected accounts; the others
505
+ // still render. (Previously a bare Promise.all meant any one failure
506
+ // wiped the tab, so a connected Google/Microsoft account "vanished".)
507
+ const safe = (p, fallback) => p.then(r => r.ok ? r.json() : fallback).catch(() => fallback);
497
508
  try {
498
509
  const [google, microsoft, notion, agents] = await Promise.all([
499
- fetch(`${API}/api/google-accounts`, { headers: authHeaders() }).then(r => r.ok ? r.json() : []),
500
- fetch(`${API}/api/microsoft-accounts`, { headers: authHeaders() }).then(r => r.ok ? r.json() : []),
501
- fetch(`${API}/api/notion-workspace`, { headers: authHeaders() }).then(r => r.ok ? r.json() : { configured: false, databases: [] }),
502
- fetch(`${API}/api/agents`, { headers: authHeaders() }).then(r => r.ok ? r.json() : []),
510
+ safe(fetch(`${API}/api/google-accounts`, { headers: authHeaders() }), []),
511
+ safe(fetch(`${API}/api/microsoft-accounts`, { headers: authHeaders() }), []),
512
+ safe(fetch(`${API}/api/notion-workspace`, { headers: authHeaders() }), { configured: false, databases: [] }),
513
+ safe(fetch(`${API}/api/agents`, { headers: authHeaders() }), []),
503
514
  ]);
504
515
  const agentNames = (agents || []).map(a => a.name).sort();
505
516
  renderConnections({ google, microsoft, notion, agentNames });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.14.93",
3
+ "version": "0.15.0",
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": {
@@ -52971,11 +52971,11 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
52971
52971
  }
52972
52972
 
52973
52973
  // ../src/build-info.ts
52974
- var VERSION = "0.14.93";
52975
- var COMMIT_SHA = "87b62902";
52976
- var COMMIT_DATE = "2026-06-10T08:22:44+10:00";
52977
- var LATEST_PR = null;
52978
- var COMMITS_AHEAD_OF_TAG = 3;
52974
+ var VERSION = "0.15.0";
52975
+ var COMMIT_SHA = "5841c1d5";
52976
+ var COMMIT_DATE = "2026-06-09T23:17:14Z";
52977
+ var LATEST_PR = 2253;
52978
+ var COMMITS_AHEAD_OF_TAG = 0;
52979
52979
 
52980
52980
  // gateway/boot-version.ts
52981
52981
  function formatRelativeAgo(iso) {