switchroom 0.19.3 → 0.19.4
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.
|
@@ -18839,6 +18839,27 @@ function parseQuotaHeaders(headers, now = new Date) {
|
|
|
18839
18839
|
}
|
|
18840
18840
|
};
|
|
18841
18841
|
}
|
|
18842
|
+
function extractApiErrorMessage(bodyText) {
|
|
18843
|
+
if (!bodyText || bodyText.trim().length === 0)
|
|
18844
|
+
return null;
|
|
18845
|
+
try {
|
|
18846
|
+
const parsed = JSON.parse(bodyText);
|
|
18847
|
+
const msg = parsed?.error?.message;
|
|
18848
|
+
if (typeof msg === "string" && msg.trim().length > 0)
|
|
18849
|
+
return msg.trim();
|
|
18850
|
+
} catch {}
|
|
18851
|
+
return bodyText.trim().slice(0, 500);
|
|
18852
|
+
}
|
|
18853
|
+
function isEntitlementDisabledMessage(message) {
|
|
18854
|
+
if (!message)
|
|
18855
|
+
return false;
|
|
18856
|
+
const m = message.toLowerCase();
|
|
18857
|
+
if (!m.includes("disabled"))
|
|
18858
|
+
return false;
|
|
18859
|
+
const mentionsCode = m.includes("claude code") || m.includes("claude subscription");
|
|
18860
|
+
const mentionsOrgOrSub = m.includes("organization") || m.includes("subscription") || m.includes(" org ");
|
|
18861
|
+
return mentionsCode && mentionsOrgOrSub;
|
|
18862
|
+
}
|
|
18842
18863
|
async function fetchQuota(opts) {
|
|
18843
18864
|
const token = opts.accessToken?.trim();
|
|
18844
18865
|
if (!token || token.length === 0) {
|
|
@@ -18874,13 +18895,28 @@ async function fetchQuota(opts) {
|
|
|
18874
18895
|
}
|
|
18875
18896
|
return { ok: false, reason: `quota probe network error: ${msg}` };
|
|
18876
18897
|
}
|
|
18877
|
-
clearTimeout(timeout);
|
|
18878
18898
|
const parsed = parseQuotaHeaders(resp.headers);
|
|
18879
|
-
if (parsed.ok)
|
|
18899
|
+
if (parsed.ok) {
|
|
18900
|
+
clearTimeout(timeout);
|
|
18880
18901
|
return parsed;
|
|
18902
|
+
}
|
|
18881
18903
|
if (!resp.ok) {
|
|
18882
|
-
|
|
18904
|
+
let bodyText = "";
|
|
18905
|
+
try {
|
|
18906
|
+
bodyText = await resp.text();
|
|
18907
|
+
} catch {}
|
|
18908
|
+
clearTimeout(timeout);
|
|
18909
|
+
const apiErrorMessage = extractApiErrorMessage(bodyText);
|
|
18910
|
+
const entitlement = resp.status === 403 && isEntitlementDisabledMessage(apiErrorMessage);
|
|
18911
|
+
return {
|
|
18912
|
+
ok: false,
|
|
18913
|
+
reason: `HTTP ${resp.status} from Anthropic (${parsed.reason})`,
|
|
18914
|
+
httpStatus: resp.status,
|
|
18915
|
+
...apiErrorMessage ? { apiErrorMessage } : {},
|
|
18916
|
+
failureKind: entitlement ? "entitlement_blocked" : "other"
|
|
18917
|
+
};
|
|
18883
18918
|
}
|
|
18919
|
+
clearTimeout(timeout);
|
|
18884
18920
|
return parsed;
|
|
18885
18921
|
}
|
|
18886
18922
|
|
|
@@ -18912,7 +18948,9 @@ function snapshotClearlyHealthy(s) {
|
|
|
18912
18948
|
return s.fiveHourUtilizationPct < HEALTHY_CLEAR_PCT && s.sevenDayUtilizationPct < HEALTHY_CLEAR_PCT;
|
|
18913
18949
|
}
|
|
18914
18950
|
function accountEligibility(opts) {
|
|
18915
|
-
const { mark, snapshot, now, allowOverage = false } = opts;
|
|
18951
|
+
const { mark, snapshot, now, allowOverage = false, entitlementBlocked = false } = opts;
|
|
18952
|
+
if (entitlementBlocked)
|
|
18953
|
+
return "blocked";
|
|
18916
18954
|
if (snapshotFresh(snapshot, now) && snapshotWalled(snapshot) && !overageLiftsWall(snapshot, allowOverage)) {
|
|
18917
18955
|
return "blocked";
|
|
18918
18956
|
}
|
|
@@ -21170,9 +21208,51 @@ class AuthBroker {
|
|
|
21170
21208
|
mark: this.exhaustionMarkOf(account),
|
|
21171
21209
|
snapshot: this.lastQuotaCache[account],
|
|
21172
21210
|
now: this.now(),
|
|
21173
|
-
allowOverage: this.isOverageAllowed(account)
|
|
21211
|
+
allowOverage: this.isOverageAllowed(account),
|
|
21212
|
+
entitlementBlocked: this.isAccountEntitlementBlocked(account)
|
|
21174
21213
|
});
|
|
21175
21214
|
}
|
|
21215
|
+
isAccountEntitlementBlocked(account) {
|
|
21216
|
+
return this.quota[account]?.entitlement_blocked === true;
|
|
21217
|
+
}
|
|
21218
|
+
markEntitlementBlocked(label, result) {
|
|
21219
|
+
if (result.ok || result.failureKind !== "entitlement_blocked")
|
|
21220
|
+
return;
|
|
21221
|
+
if (this.quota[label]?.entitlement_blocked === true)
|
|
21222
|
+
return;
|
|
21223
|
+
this.quota[label] = {
|
|
21224
|
+
...this.quota[label],
|
|
21225
|
+
entitlement_blocked: true,
|
|
21226
|
+
entitlement_blocked_at: this.now(),
|
|
21227
|
+
...result.apiErrorMessage ? { entitlement_blocked_reason: result.apiErrorMessage } : {}
|
|
21228
|
+
};
|
|
21229
|
+
this.persistQuota();
|
|
21230
|
+
this.audit({
|
|
21231
|
+
op: "mark-exhausted",
|
|
21232
|
+
identity: { kind: "operator" },
|
|
21233
|
+
account: label,
|
|
21234
|
+
accountKind: "claude",
|
|
21235
|
+
ok: true,
|
|
21236
|
+
reason: "entitlement-403"
|
|
21237
|
+
});
|
|
21238
|
+
process.stdout.write(`auth-broker: ${label} returned entitlement-403 (Claude Code access disabled) — marked entitlement_blocked${result.apiErrorMessage ? ` (${result.apiErrorMessage})` : ""}
|
|
21239
|
+
`);
|
|
21240
|
+
}
|
|
21241
|
+
clearEntitlementBlocked(label) {
|
|
21242
|
+
const entry = this.quota[label];
|
|
21243
|
+
if (!entry?.entitlement_blocked)
|
|
21244
|
+
return;
|
|
21245
|
+
const {
|
|
21246
|
+
entitlement_blocked,
|
|
21247
|
+
entitlement_blocked_at,
|
|
21248
|
+
entitlement_blocked_reason,
|
|
21249
|
+
...rest
|
|
21250
|
+
} = entry;
|
|
21251
|
+
this.quota[label] = rest;
|
|
21252
|
+
this.persistQuota();
|
|
21253
|
+
process.stdout.write(`auth-broker: live probe of ${label} succeeded — cleared entitlement_blocked mark
|
|
21254
|
+
`);
|
|
21255
|
+
}
|
|
21176
21256
|
premiumWallMarkOf(account) {
|
|
21177
21257
|
const q = this.quota[account];
|
|
21178
21258
|
if (!q || q.premium_walled_until === undefined)
|
|
@@ -21329,7 +21409,8 @@ class AuthBroker {
|
|
|
21329
21409
|
mark: this.exhaustionMarkOf(account),
|
|
21330
21410
|
snapshot,
|
|
21331
21411
|
now,
|
|
21332
|
-
allowOverage: true
|
|
21412
|
+
allowOverage: true,
|
|
21413
|
+
entitlementBlocked: this.isAccountEntitlementBlocked(account)
|
|
21333
21414
|
}) === "eligible";
|
|
21334
21415
|
}
|
|
21335
21416
|
accountEligibilityOf(account) {
|
|
@@ -21339,7 +21420,8 @@ class AuthBroker {
|
|
|
21339
21420
|
mark: this.exhaustionMarkOf(account),
|
|
21340
21421
|
snapshot,
|
|
21341
21422
|
now: this.now(),
|
|
21342
|
-
allowOverage
|
|
21423
|
+
allowOverage,
|
|
21424
|
+
entitlementBlocked: this.isAccountEntitlementBlocked(account)
|
|
21343
21425
|
});
|
|
21344
21426
|
if (verdict === "eligible" && allowOverage && snapshot && (snapshot.fiveHourUtilizationPct >= WALL_PCT || snapshot.sevenDayUtilizationPct >= WALL_PCT)) {
|
|
21345
21427
|
process.stdout.write(`auth-broker: ${account} is past the utilization wall but eligible via allow_overage — Anthropic overage billing active (5h=${snapshot.fiveHourUtilizationPct.toFixed(1)}%, 7d=${snapshot.sevenDayUtilizationPct.toFixed(1)}%)
|
|
@@ -21356,6 +21438,8 @@ class AuthBroker {
|
|
|
21356
21438
|
const result = await this.probeQuotaSingleFlight(account, token);
|
|
21357
21439
|
if (result.ok)
|
|
21358
21440
|
this.cacheQuotaSnapshot(account, result);
|
|
21441
|
+
else
|
|
21442
|
+
this.markEntitlementBlocked(account, result);
|
|
21359
21443
|
} catch {}
|
|
21360
21444
|
}
|
|
21361
21445
|
async nextHealthyAccountLive(current, order) {
|
|
@@ -21455,7 +21539,6 @@ class AuthBroker {
|
|
|
21455
21539
|
expiresAt: creds?.claudeAiOauth?.expiresAt,
|
|
21456
21540
|
exhausted,
|
|
21457
21541
|
in_service: inService.has(label),
|
|
21458
|
-
entitlement_blocked: false,
|
|
21459
21542
|
exhausted_until: q?.exhausted_until,
|
|
21460
21543
|
throttled_until: q?.throttled_until,
|
|
21461
21544
|
threshold_violations: this.thresholdViolations[label] ?? 0,
|
|
@@ -21465,6 +21548,7 @@ class AuthBroker {
|
|
|
21465
21548
|
premium_walled_until: q?.premium_walled_until,
|
|
21466
21549
|
premium_wall_bucket: q?.premium_wall_bucket,
|
|
21467
21550
|
last_tier_quota: this.lastTierQuotaCache[label] ?? null,
|
|
21551
|
+
entitlement_blocked: q?.entitlement_blocked ?? false,
|
|
21468
21552
|
usage_ledger: summarizeAccountUsage(this.usageLedger, label, this.now())
|
|
21469
21553
|
};
|
|
21470
21554
|
});
|
|
@@ -21539,6 +21623,7 @@ class AuthBroker {
|
|
|
21539
21623
|
this.cacheQuotaSnapshot(label, result);
|
|
21540
21624
|
return { label, result, served: "live" };
|
|
21541
21625
|
}
|
|
21626
|
+
this.markEntitlementBlocked(label, result);
|
|
21542
21627
|
if (cached) {
|
|
21543
21628
|
return { label, result: cachedSnapshotToResult(cached), served: "cache", capturedAt: cached.capturedAt };
|
|
21544
21629
|
}
|
|
@@ -21560,6 +21645,7 @@ class AuthBroker {
|
|
|
21560
21645
|
cacheQuotaSnapshot(label, result) {
|
|
21561
21646
|
if (!result.ok)
|
|
21562
21647
|
return;
|
|
21648
|
+
this.clearEntitlementBlocked(label);
|
|
21563
21649
|
const snapshot = {
|
|
21564
21650
|
fiveHourUtilizationPct: result.data.fiveHourUtilizationPct,
|
|
21565
21651
|
sevenDayUtilizationPct: result.data.sevenDayUtilizationPct,
|
|
@@ -21589,6 +21675,8 @@ class AuthBroker {
|
|
|
21589
21675
|
const canarySet = this.premiumCanarySet();
|
|
21590
21676
|
const canaryEnabled = process.env.SWITCHROOM_DISABLE_MODEL_TIER_PROBE !== "1";
|
|
21591
21677
|
for (const label of listAccounts(this.home)) {
|
|
21678
|
+
if (this.isAccountEntitlementBlocked(label))
|
|
21679
|
+
continue;
|
|
21592
21680
|
const creds = readAccountCredentials(label, this.home);
|
|
21593
21681
|
const token = creds?.claudeAiOauth?.accessToken;
|
|
21594
21682
|
if (!token)
|
|
@@ -21601,6 +21689,7 @@ class AuthBroker {
|
|
|
21601
21689
|
continue;
|
|
21602
21690
|
}
|
|
21603
21691
|
this.cacheQuotaSnapshot(label, result);
|
|
21692
|
+
this.markEntitlementBlocked(label, result);
|
|
21604
21693
|
probed.push({ label, result });
|
|
21605
21694
|
if (canaryEnabled && canarySet.has(label)) {
|
|
21606
21695
|
let tierResult;
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.19.
|
|
2123
|
+
var VERSION = "0.19.4", COMMIT_SHA = "7cdf5428";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -26663,7 +26663,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:f
|
|
|
26663
26663
|
import { dirname as dirname4, join as join7 } from "node:path";
|
|
26664
26664
|
|
|
26665
26665
|
// src/build-info.ts
|
|
26666
|
-
var VERSION = "0.19.
|
|
26666
|
+
var VERSION = "0.19.4";
|
|
26667
26667
|
|
|
26668
26668
|
// src/cli/resolve-version.ts
|
|
26669
26669
|
function readPackageVersion() {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.19.
|
|
4
|
+
"version": "0.19.4",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -93135,10 +93135,10 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
|
|
|
93135
93135
|
}
|
|
93136
93136
|
|
|
93137
93137
|
// ../src/build-info.ts
|
|
93138
|
-
var VERSION = "0.19.
|
|
93139
|
-
var COMMIT_SHA = "
|
|
93140
|
-
var COMMIT_DATE = "2026-07-
|
|
93141
|
-
var LATEST_PR =
|
|
93138
|
+
var VERSION = "0.19.4";
|
|
93139
|
+
var COMMIT_SHA = "7cdf5428";
|
|
93140
|
+
var COMMIT_DATE = "2026-07-20T11:09:10+10:00";
|
|
93141
|
+
var LATEST_PR = 3457;
|
|
93142
93142
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
93143
93143
|
|
|
93144
93144
|
// gateway/boot-version.ts
|