switchroom 0.14.93 → 0.15.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 +22 -1
- package/dist/auth-broker/index.js +84 -13
- package/dist/cli/drive-write-pretool.mjs +23 -2
- package/dist/cli/switchroom.js +39 -6
- package/dist/cli/ui/index.html +16 -5
- package/package.json +1 -1
- package/telegram-plugin/auth-snapshot-format.ts +9 -0
- package/telegram-plugin/auto-fallback-fleet.ts +59 -0
- package/telegram-plugin/dist/gateway/gateway.js +225 -20
- package/telegram-plugin/gateway/auth-broker-client.ts +2 -0
- package/telegram-plugin/gateway/auth-command.ts +35 -2
- package/telegram-plugin/gateway/gateway.ts +203 -18
- package/telegram-plugin/quota-watch.ts +141 -3
- package/telegram-plugin/tests/auth-quota-util-cell.test.ts +23 -0
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +71 -0
- package/telegram-plugin/tests/quota-watch.test.ts +266 -0
|
@@ -13283,6 +13283,13 @@ var ProbeQuotaRequestSchema = exports_external.object({
|
|
|
13283
13283
|
accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
13284
13284
|
timeoutMs: exports_external.number().int().positive().max(60000).optional()
|
|
13285
13285
|
});
|
|
13286
|
+
var ClaimNotificationRequestSchema = exports_external.object({
|
|
13287
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
13288
|
+
op: exports_external.literal("claim-notification"),
|
|
13289
|
+
id: exports_external.string().min(1),
|
|
13290
|
+
key: exports_external.string().min(1).max(512),
|
|
13291
|
+
windowMs: exports_external.number().int().positive().max(86400000)
|
|
13292
|
+
});
|
|
13286
13293
|
var RequestSchema2 = exports_external.discriminatedUnion("op", [
|
|
13287
13294
|
GetCredentialsRequestSchema,
|
|
13288
13295
|
ListStateRequestSchema,
|
|
@@ -13294,7 +13301,8 @@ var RequestSchema2 = exports_external.discriminatedUnion("op", [
|
|
|
13294
13301
|
SetOverrideRequestSchema,
|
|
13295
13302
|
ListGoogleAccountsRequestSchema,
|
|
13296
13303
|
ListMicrosoftAccountsRequestSchema,
|
|
13297
|
-
ProbeQuotaRequestSchema
|
|
13304
|
+
ProbeQuotaRequestSchema,
|
|
13305
|
+
ClaimNotificationRequestSchema
|
|
13298
13306
|
]);
|
|
13299
13307
|
var GetCredentialsDataSchema = exports_external.object({
|
|
13300
13308
|
account: exports_external.string(),
|
|
@@ -13350,6 +13358,9 @@ var SetOverrideDataSchema = exports_external.object({
|
|
|
13350
13358
|
agent: exports_external.string(),
|
|
13351
13359
|
account: exports_external.string().nullable()
|
|
13352
13360
|
});
|
|
13361
|
+
var ClaimNotificationDataSchema = exports_external.object({
|
|
13362
|
+
granted: exports_external.boolean()
|
|
13363
|
+
});
|
|
13353
13364
|
var GoogleAccountStateSchema = exports_external.object({
|
|
13354
13365
|
account: exports_external.string(),
|
|
13355
13366
|
expiresAt: exports_external.number(),
|
|
@@ -13556,6 +13567,16 @@ class AuthBrokerClient {
|
|
|
13556
13567
|
const data = await this.send(req);
|
|
13557
13568
|
return data;
|
|
13558
13569
|
}
|
|
13570
|
+
async claimNotification(key, windowMs) {
|
|
13571
|
+
const data = await this.send({
|
|
13572
|
+
v: PROTOCOL_VERSION,
|
|
13573
|
+
id: randomUUID(),
|
|
13574
|
+
op: "claim-notification",
|
|
13575
|
+
key,
|
|
13576
|
+
windowMs
|
|
13577
|
+
});
|
|
13578
|
+
return data;
|
|
13579
|
+
}
|
|
13559
13580
|
async refreshAccount(account) {
|
|
13560
13581
|
const data = await this.send({
|
|
13561
13582
|
v: PROTOCOL_VERSION,
|
|
@@ -13367,6 +13367,13 @@ var ProbeQuotaRequestSchema = exports_external.object({
|
|
|
13367
13367
|
accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
13368
13368
|
timeoutMs: exports_external.number().int().positive().max(60000).optional()
|
|
13369
13369
|
});
|
|
13370
|
+
var ClaimNotificationRequestSchema = exports_external.object({
|
|
13371
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
13372
|
+
op: exports_external.literal("claim-notification"),
|
|
13373
|
+
id: exports_external.string().min(1),
|
|
13374
|
+
key: exports_external.string().min(1).max(512),
|
|
13375
|
+
windowMs: exports_external.number().int().positive().max(86400000)
|
|
13376
|
+
});
|
|
13370
13377
|
var RequestSchema = exports_external.discriminatedUnion("op", [
|
|
13371
13378
|
GetCredentialsRequestSchema,
|
|
13372
13379
|
ListStateRequestSchema,
|
|
@@ -13378,7 +13385,8 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
|
|
|
13378
13385
|
SetOverrideRequestSchema,
|
|
13379
13386
|
ListGoogleAccountsRequestSchema,
|
|
13380
13387
|
ListMicrosoftAccountsRequestSchema,
|
|
13381
|
-
ProbeQuotaRequestSchema
|
|
13388
|
+
ProbeQuotaRequestSchema,
|
|
13389
|
+
ClaimNotificationRequestSchema
|
|
13382
13390
|
]);
|
|
13383
13391
|
var GetCredentialsDataSchema = exports_external.object({
|
|
13384
13392
|
account: exports_external.string(),
|
|
@@ -13434,6 +13442,9 @@ var SetOverrideDataSchema = exports_external.object({
|
|
|
13434
13442
|
agent: exports_external.string(),
|
|
13435
13443
|
account: exports_external.string().nullable()
|
|
13436
13444
|
});
|
|
13445
|
+
var ClaimNotificationDataSchema = exports_external.object({
|
|
13446
|
+
granted: exports_external.boolean()
|
|
13447
|
+
});
|
|
13437
13448
|
var GoogleAccountStateSchema = exports_external.object({
|
|
13438
13449
|
account: exports_external.string(),
|
|
13439
13450
|
expiresAt: exports_external.number(),
|
|
@@ -13520,6 +13531,7 @@ var MARK_EXHAUSTED_DEFAULT_MS = 5 * 60 * 60 * 1000;
|
|
|
13520
13531
|
var AUDIT_ROTATE_BYTES = 10 * 1024 * 1024;
|
|
13521
13532
|
var AUDIT_KEEP = 5;
|
|
13522
13533
|
var AUDIT_LINE_MAX = 4000;
|
|
13534
|
+
var NOTIFICATION_CLAIM_MAX_AGE_MS = 86400000;
|
|
13523
13535
|
function sha256Hex(content) {
|
|
13524
13536
|
return createHash2("sha256").update(content).digest("hex");
|
|
13525
13537
|
}
|
|
@@ -13560,6 +13572,7 @@ class AuthBroker {
|
|
|
13560
13572
|
listeners = new Map;
|
|
13561
13573
|
refreshTimer = null;
|
|
13562
13574
|
consumerProbeTimer = null;
|
|
13575
|
+
fleetProbeTimer = null;
|
|
13563
13576
|
fetchQuotaImpl;
|
|
13564
13577
|
stateDir;
|
|
13565
13578
|
socketRoot;
|
|
@@ -13572,6 +13585,7 @@ class AuthBroker {
|
|
|
13572
13585
|
lastQuotaCache = {};
|
|
13573
13586
|
shaIndex = {};
|
|
13574
13587
|
thresholdViolations = {};
|
|
13588
|
+
notificationClaims = {};
|
|
13575
13589
|
lastWrittenExpiresAt = new Map;
|
|
13576
13590
|
refreshInFlight = new Set;
|
|
13577
13591
|
consumerLastSeen = {};
|
|
@@ -13641,6 +13655,17 @@ class AuthBroker {
|
|
|
13641
13655
|
}, probeMs);
|
|
13642
13656
|
this.consumerProbeTimer.unref();
|
|
13643
13657
|
}
|
|
13658
|
+
if (probeMs > 0 && process.env.SWITCHROOM_DISABLE_FLEET_QUOTA_PROBE !== "1") {
|
|
13659
|
+
this.fleetQuotaProbeTick().catch((err) => {
|
|
13660
|
+
this.logErr(`fleet-quota-probe (boot) threw: ${err.message}`);
|
|
13661
|
+
});
|
|
13662
|
+
this.fleetProbeTimer = setInterval(() => {
|
|
13663
|
+
this.fleetQuotaProbeTick().catch((err) => {
|
|
13664
|
+
this.logErr(`fleet-quota-probe threw: ${err.message}`);
|
|
13665
|
+
});
|
|
13666
|
+
}, probeMs);
|
|
13667
|
+
this.fleetProbeTimer.unref();
|
|
13668
|
+
}
|
|
13644
13669
|
}
|
|
13645
13670
|
const fanned = this.fanoutAll();
|
|
13646
13671
|
if (fanned.length > 0) {
|
|
@@ -13671,6 +13696,10 @@ class AuthBroker {
|
|
|
13671
13696
|
clearInterval(this.consumerProbeTimer);
|
|
13672
13697
|
this.consumerProbeTimer = null;
|
|
13673
13698
|
}
|
|
13699
|
+
if (this.fleetProbeTimer) {
|
|
13700
|
+
clearInterval(this.fleetProbeTimer);
|
|
13701
|
+
this.fleetProbeTimer = null;
|
|
13702
|
+
}
|
|
13674
13703
|
for (const [sock, lis] of this.listeners) {
|
|
13675
13704
|
try {
|
|
13676
13705
|
lis.server.close();
|
|
@@ -13955,6 +13984,9 @@ class AuthBroker {
|
|
|
13955
13984
|
case "probe-quota":
|
|
13956
13985
|
await this.opProbeQuota(socket, reqId, identity2, req.accounts, req.timeoutMs);
|
|
13957
13986
|
break;
|
|
13987
|
+
case "claim-notification":
|
|
13988
|
+
this.opClaimNotification(socket, reqId, identity2, req.key, req.windowMs);
|
|
13989
|
+
break;
|
|
13958
13990
|
}
|
|
13959
13991
|
} catch (err) {
|
|
13960
13992
|
socket.write(encodeError(reqId, "INTERNAL", err.message));
|
|
@@ -14096,22 +14128,42 @@ class AuthBroker {
|
|
|
14096
14128
|
ok: result.ok,
|
|
14097
14129
|
error: result.ok ? undefined : result.reason
|
|
14098
14130
|
});
|
|
14099
|
-
if (result.ok)
|
|
14100
|
-
this.
|
|
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
|
-
}
|
|
14131
|
+
if (result.ok)
|
|
14132
|
+
this.cacheQuotaSnapshot(label, result);
|
|
14111
14133
|
return { label, result };
|
|
14112
14134
|
}));
|
|
14113
14135
|
socket.write(encodeSuccess(id, { results }));
|
|
14114
14136
|
}
|
|
14137
|
+
cacheQuotaSnapshot(label, result) {
|
|
14138
|
+
if (!result.ok)
|
|
14139
|
+
return;
|
|
14140
|
+
this.lastQuotaCache[label] = {
|
|
14141
|
+
fiveHourUtilizationPct: result.data.fiveHourUtilizationPct,
|
|
14142
|
+
sevenDayUtilizationPct: result.data.sevenDayUtilizationPct,
|
|
14143
|
+
fiveHourResetAt: result.data.fiveHourResetAt?.toISOString() ?? null,
|
|
14144
|
+
sevenDayResetAt: result.data.sevenDayResetAt?.toISOString() ?? null,
|
|
14145
|
+
representativeClaim: result.data.representativeClaim,
|
|
14146
|
+
overageStatus: result.data.overageStatus,
|
|
14147
|
+
overageDisabledReason: result.data.overageDisabledReason,
|
|
14148
|
+
capturedAt: this.now()
|
|
14149
|
+
};
|
|
14150
|
+
}
|
|
14151
|
+
async fleetQuotaProbeTick() {
|
|
14152
|
+
for (const label of listAccounts(this.home)) {
|
|
14153
|
+
const creds = readAccountCredentials(label, this.home);
|
|
14154
|
+
const token = creds?.claudeAiOauth?.accessToken;
|
|
14155
|
+
if (!token)
|
|
14156
|
+
continue;
|
|
14157
|
+
let result;
|
|
14158
|
+
try {
|
|
14159
|
+
result = await this.fetchQuotaImpl({ accessToken: token });
|
|
14160
|
+
} catch (err) {
|
|
14161
|
+
this.logErr(`fleet-quota-probe ${label}: ${err.message}`);
|
|
14162
|
+
continue;
|
|
14163
|
+
}
|
|
14164
|
+
this.cacheQuotaSnapshot(label, result);
|
|
14165
|
+
}
|
|
14166
|
+
}
|
|
14115
14167
|
async consumerQuotaProbeTick() {
|
|
14116
14168
|
const accounts = Array.from(new Set((this.config.auth?.consumers ?? []).map((c) => c.account)));
|
|
14117
14169
|
for (const label of accounts) {
|
|
@@ -14175,6 +14227,21 @@ class AuthBroker {
|
|
|
14175
14227
|
this.audit({ op: "mark-exhausted", identity: identity2, account, ok: true });
|
|
14176
14228
|
socket.write(encodeSuccess(id, { account, rolled, rolledTo }));
|
|
14177
14229
|
}
|
|
14230
|
+
opClaimNotification(socket, id, identity2, key, windowMs) {
|
|
14231
|
+
const now = this.now();
|
|
14232
|
+
const prev = this.notificationClaims[key];
|
|
14233
|
+
const granted = prev === undefined || now - prev >= windowMs;
|
|
14234
|
+
if (granted) {
|
|
14235
|
+
this.notificationClaims[key] = now;
|
|
14236
|
+
for (const [k, ts] of Object.entries(this.notificationClaims)) {
|
|
14237
|
+
if (now - ts > NOTIFICATION_CLAIM_MAX_AGE_MS)
|
|
14238
|
+
delete this.notificationClaims[k];
|
|
14239
|
+
}
|
|
14240
|
+
this.persistNotificationClaims();
|
|
14241
|
+
this.audit({ op: "claim-notification", identity: identity2, account: key, ok: true });
|
|
14242
|
+
}
|
|
14243
|
+
socket.write(encodeSuccess(id, { granted }));
|
|
14244
|
+
}
|
|
14178
14245
|
async opRefreshAccount(socket, id, identity2, account) {
|
|
14179
14246
|
if (!this.isAdmin(identity2)) {
|
|
14180
14247
|
this.audit({ op: "refresh-account", identity: identity2, account, ok: false, error: "FORBIDDEN" });
|
|
@@ -14777,6 +14844,7 @@ class AuthBroker {
|
|
|
14777
14844
|
this.quota = this.readJson("quota.json") ?? {};
|
|
14778
14845
|
this.shaIndex = this.readJson("sha-index.json") ?? {};
|
|
14779
14846
|
this.thresholdViolations = this.readJson("threshold-violations.json") ?? {};
|
|
14847
|
+
this.notificationClaims = this.readJson("notification-claims.json") ?? {};
|
|
14780
14848
|
}
|
|
14781
14849
|
readJson(name) {
|
|
14782
14850
|
const p = join4(this.stateDir, name);
|
|
@@ -14791,6 +14859,9 @@ class AuthBroker {
|
|
|
14791
14859
|
persistQuota() {
|
|
14792
14860
|
atomicWriteJsonSync(join4(this.stateDir, "quota.json"), this.quota, 384);
|
|
14793
14861
|
}
|
|
14862
|
+
persistNotificationClaims() {
|
|
14863
|
+
atomicWriteJsonSync(join4(this.stateDir, "notification-claims.json"), this.notificationClaims, 384);
|
|
14864
|
+
}
|
|
14794
14865
|
persistShaIndex() {
|
|
14795
14866
|
atomicWriteJsonSync(join4(this.stateDir, "sha-index.json"), this.shaIndex, 384);
|
|
14796
14867
|
}
|
|
@@ -4000,7 +4000,7 @@ function decodeResponse(line) {
|
|
|
4000
4000
|
}
|
|
4001
4001
|
return ResponseSchema.parse(parsed);
|
|
4002
4002
|
}
|
|
4003
|
-
var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
|
|
4003
|
+
var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
|
|
4004
4004
|
var init_protocol = __esm(() => {
|
|
4005
4005
|
init_zod();
|
|
4006
4006
|
MAX_FRAME_BYTES = 64 * 1024;
|
|
@@ -4116,6 +4116,13 @@ var init_protocol = __esm(() => {
|
|
|
4116
4116
|
accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
4117
4117
|
timeoutMs: exports_external.number().int().positive().max(60000).optional()
|
|
4118
4118
|
});
|
|
4119
|
+
ClaimNotificationRequestSchema = exports_external.object({
|
|
4120
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
4121
|
+
op: exports_external.literal("claim-notification"),
|
|
4122
|
+
id: exports_external.string().min(1),
|
|
4123
|
+
key: exports_external.string().min(1).max(512),
|
|
4124
|
+
windowMs: exports_external.number().int().positive().max(86400000)
|
|
4125
|
+
});
|
|
4119
4126
|
RequestSchema = exports_external.discriminatedUnion("op", [
|
|
4120
4127
|
GetCredentialsRequestSchema,
|
|
4121
4128
|
ListStateRequestSchema,
|
|
@@ -4127,7 +4134,8 @@ var init_protocol = __esm(() => {
|
|
|
4127
4134
|
SetOverrideRequestSchema,
|
|
4128
4135
|
ListGoogleAccountsRequestSchema,
|
|
4129
4136
|
ListMicrosoftAccountsRequestSchema,
|
|
4130
|
-
ProbeQuotaRequestSchema
|
|
4137
|
+
ProbeQuotaRequestSchema,
|
|
4138
|
+
ClaimNotificationRequestSchema
|
|
4131
4139
|
]);
|
|
4132
4140
|
GetCredentialsDataSchema = exports_external.object({
|
|
4133
4141
|
account: exports_external.string(),
|
|
@@ -4183,6 +4191,9 @@ var init_protocol = __esm(() => {
|
|
|
4183
4191
|
agent: exports_external.string(),
|
|
4184
4192
|
account: exports_external.string().nullable()
|
|
4185
4193
|
});
|
|
4194
|
+
ClaimNotificationDataSchema = exports_external.object({
|
|
4195
|
+
granted: exports_external.boolean()
|
|
4196
|
+
});
|
|
4186
4197
|
GoogleAccountStateSchema = exports_external.object({
|
|
4187
4198
|
account: exports_external.string(),
|
|
4188
4199
|
expiresAt: exports_external.number(),
|
|
@@ -4364,6 +4375,16 @@ class AuthBrokerClient {
|
|
|
4364
4375
|
const data = await this.send(req);
|
|
4365
4376
|
return data;
|
|
4366
4377
|
}
|
|
4378
|
+
async claimNotification(key, windowMs) {
|
|
4379
|
+
const data = await this.send({
|
|
4380
|
+
v: PROTOCOL_VERSION,
|
|
4381
|
+
id: randomUUID(),
|
|
4382
|
+
op: "claim-notification",
|
|
4383
|
+
key,
|
|
4384
|
+
windowMs
|
|
4385
|
+
});
|
|
4386
|
+
return data;
|
|
4387
|
+
}
|
|
4367
4388
|
async refreshAccount(account) {
|
|
4368
4389
|
const data = await this.send({
|
|
4369
4390
|
v: PROTOCOL_VERSION,
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -25561,7 +25561,7 @@ function decodeResponse2(line) {
|
|
|
25561
25561
|
}
|
|
25562
25562
|
return ResponseSchema2.parse(parsed);
|
|
25563
25563
|
}
|
|
25564
|
-
var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
25564
|
+
var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
25565
25565
|
var init_protocol2 = __esm(() => {
|
|
25566
25566
|
init_zod();
|
|
25567
25567
|
MAX_FRAME_BYTES2 = 64 * 1024;
|
|
@@ -25677,6 +25677,13 @@ var init_protocol2 = __esm(() => {
|
|
|
25677
25677
|
accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
25678
25678
|
timeoutMs: exports_external.number().int().positive().max(60000).optional()
|
|
25679
25679
|
});
|
|
25680
|
+
ClaimNotificationRequestSchema = exports_external.object({
|
|
25681
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
25682
|
+
op: exports_external.literal("claim-notification"),
|
|
25683
|
+
id: exports_external.string().min(1),
|
|
25684
|
+
key: exports_external.string().min(1).max(512),
|
|
25685
|
+
windowMs: exports_external.number().int().positive().max(86400000)
|
|
25686
|
+
});
|
|
25680
25687
|
RequestSchema2 = exports_external.discriminatedUnion("op", [
|
|
25681
25688
|
GetCredentialsRequestSchema,
|
|
25682
25689
|
ListStateRequestSchema,
|
|
@@ -25688,7 +25695,8 @@ var init_protocol2 = __esm(() => {
|
|
|
25688
25695
|
SetOverrideRequestSchema,
|
|
25689
25696
|
ListGoogleAccountsRequestSchema,
|
|
25690
25697
|
ListMicrosoftAccountsRequestSchema,
|
|
25691
|
-
ProbeQuotaRequestSchema
|
|
25698
|
+
ProbeQuotaRequestSchema,
|
|
25699
|
+
ClaimNotificationRequestSchema
|
|
25692
25700
|
]);
|
|
25693
25701
|
GetCredentialsDataSchema = exports_external.object({
|
|
25694
25702
|
account: exports_external.string(),
|
|
@@ -25744,6 +25752,9 @@ var init_protocol2 = __esm(() => {
|
|
|
25744
25752
|
agent: exports_external.string(),
|
|
25745
25753
|
account: exports_external.string().nullable()
|
|
25746
25754
|
});
|
|
25755
|
+
ClaimNotificationDataSchema = exports_external.object({
|
|
25756
|
+
granted: exports_external.boolean()
|
|
25757
|
+
});
|
|
25747
25758
|
GoogleAccountStateSchema = exports_external.object({
|
|
25748
25759
|
account: exports_external.string(),
|
|
25749
25760
|
expiresAt: exports_external.number(),
|
|
@@ -25925,6 +25936,16 @@ class AuthBrokerClient {
|
|
|
25925
25936
|
const data = await this.send(req);
|
|
25926
25937
|
return data;
|
|
25927
25938
|
}
|
|
25939
|
+
async claimNotification(key, windowMs) {
|
|
25940
|
+
const data = await this.send({
|
|
25941
|
+
v: PROTOCOL_VERSION,
|
|
25942
|
+
id: randomUUID(),
|
|
25943
|
+
op: "claim-notification",
|
|
25944
|
+
key,
|
|
25945
|
+
windowMs
|
|
25946
|
+
});
|
|
25947
|
+
return data;
|
|
25948
|
+
}
|
|
25928
25949
|
async refreshAccount(account) {
|
|
25929
25950
|
const data = await this.send({
|
|
25930
25951
|
v: PROTOCOL_VERSION,
|
|
@@ -49937,8 +49958,8 @@ var {
|
|
|
49937
49958
|
} = import__.default;
|
|
49938
49959
|
|
|
49939
49960
|
// src/build-info.ts
|
|
49940
|
-
var VERSION = "0.
|
|
49941
|
-
var COMMIT_SHA = "
|
|
49961
|
+
var VERSION = "0.15.1";
|
|
49962
|
+
var COMMIT_SHA = "a93177c8";
|
|
49942
49963
|
|
|
49943
49964
|
// src/cli/agent.ts
|
|
49944
49965
|
init_source();
|
|
@@ -59358,15 +59379,27 @@ function formatQuotaReset(state) {
|
|
|
59358
59379
|
const mins = Math.floor(remainingMs % 3600000 / 60000);
|
|
59359
59380
|
return `${hours}h ${mins}m`;
|
|
59360
59381
|
}
|
|
59382
|
+
function formatQuotaUtilCell(a, now = Date.now()) {
|
|
59383
|
+
const lq = a.last_quota;
|
|
59384
|
+
if (!lq)
|
|
59385
|
+
return "no data";
|
|
59386
|
+
const ageMs = Math.max(0, now - lq.capturedAt);
|
|
59387
|
+
const mins = Math.floor(ageMs / 60000);
|
|
59388
|
+
const ageStr = mins < 1 ? "just now" : mins < 60 ? `${mins}m ago` : mins < 1440 ? `${Math.floor(mins / 60)}h ago` : `${Math.floor(mins / 1440)}d ago`;
|
|
59389
|
+
const five = Math.round(lq.fiveHourUtilizationPct);
|
|
59390
|
+
const seven = Math.round(lq.sevenDayUtilizationPct);
|
|
59391
|
+
return `${five}%\u00b7${seven}% (${ageStr})`;
|
|
59392
|
+
}
|
|
59361
59393
|
function printAccountsTable(state) {
|
|
59362
|
-
console.log(source_default.bold(" ACCOUNT STATUS EXPIRES QUOTA-RESET"));
|
|
59394
|
+
console.log(source_default.bold(" ACCOUNT STATUS EXPIRES QUOTA 5h\u00b77d QUOTA-RESET"));
|
|
59363
59395
|
for (const a of state.accounts) {
|
|
59364
59396
|
const marker = a.label === state.active ? source_default.green("\u25cf") : a.exhausted ? source_default.red("!") : source_default.gray("\u2713");
|
|
59365
59397
|
const status = a.label === state.active ? source_default.green("active ") : a.exhausted ? source_default.red("exhausted") : "available";
|
|
59366
59398
|
const label = a.label.padEnd(32);
|
|
59367
59399
|
const exp = formatExpiry2(a.expiresAt).padEnd(10);
|
|
59400
|
+
const util3 = formatQuotaUtilCell(a).padEnd(20);
|
|
59368
59401
|
const quota = formatQuotaReset(a);
|
|
59369
|
-
console.log(` ${marker} ${label} ${status} ${exp} ${quota}`);
|
|
59402
|
+
console.log(` ${marker} ${label} ${status} ${exp} ${util3} ${quota}`);
|
|
59370
59403
|
}
|
|
59371
59404
|
}
|
|
59372
59405
|
function printAgentsTable(state) {
|
package/dist/cli/ui/index.html
CHANGED
|
@@ -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() })
|
|
500
|
-
fetch(`${API}/api/microsoft-accounts`, { headers: authHeaders() })
|
|
501
|
-
fetch(`${API}/api/notion-workspace`, { headers: authHeaders() })
|
|
502
|
-
fetch(`${API}/api/agents`, { headers: authHeaders() })
|
|
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
|
@@ -43,6 +43,11 @@ export interface AccountSnapshot {
|
|
|
43
43
|
/** Mirrors the broker's `expiresAt` so the table can show token-life
|
|
44
44
|
* for accounts whose creds are about to expire. */
|
|
45
45
|
expiresAtMs?: number;
|
|
46
|
+
/** Unix ms when `quota` was captured. Set for CACHED snapshots
|
|
47
|
+
* (`buildSnapshotsFromCachedState`) so consumers can refuse to treat
|
|
48
|
+
* stale data as current; undefined for live-probe snapshots (fresh
|
|
49
|
+
* by construction). */
|
|
50
|
+
capturedAtMs?: number;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
// ── health classification ────────────────────────────────────────────
|
|
@@ -653,6 +658,10 @@ export function buildSnapshotsFromCachedState(
|
|
|
653
658
|
quota: reviveLastQuota(lq),
|
|
654
659
|
quotaError: lq ? undefined : 'no cached quota (no probe since broker start)',
|
|
655
660
|
expiresAtMs: acc.expiresAt,
|
|
661
|
+
// Surface the cache age so quota-watch can refuse to classify off
|
|
662
|
+
// stale data (the 2026-06-09 incident: a recovery latched days
|
|
663
|
+
// earlier only surfaced — and notified — at the next fleet bounce).
|
|
664
|
+
capturedAtMs: lq?.capturedAt,
|
|
656
665
|
};
|
|
657
666
|
});
|
|
658
667
|
}
|
|
@@ -42,6 +42,65 @@ import {
|
|
|
42
42
|
buildSnapshotsFromState,
|
|
43
43
|
} from './auth-snapshot-format.js';
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Failure notice for when the fallback dispatcher itself errors (broker
|
|
47
|
+
* unreachable, listState/markExhausted throw). The model-unavailable
|
|
48
|
+
* card renders "Auto-failover in progress — see the announcement below"
|
|
49
|
+
* BEFORE the outcome is known; every error path must therefore still
|
|
50
|
+
* produce an announcement or the card's promise is broken (the
|
|
51
|
+
* 2026-06-06→07 incident: 12 cards promised an announcement while every
|
|
52
|
+
* dispatch errored "set-active requires admin" — log-only, nothing
|
|
53
|
+
* arrived). Pure builder so the shape is unit-testable.
|
|
54
|
+
*/
|
|
55
|
+
export function renderFallbackFailureNotice(triggerAgent: string, reason: string): string {
|
|
56
|
+
return (
|
|
57
|
+
`⚠️ <b>Auto-failover could not run</b> (trigger: <b>${escFailureHtml(triggerAgent)}</b>)\n` +
|
|
58
|
+
`${escFailureHtml(reason)}\n\n` +
|
|
59
|
+
`<i>Switch manually with <code>/auth use <label></code>, or <code>/auth</code> for fleet status.</i>`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function escFailureHtml(s: string): string {
|
|
64
|
+
return s
|
|
65
|
+
.replace(/&/g, '&')
|
|
66
|
+
.replace(/</g, '<')
|
|
67
|
+
.replace(/>/g, '>')
|
|
68
|
+
.replace(/"/g, '"')
|
|
69
|
+
.replace(/'/g, ''');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Cooldown for the failure notice. The fleetFallbackGate's dedup window
|
|
74
|
+
* deliberately arms ONLY on a successful swap (fleet-fallback-gate.ts:
|
|
75
|
+
* "No-ops … DO NOT arm the suppression window") — so it bounds nothing
|
|
76
|
+
* on the error path, and the card-less `quota_wall_detected` trigger
|
|
77
|
+
* re-signals every ~60s for the duration of a weekly wall. Without a
|
|
78
|
+
* notice-level bound, a persistent broker outage during a wall would
|
|
79
|
+
* stream ~60 failure notices/hour to every chat for days.
|
|
80
|
+
*
|
|
81
|
+
* Plain time cooldown, per gateway, in-memory. Deliberately NOT keyed
|
|
82
|
+
* by reason: broker error strings vary per attempt (timeout ms values
|
|
83
|
+
* etc.), so a new-reason bypass would re-open the spam hole. Worst
|
|
84
|
+
* case is one notice per gateway per cooldown window.
|
|
85
|
+
*/
|
|
86
|
+
export const FALLBACK_FAILURE_NOTICE_COOLDOWN_MS = 30 * 60_000;
|
|
87
|
+
|
|
88
|
+
export interface FallbackFailureNoticeState {
|
|
89
|
+
/** Unix ms of the last failure notice this gateway sent. 0 = never. */
|
|
90
|
+
lastSentAtMs: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function evaluateFallbackFailureNotice(
|
|
94
|
+
prev: FallbackFailureNoticeState,
|
|
95
|
+
now: number,
|
|
96
|
+
cooldownMs: number = FALLBACK_FAILURE_NOTICE_COOLDOWN_MS,
|
|
97
|
+
): { send: boolean; next: FallbackFailureNoticeState } {
|
|
98
|
+
if (now - prev.lastSentAtMs >= cooldownMs) {
|
|
99
|
+
return { send: true, next: { lastSentAtMs: now } };
|
|
100
|
+
}
|
|
101
|
+
return { send: false, next: prev };
|
|
102
|
+
}
|
|
103
|
+
|
|
45
104
|
export type FleetFallbackOutcome =
|
|
46
105
|
| {
|
|
47
106
|
kind: 'switched';
|