switchroom 0.11.0 → 0.11.1
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/dist/agent-scheduler/index.js +2 -2
- package/dist/auth-broker/index.js +125 -3
- package/dist/cli/drive-write-pretool.mjs +20 -2
- package/dist/cli/switchroom.js +32 -7
- package/dist/host-control/main.js +2 -2
- package/dist/vault/approvals/kernel-server.js +2 -2
- package/dist/vault/broker/server.js +2 -2
- package/package.json +1 -1
- package/telegram-plugin/auto-fallback-fleet.ts +4 -4
- package/telegram-plugin/dist/gateway/gateway.js +242 -226
- package/telegram-plugin/gateway/auth-broker-client.ts +2 -0
- package/telegram-plugin/gateway/auth-command.ts +10 -0
- package/telegram-plugin/gateway/gateway.ts +51 -24
- package/telegram-plugin/gateway/hostd-dispatch.ts +10 -2
|
@@ -16105,7 +16105,7 @@ function decodeResponse(line) {
|
|
|
16105
16105
|
}
|
|
16106
16106
|
return ResponseSchema.parse(parsed);
|
|
16107
16107
|
}
|
|
16108
|
-
var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
|
|
16108
|
+
var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ProbeQuotaRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
|
|
16109
16109
|
var init_protocol = __esm(() => {
|
|
16110
16110
|
init_zod();
|
|
16111
16111
|
MAX_FRAME_BYTES = 64 * 1024;
|
|
@@ -16194,6 +16194,13 @@ var init_protocol = __esm(() => {
|
|
|
16194
16194
|
op: exports_external.literal("list-google-accounts"),
|
|
16195
16195
|
id: exports_external.string().min(1)
|
|
16196
16196
|
});
|
|
16197
|
+
ProbeQuotaRequestSchema = exports_external.object({
|
|
16198
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
16199
|
+
op: exports_external.literal("probe-quota"),
|
|
16200
|
+
id: exports_external.string().min(1),
|
|
16201
|
+
accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
16202
|
+
timeoutMs: exports_external.number().int().positive().max(60000).optional()
|
|
16203
|
+
});
|
|
16197
16204
|
RequestSchema = exports_external.discriminatedUnion("op", [
|
|
16198
16205
|
GetCredentialsRequestSchema,
|
|
16199
16206
|
ListStateRequestSchema,
|
|
@@ -16203,7 +16210,8 @@ var init_protocol = __esm(() => {
|
|
|
16203
16210
|
AddAccountRequestSchema,
|
|
16204
16211
|
RmAccountRequestSchema,
|
|
16205
16212
|
SetOverrideRequestSchema,
|
|
16206
|
-
ListGoogleAccountsRequestSchema
|
|
16213
|
+
ListGoogleAccountsRequestSchema,
|
|
16214
|
+
ProbeQuotaRequestSchema
|
|
16207
16215
|
]);
|
|
16208
16216
|
GetCredentialsDataSchema = exports_external.object({
|
|
16209
16217
|
account: exports_external.string(),
|
|
@@ -16382,6 +16390,16 @@ class AuthBrokerClient {
|
|
|
16382
16390
|
});
|
|
16383
16391
|
return data;
|
|
16384
16392
|
}
|
|
16393
|
+
async probeQuota(accounts, timeoutMs) {
|
|
16394
|
+
const data = await this.send({
|
|
16395
|
+
v: PROTOCOL_VERSION,
|
|
16396
|
+
id: randomUUID3(),
|
|
16397
|
+
op: "probe-quota",
|
|
16398
|
+
accounts: [...accounts],
|
|
16399
|
+
...timeoutMs !== undefined ? { timeoutMs } : {}
|
|
16400
|
+
});
|
|
16401
|
+
return data;
|
|
16402
|
+
}
|
|
16385
16403
|
async setActive(account) {
|
|
16386
16404
|
const data = await this.send({
|
|
16387
16405
|
v: PROTOCOL_VERSION,
|
|
@@ -23889,7 +23907,7 @@ var init_schema = __esm(() => {
|
|
|
23889
23907
|
monthly_budget_usd: exports_external.number().positive().optional().describe("Monthly USD spend budget. If unset, the greeting shows raw usage only.")
|
|
23890
23908
|
});
|
|
23891
23909
|
HostControlConfigSchema = exports_external.object({
|
|
23892
|
-
enabled: exports_external.boolean().
|
|
23910
|
+
enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip \u2014 the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` \u2014 " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C \u00a75.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3).")
|
|
23893
23911
|
});
|
|
23894
23912
|
SwitchroomConfigSchema = exports_external.object({
|
|
23895
23913
|
switchroom: exports_external.object({
|
|
@@ -23915,7 +23933,7 @@ var init_schema = __esm(() => {
|
|
|
23915
23933
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key \u2014 use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
23916
23934
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration \u2014 " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
23917
23935
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
23918
|
-
host_control: HostControlConfigSchema.
|
|
23936
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (docs/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
23919
23937
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
23920
23938
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
23921
23939
|
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
|
@@ -24901,7 +24919,7 @@ function isDockerRuntime() {
|
|
|
24901
24919
|
import * as net3 from "node:net";
|
|
24902
24920
|
import * as fs from "node:fs";
|
|
24903
24921
|
import { homedir as homedir7 } from "node:os";
|
|
24904
|
-
import { join as
|
|
24922
|
+
import { join as join16 } from "node:path";
|
|
24905
24923
|
function defaultBrokerSocketPath() {
|
|
24906
24924
|
if (isDockerRuntime()) {
|
|
24907
24925
|
if (fs.existsSync(OPERATOR_SOCKET_PATH))
|
|
@@ -24911,7 +24929,7 @@ function defaultBrokerSocketPath() {
|
|
|
24911
24929
|
return LEGACY_SOCKET_PATH;
|
|
24912
24930
|
}
|
|
24913
24931
|
function vaultTokenFilePath(agentSlug) {
|
|
24914
|
-
return
|
|
24932
|
+
return join16(homedir7(), ".switchroom", "agents", agentSlug, ".vault-token");
|
|
24915
24933
|
}
|
|
24916
24934
|
function readVaultTokenFile(agentSlug) {
|
|
24917
24935
|
const filePath = vaultTokenFilePath(agentSlug);
|
|
@@ -25038,8 +25056,8 @@ var DEFAULT_TIMEOUT_MS3 = 2000, LEGACY_SOCKET_PATH, OPERATOR_SOCKET_PATH;
|
|
|
25038
25056
|
var init_client2 = __esm(() => {
|
|
25039
25057
|
init_protocol2();
|
|
25040
25058
|
init_peercred();
|
|
25041
|
-
LEGACY_SOCKET_PATH =
|
|
25042
|
-
OPERATOR_SOCKET_PATH =
|
|
25059
|
+
LEGACY_SOCKET_PATH = join16(homedir7(), ".switchroom", "vault-broker.sock");
|
|
25060
|
+
OPERATOR_SOCKET_PATH = join16(homedir7(), ".switchroom", "broker-operator", "sock");
|
|
25043
25061
|
});
|
|
25044
25062
|
|
|
25045
25063
|
// ../src/drive/deep-links.ts
|
|
@@ -38161,7 +38179,8 @@ function createAuthBrokerClient() {
|
|
|
38161
38179
|
setActive: (label) => broker.setActive(label),
|
|
38162
38180
|
rmAccount: (label) => broker.rmAccount(label),
|
|
38163
38181
|
refreshAccount: (label) => broker.refreshAccount(label),
|
|
38164
|
-
setOverride: (agent, account) => broker.setOverride(agent, account)
|
|
38182
|
+
setOverride: (agent, account) => broker.setOverride(agent, account),
|
|
38183
|
+
probeQuota: (accounts, timeoutMs) => broker.probeQuota(accounts, timeoutMs)
|
|
38165
38184
|
};
|
|
38166
38185
|
return { client: client3, close: () => broker.close() };
|
|
38167
38186
|
}
|
|
@@ -38272,6 +38291,16 @@ class AuthBrokerClient2 {
|
|
|
38272
38291
|
});
|
|
38273
38292
|
return data;
|
|
38274
38293
|
}
|
|
38294
|
+
async probeQuota(accounts, timeoutMs) {
|
|
38295
|
+
const data = await this.send({
|
|
38296
|
+
v: PROTOCOL_VERSION,
|
|
38297
|
+
id: randomUUID4(),
|
|
38298
|
+
op: "probe-quota",
|
|
38299
|
+
accounts: [...accounts],
|
|
38300
|
+
...timeoutMs !== undefined ? { timeoutMs } : {}
|
|
38301
|
+
});
|
|
38302
|
+
return data;
|
|
38303
|
+
}
|
|
38275
38304
|
async setActive(account) {
|
|
38276
38305
|
const data = await this.send({
|
|
38277
38306
|
v: PROTOCOL_VERSION,
|
|
@@ -39195,194 +39224,6 @@ function pctSummary(q) {
|
|
|
39195
39224
|
return `${Math.round(q.fiveHourUtilizationPct)}% / ${Math.round(q.sevenDayUtilizationPct)}%`;
|
|
39196
39225
|
}
|
|
39197
39226
|
|
|
39198
|
-
// quota-check.ts
|
|
39199
|
-
import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
|
|
39200
|
-
import { join as join13 } from "path";
|
|
39201
|
-
var OAUTH_BETA2 = "oauth-2025-04-20";
|
|
39202
|
-
var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
|
|
39203
|
-
var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
|
|
39204
|
-
function readOauthToken2(claudeConfigDir) {
|
|
39205
|
-
const tokenFile = join13(claudeConfigDir, ".oauth-token");
|
|
39206
|
-
if (!existsSync13(tokenFile))
|
|
39207
|
-
return null;
|
|
39208
|
-
try {
|
|
39209
|
-
const raw = readFileSync9(tokenFile, "utf-8").trim();
|
|
39210
|
-
return raw.length > 0 ? raw : null;
|
|
39211
|
-
} catch {
|
|
39212
|
-
return null;
|
|
39213
|
-
}
|
|
39214
|
-
}
|
|
39215
|
-
function parseFloatHeader2(headers, name) {
|
|
39216
|
-
const v = headers.get(name);
|
|
39217
|
-
if (v == null || v.trim().length === 0)
|
|
39218
|
-
return null;
|
|
39219
|
-
const n = Number(v);
|
|
39220
|
-
return Number.isFinite(n) ? n : null;
|
|
39221
|
-
}
|
|
39222
|
-
function parseEpochHeader2(headers, name) {
|
|
39223
|
-
const v = headers.get(name);
|
|
39224
|
-
if (v == null)
|
|
39225
|
-
return null;
|
|
39226
|
-
const n = Number(v);
|
|
39227
|
-
if (!Number.isFinite(n) || n <= 0)
|
|
39228
|
-
return null;
|
|
39229
|
-
return new Date(n * 1000);
|
|
39230
|
-
}
|
|
39231
|
-
function parseQuotaHeaders2(headers) {
|
|
39232
|
-
const fiveHour = parseFloatHeader2(headers, "anthropic-ratelimit-unified-5h-utilization");
|
|
39233
|
-
const sevenDay = parseFloatHeader2(headers, "anthropic-ratelimit-unified-7d-utilization");
|
|
39234
|
-
if (fiveHour == null && sevenDay == null) {
|
|
39235
|
-
return {
|
|
39236
|
-
ok: false,
|
|
39237
|
-
reason: "no unified rate-limit headers in response (API token, not OAuth?)"
|
|
39238
|
-
};
|
|
39239
|
-
}
|
|
39240
|
-
return {
|
|
39241
|
-
ok: true,
|
|
39242
|
-
data: {
|
|
39243
|
-
fiveHourUtilizationPct: (fiveHour ?? 0) * 100,
|
|
39244
|
-
sevenDayUtilizationPct: (sevenDay ?? 0) * 100,
|
|
39245
|
-
fiveHourResetAt: parseEpochHeader2(headers, "anthropic-ratelimit-unified-5h-reset"),
|
|
39246
|
-
sevenDayResetAt: parseEpochHeader2(headers, "anthropic-ratelimit-unified-7d-reset"),
|
|
39247
|
-
representativeClaim: headers.get("anthropic-ratelimit-unified-representative-claim"),
|
|
39248
|
-
overageStatus: headers.get("anthropic-ratelimit-unified-overage-status"),
|
|
39249
|
-
overageDisabledReason: headers.get("anthropic-ratelimit-unified-overage-disabled-reason")
|
|
39250
|
-
}
|
|
39251
|
-
};
|
|
39252
|
-
}
|
|
39253
|
-
async function fetchQuota2(opts) {
|
|
39254
|
-
let token;
|
|
39255
|
-
if (opts.accessToken && opts.claudeConfigDir) {
|
|
39256
|
-
return {
|
|
39257
|
-
ok: false,
|
|
39258
|
-
reason: "pass only one of `accessToken` or `claudeConfigDir`, not both"
|
|
39259
|
-
};
|
|
39260
|
-
}
|
|
39261
|
-
if (opts.accessToken) {
|
|
39262
|
-
token = opts.accessToken.trim().length > 0 ? opts.accessToken : null;
|
|
39263
|
-
} else if (opts.claudeConfigDir) {
|
|
39264
|
-
token = readOauthToken2(opts.claudeConfigDir);
|
|
39265
|
-
} else {
|
|
39266
|
-
return {
|
|
39267
|
-
ok: false,
|
|
39268
|
-
reason: "fetchQuota requires `accessToken` or `claudeConfigDir`"
|
|
39269
|
-
};
|
|
39270
|
-
}
|
|
39271
|
-
if (!token) {
|
|
39272
|
-
return { ok: false, reason: "no OAuth token at .oauth-token" };
|
|
39273
|
-
}
|
|
39274
|
-
const controller = new AbortController;
|
|
39275
|
-
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 1e4);
|
|
39276
|
-
const fetchFn = opts.fetchImpl ?? fetch;
|
|
39277
|
-
let resp;
|
|
39278
|
-
try {
|
|
39279
|
-
resp = await fetchFn("https://api.anthropic.com/v1/messages", {
|
|
39280
|
-
method: "POST",
|
|
39281
|
-
headers: {
|
|
39282
|
-
"anthropic-version": "2023-06-01",
|
|
39283
|
-
"anthropic-beta": OAUTH_BETA2,
|
|
39284
|
-
authorization: `Bearer ${token}`,
|
|
39285
|
-
"x-app": "cli",
|
|
39286
|
-
"user-agent": DEFAULT_USER_AGENT2,
|
|
39287
|
-
"content-type": "application/json"
|
|
39288
|
-
},
|
|
39289
|
-
body: JSON.stringify({
|
|
39290
|
-
model: opts.model ?? DEFAULT_PROBE_MODEL2,
|
|
39291
|
-
max_tokens: 1,
|
|
39292
|
-
messages: [{ role: "user", content: "hi" }]
|
|
39293
|
-
}),
|
|
39294
|
-
signal: controller.signal
|
|
39295
|
-
});
|
|
39296
|
-
} catch (err) {
|
|
39297
|
-
const msg = err?.message ?? String(err);
|
|
39298
|
-
return { ok: false, reason: `request failed: ${msg}` };
|
|
39299
|
-
} finally {
|
|
39300
|
-
clearTimeout(timeout);
|
|
39301
|
-
}
|
|
39302
|
-
if (resp.status === 401 || resp.status === 403) {
|
|
39303
|
-
return { ok: false, reason: `auth rejected (HTTP ${resp.status})` };
|
|
39304
|
-
}
|
|
39305
|
-
const parsed = parseQuotaHeaders2(resp.headers);
|
|
39306
|
-
if (!parsed.ok && resp.status >= 400) {
|
|
39307
|
-
return { ok: false, reason: `HTTP ${resp.status}, ${parsed.reason}` };
|
|
39308
|
-
}
|
|
39309
|
-
return parsed;
|
|
39310
|
-
}
|
|
39311
|
-
function formatResetRelative2(target, now = new Date) {
|
|
39312
|
-
if (!target)
|
|
39313
|
-
return "\u2014";
|
|
39314
|
-
const deltaMs = target.getTime() - now.getTime();
|
|
39315
|
-
if (deltaMs <= 0)
|
|
39316
|
-
return "resets now";
|
|
39317
|
-
const totalMin = Math.round(deltaMs / 60000);
|
|
39318
|
-
if (totalMin < 60)
|
|
39319
|
-
return `resets in ${totalMin}m`;
|
|
39320
|
-
const hours = Math.floor(totalMin / 60);
|
|
39321
|
-
const mins = totalMin % 60;
|
|
39322
|
-
if (hours < 24)
|
|
39323
|
-
return mins > 0 ? `resets in ${hours}h ${mins}m` : `resets in ${hours}h`;
|
|
39324
|
-
const days = Math.floor(hours / 24);
|
|
39325
|
-
const remH = hours % 24;
|
|
39326
|
-
return remH > 0 ? `resets in ${days}d ${remH}h` : `resets in ${days}d`;
|
|
39327
|
-
}
|
|
39328
|
-
function formatQuotaBlock(q, now = new Date) {
|
|
39329
|
-
const lines = [];
|
|
39330
|
-
lines.push("<b>Claude plan quota</b>");
|
|
39331
|
-
lines.push("");
|
|
39332
|
-
lines.push(`<b>5h window</b> ${Math.round(q.fiveHourUtilizationPct)}% \u00b7 ${formatResetRelative2(q.fiveHourResetAt, now)}`);
|
|
39333
|
-
lines.push(`<b>7d window</b> ${Math.round(q.sevenDayUtilizationPct)}% \u00b7 ${formatResetRelative2(q.sevenDayResetAt, now)}`);
|
|
39334
|
-
if (q.representativeClaim) {
|
|
39335
|
-
lines.push("");
|
|
39336
|
-
lines.push(`<i>Binding window: ${q.representativeClaim.replace(/_/g, " ")}</i>`);
|
|
39337
|
-
}
|
|
39338
|
-
if (q.overageStatus && q.overageStatus !== "allowed") {
|
|
39339
|
-
const reason = q.overageDisabledReason ? ` (${q.overageDisabledReason})` : "";
|
|
39340
|
-
lines.push(`<i>Overage: ${q.overageStatus}${reason}</i>`);
|
|
39341
|
-
}
|
|
39342
|
-
return lines.join(`
|
|
39343
|
-
`);
|
|
39344
|
-
}
|
|
39345
|
-
function readAccountAccessToken(label, home2 = process.env.HOME ?? "/root") {
|
|
39346
|
-
const credPath = join13(home2, ".switchroom", "accounts", label, "credentials.json");
|
|
39347
|
-
if (!existsSync13(credPath))
|
|
39348
|
-
return null;
|
|
39349
|
-
try {
|
|
39350
|
-
const raw = readFileSync9(credPath, "utf-8");
|
|
39351
|
-
const parsed = JSON.parse(raw);
|
|
39352
|
-
const token = parsed.claudeAiOauth?.accessToken?.trim();
|
|
39353
|
-
return token && token.length > 0 ? token : null;
|
|
39354
|
-
} catch {
|
|
39355
|
-
return null;
|
|
39356
|
-
}
|
|
39357
|
-
}
|
|
39358
|
-
var ACCOUNT_QUOTA_CACHE_TTL_MS2 = 5 * 60000;
|
|
39359
|
-
var accountQuotaCache2 = new Map;
|
|
39360
|
-
async function fetchAccountQuota(label, opts = {}) {
|
|
39361
|
-
const now = opts.now?.() ?? Date.now();
|
|
39362
|
-
if (!opts.force) {
|
|
39363
|
-
const cached = accountQuotaCache2.get(label);
|
|
39364
|
-
if (cached && now - cached.fetchedAt < ACCOUNT_QUOTA_CACHE_TTL_MS2) {
|
|
39365
|
-
return cached.result;
|
|
39366
|
-
}
|
|
39367
|
-
}
|
|
39368
|
-
const token = readAccountAccessToken(label, opts.home);
|
|
39369
|
-
if (!token) {
|
|
39370
|
-
const result2 = {
|
|
39371
|
-
ok: false,
|
|
39372
|
-
reason: "no credentials.json or accessToken for account"
|
|
39373
|
-
};
|
|
39374
|
-
accountQuotaCache2.set(label, { fetchedAt: now, result: result2 });
|
|
39375
|
-
return result2;
|
|
39376
|
-
}
|
|
39377
|
-
const result = await fetchQuota2({
|
|
39378
|
-
accessToken: token,
|
|
39379
|
-
fetchImpl: opts.fetchImpl,
|
|
39380
|
-
timeoutMs: opts.timeoutMs
|
|
39381
|
-
});
|
|
39382
|
-
accountQuotaCache2.set(label, { fetchedAt: now, result });
|
|
39383
|
-
return result;
|
|
39384
|
-
}
|
|
39385
|
-
|
|
39386
39227
|
// gateway/restart-watchdog.ts
|
|
39387
39228
|
function parseSystemdShowOutput(raw) {
|
|
39388
39229
|
const lines = raw.split(/\r?\n/);
|
|
@@ -40278,8 +40119,8 @@ function isTurnFlushSafetyEnabled(env = process.env) {
|
|
|
40278
40119
|
}
|
|
40279
40120
|
|
|
40280
40121
|
// handoff-continuity.ts
|
|
40281
|
-
import { readFileSync as
|
|
40282
|
-
import { dirname as dirname7, join as
|
|
40122
|
+
import { readFileSync as readFileSync9, unlinkSync as unlinkSync2, existsSync as existsSync13, writeFileSync as writeFileSync6, renameSync as renameSync2 } from "node:fs";
|
|
40123
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
40283
40124
|
var TOPIC_DISPLAY_MAX = 117;
|
|
40284
40125
|
var HANDOFF_TOPIC_FILENAME = ".handoff-topic";
|
|
40285
40126
|
var LAST_TURN_SUMMARY_FILENAME = ".last-turn-summary";
|
|
@@ -40290,12 +40131,12 @@ function resolveAgentDirFromEnv() {
|
|
|
40290
40131
|
return dirname7(state3);
|
|
40291
40132
|
}
|
|
40292
40133
|
function readHandoffTopic(agentDir) {
|
|
40293
|
-
const p =
|
|
40294
|
-
if (!
|
|
40134
|
+
const p = join13(agentDir, HANDOFF_TOPIC_FILENAME);
|
|
40135
|
+
if (!existsSync13(p))
|
|
40295
40136
|
return null;
|
|
40296
40137
|
let raw;
|
|
40297
40138
|
try {
|
|
40298
|
-
raw =
|
|
40139
|
+
raw = readFileSync9(p, "utf-8");
|
|
40299
40140
|
} catch {
|
|
40300
40141
|
return null;
|
|
40301
40142
|
}
|
|
@@ -40309,12 +40150,12 @@ function readHandoffTopic(agentDir) {
|
|
|
40309
40150
|
return topic;
|
|
40310
40151
|
}
|
|
40311
40152
|
function readLastTurnSummary(agentDir) {
|
|
40312
|
-
const p =
|
|
40313
|
-
if (!
|
|
40153
|
+
const p = join13(agentDir, LAST_TURN_SUMMARY_FILENAME);
|
|
40154
|
+
if (!existsSync13(p))
|
|
40314
40155
|
return null;
|
|
40315
40156
|
let raw;
|
|
40316
40157
|
try {
|
|
40317
|
-
raw =
|
|
40158
|
+
raw = readFileSync9(p, "utf-8");
|
|
40318
40159
|
} catch {
|
|
40319
40160
|
return null;
|
|
40320
40161
|
}
|
|
@@ -40329,8 +40170,8 @@ function readLastTurnSummary(agentDir) {
|
|
|
40329
40170
|
}
|
|
40330
40171
|
function consumeHandoffTopic(agentDir) {
|
|
40331
40172
|
const primary = readHandoffTopic(agentDir);
|
|
40332
|
-
const primaryPath =
|
|
40333
|
-
const fallbackPath =
|
|
40173
|
+
const primaryPath = join13(agentDir, HANDOFF_TOPIC_FILENAME);
|
|
40174
|
+
const fallbackPath = join13(agentDir, LAST_TURN_SUMMARY_FILENAME);
|
|
40334
40175
|
const removeFallback = () => {
|
|
40335
40176
|
try {
|
|
40336
40177
|
unlinkSync2(fallbackPath);
|
|
@@ -40383,19 +40224,19 @@ function escapeMarkdownV2(s) {
|
|
|
40383
40224
|
}
|
|
40384
40225
|
|
|
40385
40226
|
// active-reactions.ts
|
|
40386
|
-
import { readFileSync as
|
|
40387
|
-
import { join as
|
|
40227
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync3, existsSync as existsSync14, unlinkSync as unlinkSync3 } from "node:fs";
|
|
40228
|
+
import { join as join14 } from "node:path";
|
|
40388
40229
|
var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
|
|
40389
40230
|
function reactionsPath(agentDir) {
|
|
40390
|
-
return
|
|
40231
|
+
return join14(agentDir, ACTIVE_REACTIONS_FILENAME);
|
|
40391
40232
|
}
|
|
40392
40233
|
function readActiveReactions(agentDir) {
|
|
40393
40234
|
const p = reactionsPath(agentDir);
|
|
40394
|
-
if (!
|
|
40235
|
+
if (!existsSync14(p))
|
|
40395
40236
|
return [];
|
|
40396
40237
|
let raw;
|
|
40397
40238
|
try {
|
|
40398
|
-
raw =
|
|
40239
|
+
raw = readFileSync10(p, "utf-8");
|
|
40399
40240
|
} catch {
|
|
40400
40241
|
return [];
|
|
40401
40242
|
}
|
|
@@ -40451,19 +40292,19 @@ function clearActiveReactions(agentDir) {
|
|
|
40451
40292
|
}
|
|
40452
40293
|
|
|
40453
40294
|
// active-reactions.ts
|
|
40454
|
-
import { readFileSync as
|
|
40455
|
-
import { join as
|
|
40295
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync4, existsSync as existsSync15, unlinkSync as unlinkSync4 } from "node:fs";
|
|
40296
|
+
import { join as join15 } from "node:path";
|
|
40456
40297
|
var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
|
|
40457
40298
|
function reactionsPath2(agentDir) {
|
|
40458
|
-
return
|
|
40299
|
+
return join15(agentDir, ACTIVE_REACTIONS_FILENAME2);
|
|
40459
40300
|
}
|
|
40460
40301
|
function readActiveReactions2(agentDir) {
|
|
40461
40302
|
const p = reactionsPath2(agentDir);
|
|
40462
|
-
if (!
|
|
40303
|
+
if (!existsSync15(p))
|
|
40463
40304
|
return [];
|
|
40464
40305
|
let raw;
|
|
40465
40306
|
try {
|
|
40466
|
-
raw =
|
|
40307
|
+
raw = readFileSync11(p, "utf-8");
|
|
40467
40308
|
} catch {
|
|
40468
40309
|
return [];
|
|
40469
40310
|
}
|
|
@@ -41321,6 +41162,156 @@ async function approvalRecord(args, opts) {
|
|
|
41321
41162
|
return r.resp.decision_id;
|
|
41322
41163
|
}
|
|
41323
41164
|
|
|
41165
|
+
// quota-check.ts
|
|
41166
|
+
import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
|
|
41167
|
+
import { join as join17 } from "path";
|
|
41168
|
+
var OAUTH_BETA2 = "oauth-2025-04-20";
|
|
41169
|
+
var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
|
|
41170
|
+
var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
|
|
41171
|
+
function readOauthToken2(claudeConfigDir) {
|
|
41172
|
+
const tokenFile = join17(claudeConfigDir, ".oauth-token");
|
|
41173
|
+
if (!existsSync17(tokenFile))
|
|
41174
|
+
return null;
|
|
41175
|
+
try {
|
|
41176
|
+
const raw = readFileSync13(tokenFile, "utf-8").trim();
|
|
41177
|
+
return raw.length > 0 ? raw : null;
|
|
41178
|
+
} catch {
|
|
41179
|
+
return null;
|
|
41180
|
+
}
|
|
41181
|
+
}
|
|
41182
|
+
function parseFloatHeader2(headers, name) {
|
|
41183
|
+
const v = headers.get(name);
|
|
41184
|
+
if (v == null || v.trim().length === 0)
|
|
41185
|
+
return null;
|
|
41186
|
+
const n = Number(v);
|
|
41187
|
+
return Number.isFinite(n) ? n : null;
|
|
41188
|
+
}
|
|
41189
|
+
function parseEpochHeader2(headers, name) {
|
|
41190
|
+
const v = headers.get(name);
|
|
41191
|
+
if (v == null)
|
|
41192
|
+
return null;
|
|
41193
|
+
const n = Number(v);
|
|
41194
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
41195
|
+
return null;
|
|
41196
|
+
return new Date(n * 1000);
|
|
41197
|
+
}
|
|
41198
|
+
function parseQuotaHeaders2(headers) {
|
|
41199
|
+
const fiveHour = parseFloatHeader2(headers, "anthropic-ratelimit-unified-5h-utilization");
|
|
41200
|
+
const sevenDay = parseFloatHeader2(headers, "anthropic-ratelimit-unified-7d-utilization");
|
|
41201
|
+
if (fiveHour == null && sevenDay == null) {
|
|
41202
|
+
return {
|
|
41203
|
+
ok: false,
|
|
41204
|
+
reason: "no unified rate-limit headers in response (API token, not OAuth?)"
|
|
41205
|
+
};
|
|
41206
|
+
}
|
|
41207
|
+
return {
|
|
41208
|
+
ok: true,
|
|
41209
|
+
data: {
|
|
41210
|
+
fiveHourUtilizationPct: (fiveHour ?? 0) * 100,
|
|
41211
|
+
sevenDayUtilizationPct: (sevenDay ?? 0) * 100,
|
|
41212
|
+
fiveHourResetAt: parseEpochHeader2(headers, "anthropic-ratelimit-unified-5h-reset"),
|
|
41213
|
+
sevenDayResetAt: parseEpochHeader2(headers, "anthropic-ratelimit-unified-7d-reset"),
|
|
41214
|
+
representativeClaim: headers.get("anthropic-ratelimit-unified-representative-claim"),
|
|
41215
|
+
overageStatus: headers.get("anthropic-ratelimit-unified-overage-status"),
|
|
41216
|
+
overageDisabledReason: headers.get("anthropic-ratelimit-unified-overage-disabled-reason")
|
|
41217
|
+
}
|
|
41218
|
+
};
|
|
41219
|
+
}
|
|
41220
|
+
async function fetchQuota2(opts) {
|
|
41221
|
+
let token;
|
|
41222
|
+
if (opts.accessToken && opts.claudeConfigDir) {
|
|
41223
|
+
return {
|
|
41224
|
+
ok: false,
|
|
41225
|
+
reason: "pass only one of `accessToken` or `claudeConfigDir`, not both"
|
|
41226
|
+
};
|
|
41227
|
+
}
|
|
41228
|
+
if (opts.accessToken) {
|
|
41229
|
+
token = opts.accessToken.trim().length > 0 ? opts.accessToken : null;
|
|
41230
|
+
} else if (opts.claudeConfigDir) {
|
|
41231
|
+
token = readOauthToken2(opts.claudeConfigDir);
|
|
41232
|
+
} else {
|
|
41233
|
+
return {
|
|
41234
|
+
ok: false,
|
|
41235
|
+
reason: "fetchQuota requires `accessToken` or `claudeConfigDir`"
|
|
41236
|
+
};
|
|
41237
|
+
}
|
|
41238
|
+
if (!token) {
|
|
41239
|
+
return { ok: false, reason: "no OAuth token at .oauth-token" };
|
|
41240
|
+
}
|
|
41241
|
+
const controller = new AbortController;
|
|
41242
|
+
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 1e4);
|
|
41243
|
+
const fetchFn = opts.fetchImpl ?? fetch;
|
|
41244
|
+
let resp;
|
|
41245
|
+
try {
|
|
41246
|
+
resp = await fetchFn("https://api.anthropic.com/v1/messages", {
|
|
41247
|
+
method: "POST",
|
|
41248
|
+
headers: {
|
|
41249
|
+
"anthropic-version": "2023-06-01",
|
|
41250
|
+
"anthropic-beta": OAUTH_BETA2,
|
|
41251
|
+
authorization: `Bearer ${token}`,
|
|
41252
|
+
"x-app": "cli",
|
|
41253
|
+
"user-agent": DEFAULT_USER_AGENT2,
|
|
41254
|
+
"content-type": "application/json"
|
|
41255
|
+
},
|
|
41256
|
+
body: JSON.stringify({
|
|
41257
|
+
model: opts.model ?? DEFAULT_PROBE_MODEL2,
|
|
41258
|
+
max_tokens: 1,
|
|
41259
|
+
messages: [{ role: "user", content: "hi" }]
|
|
41260
|
+
}),
|
|
41261
|
+
signal: controller.signal
|
|
41262
|
+
});
|
|
41263
|
+
} catch (err) {
|
|
41264
|
+
const msg = err?.message ?? String(err);
|
|
41265
|
+
return { ok: false, reason: `request failed: ${msg}` };
|
|
41266
|
+
} finally {
|
|
41267
|
+
clearTimeout(timeout);
|
|
41268
|
+
}
|
|
41269
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
41270
|
+
return { ok: false, reason: `auth rejected (HTTP ${resp.status})` };
|
|
41271
|
+
}
|
|
41272
|
+
const parsed = parseQuotaHeaders2(resp.headers);
|
|
41273
|
+
if (!parsed.ok && resp.status >= 400) {
|
|
41274
|
+
return { ok: false, reason: `HTTP ${resp.status}, ${parsed.reason}` };
|
|
41275
|
+
}
|
|
41276
|
+
return parsed;
|
|
41277
|
+
}
|
|
41278
|
+
function formatResetRelative2(target, now = new Date) {
|
|
41279
|
+
if (!target)
|
|
41280
|
+
return "\u2014";
|
|
41281
|
+
const deltaMs = target.getTime() - now.getTime();
|
|
41282
|
+
if (deltaMs <= 0)
|
|
41283
|
+
return "resets now";
|
|
41284
|
+
const totalMin = Math.round(deltaMs / 60000);
|
|
41285
|
+
if (totalMin < 60)
|
|
41286
|
+
return `resets in ${totalMin}m`;
|
|
41287
|
+
const hours = Math.floor(totalMin / 60);
|
|
41288
|
+
const mins = totalMin % 60;
|
|
41289
|
+
if (hours < 24)
|
|
41290
|
+
return mins > 0 ? `resets in ${hours}h ${mins}m` : `resets in ${hours}h`;
|
|
41291
|
+
const days = Math.floor(hours / 24);
|
|
41292
|
+
const remH = hours % 24;
|
|
41293
|
+
return remH > 0 ? `resets in ${days}d ${remH}h` : `resets in ${days}d`;
|
|
41294
|
+
}
|
|
41295
|
+
function formatQuotaBlock(q, now = new Date) {
|
|
41296
|
+
const lines = [];
|
|
41297
|
+
lines.push("<b>Claude plan quota</b>");
|
|
41298
|
+
lines.push("");
|
|
41299
|
+
lines.push(`<b>5h window</b> ${Math.round(q.fiveHourUtilizationPct)}% \u00b7 ${formatResetRelative2(q.fiveHourResetAt, now)}`);
|
|
41300
|
+
lines.push(`<b>7d window</b> ${Math.round(q.sevenDayUtilizationPct)}% \u00b7 ${formatResetRelative2(q.sevenDayResetAt, now)}`);
|
|
41301
|
+
if (q.representativeClaim) {
|
|
41302
|
+
lines.push("");
|
|
41303
|
+
lines.push(`<i>Binding window: ${q.representativeClaim.replace(/_/g, " ")}</i>`);
|
|
41304
|
+
}
|
|
41305
|
+
if (q.overageStatus && q.overageStatus !== "allowed") {
|
|
41306
|
+
const reason = q.overageDisabledReason ? ` (${q.overageDisabledReason})` : "";
|
|
41307
|
+
lines.push(`<i>Overage: ${q.overageStatus}${reason}</i>`);
|
|
41308
|
+
}
|
|
41309
|
+
return lines.join(`
|
|
41310
|
+
`);
|
|
41311
|
+
}
|
|
41312
|
+
var ACCOUNT_QUOTA_CACHE_TTL_MS2 = 5 * 60000;
|
|
41313
|
+
var accountQuotaCache2 = new Map;
|
|
41314
|
+
|
|
41324
41315
|
// auto-fallback.ts
|
|
41325
41316
|
var DEFAULT_FALLBACK_COOLDOWN_MS = 2 * 60000;
|
|
41326
41317
|
var LOCKOUT_FILE = "auto-fallback-lockout.json";
|
|
@@ -42419,7 +42410,7 @@ function isHostdEnabled() {
|
|
|
42419
42410
|
return _hostdEnabled;
|
|
42420
42411
|
try {
|
|
42421
42412
|
const cfg = loadConfig();
|
|
42422
|
-
_hostdEnabled = cfg.host_control?.enabled
|
|
42413
|
+
_hostdEnabled = cfg.host_control?.enabled !== false;
|
|
42423
42414
|
} catch {
|
|
42424
42415
|
_hostdEnabled = false;
|
|
42425
42416
|
}
|
|
@@ -46241,10 +46232,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
46241
46232
|
}
|
|
46242
46233
|
|
|
46243
46234
|
// ../src/build-info.ts
|
|
46244
|
-
var VERSION = "0.11.
|
|
46245
|
-
var COMMIT_SHA = "
|
|
46246
|
-
var COMMIT_DATE = "2026-05-
|
|
46247
|
-
var LATEST_PR =
|
|
46235
|
+
var VERSION = "0.11.1";
|
|
46236
|
+
var COMMIT_SHA = "f5d84dfb";
|
|
46237
|
+
var COMMIT_DATE = "2026-05-15T19:01:48+10:00";
|
|
46238
|
+
var LATEST_PR = 1342;
|
|
46248
46239
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
46249
46240
|
|
|
46250
46241
|
// gateway/boot-version.ts
|
|
@@ -51962,7 +51953,11 @@ async function doFireFleetAutoFallback(triggerAgent) {
|
|
|
51962
51953
|
return false;
|
|
51963
51954
|
}
|
|
51964
51955
|
const state3 = await client3.listState();
|
|
51965
|
-
const
|
|
51956
|
+
const probeResp = state3.accounts.length > 0 ? await client3.probeQuota(state3.accounts.map((a) => a.label)).catch(() => ({ results: [] })) : { results: [] };
|
|
51957
|
+
const quotas = state3.accounts.map((a) => {
|
|
51958
|
+
const hit = probeResp.results.find((r) => r.label === a.label);
|
|
51959
|
+
return hit?.result ?? { ok: false, reason: "broker returned no result for account" };
|
|
51960
|
+
});
|
|
51966
51961
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
51967
51962
|
const outcome = await runFleetAutoFallback({
|
|
51968
51963
|
state: state3,
|
|
@@ -52087,7 +52082,20 @@ Send <code>/auth cancel</code> to abort.`, { html: true });
|
|
|
52087
52082
|
isAdmin: isAdmin2,
|
|
52088
52083
|
client: client3,
|
|
52089
52084
|
chatId,
|
|
52090
|
-
liveQuotas: async (accounts) =>
|
|
52085
|
+
liveQuotas: async (accounts) => {
|
|
52086
|
+
try {
|
|
52087
|
+
const { results } = await client3.probeQuota(accounts.map((a) => a.label));
|
|
52088
|
+
return accounts.map((a) => {
|
|
52089
|
+
const hit = results.find((r) => r.label === a.label);
|
|
52090
|
+
if (!hit)
|
|
52091
|
+
return { ok: false, reason: "broker returned no result for account" };
|
|
52092
|
+
return hit.result;
|
|
52093
|
+
});
|
|
52094
|
+
} catch (err) {
|
|
52095
|
+
const reason = `broker probe-quota failed: ${err?.message ?? String(err)}`;
|
|
52096
|
+
return accounts.map(() => ({ ok: false, reason }));
|
|
52097
|
+
}
|
|
52098
|
+
},
|
|
52091
52099
|
tz: process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ
|
|
52092
52100
|
});
|
|
52093
52101
|
if (reply.keyboard && reply.keyboard.length > 0) {
|
|
@@ -53095,7 +53103,11 @@ async function handleAuthDashboardCallback(ctx) {
|
|
|
53095
53103
|
return;
|
|
53096
53104
|
}
|
|
53097
53105
|
const state3 = await client3.listState();
|
|
53098
|
-
const
|
|
53106
|
+
const probeResp = state3.accounts.length > 0 ? await client3.probeQuota(state3.accounts.map((a) => a.label)).catch(() => ({ results: [] })) : { results: [] };
|
|
53107
|
+
const quotas = state3.accounts.map((a) => {
|
|
53108
|
+
const hit = probeResp.results.find((r) => r.label === a.label);
|
|
53109
|
+
return hit?.result ?? { ok: false, reason: "broker returned no result for account" };
|
|
53110
|
+
});
|
|
53099
53111
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
53100
53112
|
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState3, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
|
|
53101
53113
|
const snapshots = buildSnapshotsFromState3(state3, quotas);
|
|
@@ -53488,7 +53500,11 @@ bot.command("usage", async (ctx) => {
|
|
|
53488
53500
|
if (client3) {
|
|
53489
53501
|
const state3 = await client3.listState();
|
|
53490
53502
|
if (state3.accounts.length > 0) {
|
|
53491
|
-
const
|
|
53503
|
+
const probeResp = await client3.probeQuota(state3.accounts.map((a) => a.label)).catch(() => ({ results: [] }));
|
|
53504
|
+
const quotas = state3.accounts.map((a) => {
|
|
53505
|
+
const hit = probeResp.results.find((r) => r.label === a.label);
|
|
53506
|
+
return hit?.result ?? { ok: false, reason: "broker returned no result for account" };
|
|
53507
|
+
});
|
|
53492
53508
|
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState3 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
|
|
53493
53509
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
53494
53510
|
const snapshots = buildSnapshotsFromState3(state3, quotas);
|
|
@@ -30,6 +30,8 @@ export function createAuthBrokerClient(): {
|
|
|
30
30
|
refreshAccount: (label: string) => broker.refreshAccount(label),
|
|
31
31
|
setOverride: (agent: string, account: string | null) =>
|
|
32
32
|
broker.setOverride(agent, account),
|
|
33
|
+
probeQuota: (accounts: readonly string[], timeoutMs?: number) =>
|
|
34
|
+
broker.probeQuota(accounts, timeoutMs),
|
|
33
35
|
}
|
|
34
36
|
return { client, close: () => broker.close() }
|
|
35
37
|
}
|