switchroom 0.14.81 → 0.14.82
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.
|
@@ -6932,7 +6932,7 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6932
6932
|
});
|
|
6933
6933
|
|
|
6934
6934
|
// src/agent-scheduler/index.ts
|
|
6935
|
-
import { resolve as resolve4, join } from "node:path";
|
|
6935
|
+
import { resolve as resolve4, join as join2 } from "node:path";
|
|
6936
6936
|
|
|
6937
6937
|
// src/config/loader.ts
|
|
6938
6938
|
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "node:fs";
|
|
@@ -12295,6 +12295,581 @@ class JsonlAuditSink {
|
|
|
12295
12295
|
close() {}
|
|
12296
12296
|
}
|
|
12297
12297
|
|
|
12298
|
+
// src/scheduler/quota-preflight.ts
|
|
12299
|
+
function decideQuotaPreflight(state) {
|
|
12300
|
+
const accounts = state.accounts ?? [];
|
|
12301
|
+
if (accounts.length === 0) {
|
|
12302
|
+
return { defer: false, reason: "no accounts in broker state" };
|
|
12303
|
+
}
|
|
12304
|
+
const healthy = accounts.filter((a) => !a.exhausted);
|
|
12305
|
+
if (healthy.length > 0) {
|
|
12306
|
+
return {
|
|
12307
|
+
defer: false,
|
|
12308
|
+
reason: `${healthy.length}/${accounts.length} account(s) healthy`
|
|
12309
|
+
};
|
|
12310
|
+
}
|
|
12311
|
+
return { defer: true, reason: `all ${accounts.length} account(s) exhausted` };
|
|
12312
|
+
}
|
|
12313
|
+
|
|
12314
|
+
// src/auth/broker/client.ts
|
|
12315
|
+
import * as net from "node:net";
|
|
12316
|
+
import { homedir as homedir2 } from "node:os";
|
|
12317
|
+
import { randomUUID } from "node:crypto";
|
|
12318
|
+
import { join } from "node:path";
|
|
12319
|
+
|
|
12320
|
+
// src/auth/broker/protocol.ts
|
|
12321
|
+
var MAX_FRAME_BYTES = 64 * 1024;
|
|
12322
|
+
var PROTOCOL_VERSION = 1;
|
|
12323
|
+
var ProviderNameSchema = exports_external.enum(["anthropic", "google", "microsoft"]);
|
|
12324
|
+
var GetCredentialsRequestSchema = exports_external.object({
|
|
12325
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12326
|
+
op: exports_external.literal("get-credentials"),
|
|
12327
|
+
id: exports_external.string().min(1),
|
|
12328
|
+
provider: ProviderNameSchema.optional()
|
|
12329
|
+
});
|
|
12330
|
+
var ListStateRequestSchema = exports_external.object({
|
|
12331
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12332
|
+
op: exports_external.literal("list-state"),
|
|
12333
|
+
id: exports_external.string().min(1)
|
|
12334
|
+
});
|
|
12335
|
+
var SetActiveRequestSchema = exports_external.object({
|
|
12336
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12337
|
+
op: exports_external.literal("set-active"),
|
|
12338
|
+
id: exports_external.string().min(1),
|
|
12339
|
+
account: exports_external.string().min(1),
|
|
12340
|
+
provider: ProviderNameSchema.optional()
|
|
12341
|
+
});
|
|
12342
|
+
var MarkExhaustedRequestSchema = exports_external.object({
|
|
12343
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12344
|
+
op: exports_external.literal("mark-exhausted"),
|
|
12345
|
+
id: exports_external.string().min(1),
|
|
12346
|
+
until: exports_external.number().int().positive().optional()
|
|
12347
|
+
});
|
|
12348
|
+
var RefreshAccountRequestSchema = exports_external.object({
|
|
12349
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12350
|
+
op: exports_external.literal("refresh-account"),
|
|
12351
|
+
id: exports_external.string().min(1),
|
|
12352
|
+
account: exports_external.string().min(1),
|
|
12353
|
+
provider: ProviderNameSchema.optional()
|
|
12354
|
+
});
|
|
12355
|
+
var AnthropicCredentialsSchema = exports_external.object({
|
|
12356
|
+
claudeAiOauth: exports_external.object({
|
|
12357
|
+
accessToken: exports_external.string(),
|
|
12358
|
+
refreshToken: exports_external.string().optional(),
|
|
12359
|
+
expiresAt: exports_external.number().optional(),
|
|
12360
|
+
scopes: exports_external.array(exports_external.string()).optional(),
|
|
12361
|
+
subscriptionType: exports_external.string().optional(),
|
|
12362
|
+
rateLimitTier: exports_external.string().optional()
|
|
12363
|
+
})
|
|
12364
|
+
});
|
|
12365
|
+
var GoogleCredentialsSchema = exports_external.object({
|
|
12366
|
+
googleOauth: exports_external.object({
|
|
12367
|
+
accessToken: exports_external.string(),
|
|
12368
|
+
refreshToken: exports_external.string(),
|
|
12369
|
+
expiresAt: exports_external.number(),
|
|
12370
|
+
scope: exports_external.string(),
|
|
12371
|
+
clientId: exports_external.string(),
|
|
12372
|
+
accountEmail: exports_external.string(),
|
|
12373
|
+
tokenType: exports_external.literal("Bearer")
|
|
12374
|
+
})
|
|
12375
|
+
});
|
|
12376
|
+
var MicrosoftCredentialsSchema = exports_external.object({
|
|
12377
|
+
microsoftOauth: exports_external.object({
|
|
12378
|
+
accessToken: exports_external.string(),
|
|
12379
|
+
refreshToken: exports_external.string(),
|
|
12380
|
+
expiresAt: exports_external.number(),
|
|
12381
|
+
scope: exports_external.string(),
|
|
12382
|
+
clientId: exports_external.string(),
|
|
12383
|
+
accountEmail: exports_external.string(),
|
|
12384
|
+
tokenType: exports_external.literal("Bearer"),
|
|
12385
|
+
tenantId: exports_external.string(),
|
|
12386
|
+
accountType: exports_external.enum(["personal", "work"]),
|
|
12387
|
+
homeAccountId: exports_external.string()
|
|
12388
|
+
})
|
|
12389
|
+
});
|
|
12390
|
+
var ProviderCredentialsSchema = exports_external.union([
|
|
12391
|
+
AnthropicCredentialsSchema,
|
|
12392
|
+
GoogleCredentialsSchema,
|
|
12393
|
+
MicrosoftCredentialsSchema
|
|
12394
|
+
]);
|
|
12395
|
+
var AddAccountRequestSchema = exports_external.object({
|
|
12396
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12397
|
+
op: exports_external.literal("add-account"),
|
|
12398
|
+
id: exports_external.string().min(1),
|
|
12399
|
+
label: exports_external.string().min(1),
|
|
12400
|
+
provider: ProviderNameSchema.optional(),
|
|
12401
|
+
credentials: ProviderCredentialsSchema,
|
|
12402
|
+
replace: exports_external.boolean().optional()
|
|
12403
|
+
});
|
|
12404
|
+
var RmAccountRequestSchema = exports_external.object({
|
|
12405
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12406
|
+
op: exports_external.literal("rm-account"),
|
|
12407
|
+
id: exports_external.string().min(1),
|
|
12408
|
+
label: exports_external.string().min(1),
|
|
12409
|
+
provider: ProviderNameSchema.optional()
|
|
12410
|
+
});
|
|
12411
|
+
var SetOverrideRequestSchema = exports_external.object({
|
|
12412
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12413
|
+
op: exports_external.literal("set-override"),
|
|
12414
|
+
id: exports_external.string().min(1),
|
|
12415
|
+
agent: exports_external.string().min(1),
|
|
12416
|
+
account: exports_external.string().min(1).nullable()
|
|
12417
|
+
});
|
|
12418
|
+
var ListGoogleAccountsRequestSchema = exports_external.object({
|
|
12419
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12420
|
+
op: exports_external.literal("list-google-accounts"),
|
|
12421
|
+
id: exports_external.string().min(1)
|
|
12422
|
+
});
|
|
12423
|
+
var ListMicrosoftAccountsRequestSchema = exports_external.object({
|
|
12424
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12425
|
+
op: exports_external.literal("list-microsoft-accounts"),
|
|
12426
|
+
id: exports_external.string().min(1)
|
|
12427
|
+
});
|
|
12428
|
+
var ProbeQuotaRequestSchema = exports_external.object({
|
|
12429
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12430
|
+
op: exports_external.literal("probe-quota"),
|
|
12431
|
+
id: exports_external.string().min(1),
|
|
12432
|
+
accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
12433
|
+
timeoutMs: exports_external.number().int().positive().max(60000).optional()
|
|
12434
|
+
});
|
|
12435
|
+
var RequestSchema = exports_external.discriminatedUnion("op", [
|
|
12436
|
+
GetCredentialsRequestSchema,
|
|
12437
|
+
ListStateRequestSchema,
|
|
12438
|
+
SetActiveRequestSchema,
|
|
12439
|
+
MarkExhaustedRequestSchema,
|
|
12440
|
+
RefreshAccountRequestSchema,
|
|
12441
|
+
AddAccountRequestSchema,
|
|
12442
|
+
RmAccountRequestSchema,
|
|
12443
|
+
SetOverrideRequestSchema,
|
|
12444
|
+
ListGoogleAccountsRequestSchema,
|
|
12445
|
+
ListMicrosoftAccountsRequestSchema,
|
|
12446
|
+
ProbeQuotaRequestSchema
|
|
12447
|
+
]);
|
|
12448
|
+
var GetCredentialsDataSchema = exports_external.object({
|
|
12449
|
+
account: exports_external.string(),
|
|
12450
|
+
credentials: exports_external.unknown(),
|
|
12451
|
+
expiresAt: exports_external.number().optional()
|
|
12452
|
+
});
|
|
12453
|
+
var AccountStateSchema = exports_external.object({
|
|
12454
|
+
label: exports_external.string(),
|
|
12455
|
+
expiresAt: exports_external.number().optional(),
|
|
12456
|
+
exhausted: exports_external.boolean(),
|
|
12457
|
+
exhausted_until: exports_external.number().optional(),
|
|
12458
|
+
threshold_violations: exports_external.number().int().nonnegative().optional(),
|
|
12459
|
+
last_refreshed_at: exports_external.number().optional()
|
|
12460
|
+
});
|
|
12461
|
+
var AgentStateSchema = exports_external.object({
|
|
12462
|
+
name: exports_external.string(),
|
|
12463
|
+
account: exports_external.string(),
|
|
12464
|
+
override: exports_external.string().nullable()
|
|
12465
|
+
});
|
|
12466
|
+
var ConsumerStateSchema = exports_external.object({
|
|
12467
|
+
name: exports_external.string(),
|
|
12468
|
+
account: exports_external.string(),
|
|
12469
|
+
last_seen_at: exports_external.number().nullable()
|
|
12470
|
+
});
|
|
12471
|
+
var ListStateDataSchema = exports_external.object({
|
|
12472
|
+
active: exports_external.string(),
|
|
12473
|
+
fallback_order: exports_external.array(exports_external.string()),
|
|
12474
|
+
accounts: exports_external.array(AccountStateSchema),
|
|
12475
|
+
agents: exports_external.array(AgentStateSchema),
|
|
12476
|
+
consumers: exports_external.array(ConsumerStateSchema)
|
|
12477
|
+
});
|
|
12478
|
+
var SetActiveDataSchema = exports_external.object({
|
|
12479
|
+
active: exports_external.string(),
|
|
12480
|
+
fanned: exports_external.array(exports_external.string())
|
|
12481
|
+
});
|
|
12482
|
+
var MarkExhaustedDataSchema = exports_external.object({
|
|
12483
|
+
account: exports_external.string(),
|
|
12484
|
+
rolled: exports_external.array(exports_external.string()),
|
|
12485
|
+
rolledTo: exports_external.string().nullable().optional()
|
|
12486
|
+
});
|
|
12487
|
+
var RefreshAccountDataSchema = exports_external.object({
|
|
12488
|
+
account: exports_external.string(),
|
|
12489
|
+
expiresAt: exports_external.number().optional()
|
|
12490
|
+
});
|
|
12491
|
+
var AddAccountDataSchema = exports_external.object({
|
|
12492
|
+
label: exports_external.string(),
|
|
12493
|
+
expiresAt: exports_external.number().optional()
|
|
12494
|
+
});
|
|
12495
|
+
var RmAccountDataSchema = exports_external.object({
|
|
12496
|
+
label: exports_external.string()
|
|
12497
|
+
});
|
|
12498
|
+
var SetOverrideDataSchema = exports_external.object({
|
|
12499
|
+
agent: exports_external.string(),
|
|
12500
|
+
account: exports_external.string().nullable()
|
|
12501
|
+
});
|
|
12502
|
+
var GoogleAccountStateSchema = exports_external.object({
|
|
12503
|
+
account: exports_external.string(),
|
|
12504
|
+
expiresAt: exports_external.number(),
|
|
12505
|
+
scope: exports_external.string(),
|
|
12506
|
+
clientId: exports_external.string()
|
|
12507
|
+
});
|
|
12508
|
+
var ListGoogleAccountsDataSchema = exports_external.object({
|
|
12509
|
+
accounts: exports_external.array(GoogleAccountStateSchema)
|
|
12510
|
+
});
|
|
12511
|
+
var MicrosoftAccountStateSchema = exports_external.object({
|
|
12512
|
+
account: exports_external.string(),
|
|
12513
|
+
expiresAt: exports_external.number(),
|
|
12514
|
+
scope: exports_external.string(),
|
|
12515
|
+
clientId: exports_external.string(),
|
|
12516
|
+
accountType: exports_external.enum(["personal", "work"])
|
|
12517
|
+
});
|
|
12518
|
+
var ListMicrosoftAccountsDataSchema = exports_external.object({
|
|
12519
|
+
accounts: exports_external.array(MicrosoftAccountStateSchema)
|
|
12520
|
+
});
|
|
12521
|
+
var ErrorBodySchema = exports_external.object({
|
|
12522
|
+
code: exports_external.enum([
|
|
12523
|
+
"FORBIDDEN",
|
|
12524
|
+
"INVALID_ARGS",
|
|
12525
|
+
"UNKNOWN_VERB",
|
|
12526
|
+
"VERSION_MISMATCH",
|
|
12527
|
+
"ACCOUNT_NOT_FOUND",
|
|
12528
|
+
"ACCOUNT_ALREADY_EXISTS",
|
|
12529
|
+
"CONFIG_INVALID",
|
|
12530
|
+
"DRIFT_DETECTED",
|
|
12531
|
+
"REFRESH_FAILED",
|
|
12532
|
+
"INTERNAL"
|
|
12533
|
+
]),
|
|
12534
|
+
message: exports_external.string()
|
|
12535
|
+
});
|
|
12536
|
+
var SuccessResponseSchema = exports_external.object({
|
|
12537
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12538
|
+
id: exports_external.string(),
|
|
12539
|
+
ok: exports_external.literal(true),
|
|
12540
|
+
data: exports_external.unknown()
|
|
12541
|
+
});
|
|
12542
|
+
var ErrorResponseSchema = exports_external.object({
|
|
12543
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
12544
|
+
id: exports_external.string(),
|
|
12545
|
+
ok: exports_external.literal(false),
|
|
12546
|
+
error: ErrorBodySchema
|
|
12547
|
+
});
|
|
12548
|
+
var ResponseSchema = exports_external.discriminatedUnion("ok", [
|
|
12549
|
+
SuccessResponseSchema,
|
|
12550
|
+
ErrorResponseSchema
|
|
12551
|
+
]);
|
|
12552
|
+
function encodeRequest(req) {
|
|
12553
|
+
const line = JSON.stringify(RequestSchema.parse(req)) + `
|
|
12554
|
+
`;
|
|
12555
|
+
if (Buffer.byteLength(line, "utf-8") > MAX_FRAME_BYTES) {
|
|
12556
|
+
throw new Error(`auth-broker request exceeds MAX_FRAME_BYTES (${MAX_FRAME_BYTES})`);
|
|
12557
|
+
}
|
|
12558
|
+
return line;
|
|
12559
|
+
}
|
|
12560
|
+
function decodeResponse(line) {
|
|
12561
|
+
const trimmed = line.endsWith(`
|
|
12562
|
+
`) ? line.slice(0, -1) : line;
|
|
12563
|
+
let parsed;
|
|
12564
|
+
try {
|
|
12565
|
+
parsed = JSON.parse(trimmed);
|
|
12566
|
+
} catch {
|
|
12567
|
+
throw new Error("auth-broker response is not valid JSON");
|
|
12568
|
+
}
|
|
12569
|
+
return ResponseSchema.parse(parsed);
|
|
12570
|
+
}
|
|
12571
|
+
|
|
12572
|
+
// src/auth/broker/client.ts
|
|
12573
|
+
var DEFAULT_TIMEOUT_MS = 5000;
|
|
12574
|
+
function reviveDate(v) {
|
|
12575
|
+
if (v == null)
|
|
12576
|
+
return null;
|
|
12577
|
+
if (v instanceof Date)
|
|
12578
|
+
return Number.isNaN(v.getTime()) ? null : v;
|
|
12579
|
+
const d = new Date(v);
|
|
12580
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
12581
|
+
}
|
|
12582
|
+
function operatorSocketPath(home2 = homedir2()) {
|
|
12583
|
+
return join(home2, ".switchroom", "state", "auth-broker-operator", "sock");
|
|
12584
|
+
}
|
|
12585
|
+
function resolveAuthBrokerSocketPath(opts) {
|
|
12586
|
+
if (opts?.socket)
|
|
12587
|
+
return opts.socket;
|
|
12588
|
+
const env = process.env.SWITCHROOM_AUTH_BROKER_SOCKET;
|
|
12589
|
+
if (env && env.length > 0)
|
|
12590
|
+
return env;
|
|
12591
|
+
return operatorSocketPath(opts?.home);
|
|
12592
|
+
}
|
|
12593
|
+
|
|
12594
|
+
class AuthBrokerError extends Error {
|
|
12595
|
+
code;
|
|
12596
|
+
constructor(code, message) {
|
|
12597
|
+
super(message);
|
|
12598
|
+
this.code = code;
|
|
12599
|
+
this.name = "AuthBrokerError";
|
|
12600
|
+
}
|
|
12601
|
+
}
|
|
12602
|
+
|
|
12603
|
+
class AuthBrokerUnreachableError extends Error {
|
|
12604
|
+
reason;
|
|
12605
|
+
socketPath;
|
|
12606
|
+
constructor(reason, socketPath) {
|
|
12607
|
+
super(`auth-broker unreachable at ${socketPath}: ${reason}. ` + `The broker may be down; existing credentials remain valid until expiry.`);
|
|
12608
|
+
this.reason = reason;
|
|
12609
|
+
this.socketPath = socketPath;
|
|
12610
|
+
this.name = "AuthBrokerUnreachableError";
|
|
12611
|
+
}
|
|
12612
|
+
}
|
|
12613
|
+
|
|
12614
|
+
class AuthBrokerClient {
|
|
12615
|
+
socketPath;
|
|
12616
|
+
timeoutMs;
|
|
12617
|
+
socket = null;
|
|
12618
|
+
connecting = null;
|
|
12619
|
+
buffer = "";
|
|
12620
|
+
pending = new Map;
|
|
12621
|
+
closed = false;
|
|
12622
|
+
constructor(opts = {}) {
|
|
12623
|
+
this.socketPath = resolveAuthBrokerSocketPath(opts);
|
|
12624
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
12625
|
+
}
|
|
12626
|
+
getSocketPath() {
|
|
12627
|
+
return this.socketPath;
|
|
12628
|
+
}
|
|
12629
|
+
async close() {
|
|
12630
|
+
this.closed = true;
|
|
12631
|
+
const sock = this.socket;
|
|
12632
|
+
this.socket = null;
|
|
12633
|
+
this.connecting = null;
|
|
12634
|
+
for (const [, p] of this.pending) {
|
|
12635
|
+
clearTimeout(p.timer);
|
|
12636
|
+
p.reject(new Error("auth-broker client closed"));
|
|
12637
|
+
}
|
|
12638
|
+
this.pending.clear();
|
|
12639
|
+
if (sock) {
|
|
12640
|
+
sock.destroy();
|
|
12641
|
+
}
|
|
12642
|
+
}
|
|
12643
|
+
async getCredentials(provider) {
|
|
12644
|
+
const base = {
|
|
12645
|
+
v: PROTOCOL_VERSION,
|
|
12646
|
+
id: randomUUID(),
|
|
12647
|
+
op: "get-credentials"
|
|
12648
|
+
};
|
|
12649
|
+
const req = provider !== undefined ? { ...base, provider } : base;
|
|
12650
|
+
const data = await this.send(req);
|
|
12651
|
+
return data;
|
|
12652
|
+
}
|
|
12653
|
+
async listState() {
|
|
12654
|
+
const data = await this.send({
|
|
12655
|
+
v: PROTOCOL_VERSION,
|
|
12656
|
+
id: randomUUID(),
|
|
12657
|
+
op: "list-state"
|
|
12658
|
+
});
|
|
12659
|
+
return data;
|
|
12660
|
+
}
|
|
12661
|
+
async listGoogleAccounts() {
|
|
12662
|
+
const data = await this.send({
|
|
12663
|
+
v: PROTOCOL_VERSION,
|
|
12664
|
+
id: randomUUID(),
|
|
12665
|
+
op: "list-google-accounts"
|
|
12666
|
+
});
|
|
12667
|
+
return data;
|
|
12668
|
+
}
|
|
12669
|
+
async listMicrosoftAccounts() {
|
|
12670
|
+
const data = await this.send({
|
|
12671
|
+
v: PROTOCOL_VERSION,
|
|
12672
|
+
id: randomUUID(),
|
|
12673
|
+
op: "list-microsoft-accounts"
|
|
12674
|
+
});
|
|
12675
|
+
return data;
|
|
12676
|
+
}
|
|
12677
|
+
async probeQuota(accounts, timeoutMs) {
|
|
12678
|
+
const data = await this.send({
|
|
12679
|
+
v: PROTOCOL_VERSION,
|
|
12680
|
+
id: randomUUID(),
|
|
12681
|
+
op: "probe-quota",
|
|
12682
|
+
accounts: [...accounts],
|
|
12683
|
+
...timeoutMs !== undefined ? { timeoutMs } : {}
|
|
12684
|
+
});
|
|
12685
|
+
const parsed = data;
|
|
12686
|
+
for (const entry of parsed.results) {
|
|
12687
|
+
if (entry.result.ok) {
|
|
12688
|
+
entry.result.data.fiveHourResetAt = reviveDate(entry.result.data.fiveHourResetAt);
|
|
12689
|
+
entry.result.data.sevenDayResetAt = reviveDate(entry.result.data.sevenDayResetAt);
|
|
12690
|
+
}
|
|
12691
|
+
}
|
|
12692
|
+
return parsed;
|
|
12693
|
+
}
|
|
12694
|
+
async setActive(account) {
|
|
12695
|
+
const data = await this.send({
|
|
12696
|
+
v: PROTOCOL_VERSION,
|
|
12697
|
+
id: randomUUID(),
|
|
12698
|
+
op: "set-active",
|
|
12699
|
+
account
|
|
12700
|
+
});
|
|
12701
|
+
return data;
|
|
12702
|
+
}
|
|
12703
|
+
async markExhausted(until) {
|
|
12704
|
+
const req = until !== undefined ? { v: PROTOCOL_VERSION, id: randomUUID(), op: "mark-exhausted", until } : { v: PROTOCOL_VERSION, id: randomUUID(), op: "mark-exhausted" };
|
|
12705
|
+
const data = await this.send(req);
|
|
12706
|
+
return data;
|
|
12707
|
+
}
|
|
12708
|
+
async refreshAccount(account) {
|
|
12709
|
+
const data = await this.send({
|
|
12710
|
+
v: PROTOCOL_VERSION,
|
|
12711
|
+
id: randomUUID(),
|
|
12712
|
+
op: "refresh-account",
|
|
12713
|
+
account
|
|
12714
|
+
});
|
|
12715
|
+
return data;
|
|
12716
|
+
}
|
|
12717
|
+
async addAccount(label, credentials, replace, provider) {
|
|
12718
|
+
const base = {
|
|
12719
|
+
v: PROTOCOL_VERSION,
|
|
12720
|
+
id: randomUUID(),
|
|
12721
|
+
op: "add-account",
|
|
12722
|
+
label,
|
|
12723
|
+
credentials
|
|
12724
|
+
};
|
|
12725
|
+
const withReplace = replace ? { ...base, replace: true } : base;
|
|
12726
|
+
const req = provider !== undefined ? { ...withReplace, provider } : withReplace;
|
|
12727
|
+
const data = await this.send(req);
|
|
12728
|
+
return data;
|
|
12729
|
+
}
|
|
12730
|
+
async rmAccount(label, provider) {
|
|
12731
|
+
const base = {
|
|
12732
|
+
v: PROTOCOL_VERSION,
|
|
12733
|
+
id: randomUUID(),
|
|
12734
|
+
op: "rm-account",
|
|
12735
|
+
label
|
|
12736
|
+
};
|
|
12737
|
+
const req = provider !== undefined ? { ...base, provider } : base;
|
|
12738
|
+
const data = await this.send(req);
|
|
12739
|
+
return data;
|
|
12740
|
+
}
|
|
12741
|
+
async setOverride(agent, account) {
|
|
12742
|
+
const data = await this.send({
|
|
12743
|
+
v: PROTOCOL_VERSION,
|
|
12744
|
+
id: randomUUID(),
|
|
12745
|
+
op: "set-override",
|
|
12746
|
+
agent,
|
|
12747
|
+
account
|
|
12748
|
+
});
|
|
12749
|
+
return data;
|
|
12750
|
+
}
|
|
12751
|
+
async ensureConnected() {
|
|
12752
|
+
if (this.closed) {
|
|
12753
|
+
throw new Error("auth-broker client is closed");
|
|
12754
|
+
}
|
|
12755
|
+
if (this.socket && !this.socket.destroyed)
|
|
12756
|
+
return this.socket;
|
|
12757
|
+
if (this.connecting)
|
|
12758
|
+
return this.connecting;
|
|
12759
|
+
this.connecting = new Promise((resolve4, reject) => {
|
|
12760
|
+
const sock = new net.Socket;
|
|
12761
|
+
const onError = (err) => {
|
|
12762
|
+
sock.removeAllListeners();
|
|
12763
|
+
sock.destroy();
|
|
12764
|
+
const code = err.code ?? "ERR";
|
|
12765
|
+
let reason;
|
|
12766
|
+
if (code === "ENOENT")
|
|
12767
|
+
reason = "socket file not found";
|
|
12768
|
+
else if (code === "ECONNREFUSED")
|
|
12769
|
+
reason = "connection refused";
|
|
12770
|
+
else if (code === "EACCES")
|
|
12771
|
+
reason = "access denied";
|
|
12772
|
+
else
|
|
12773
|
+
reason = err.message;
|
|
12774
|
+
reject(new AuthBrokerUnreachableError(reason, this.socketPath));
|
|
12775
|
+
};
|
|
12776
|
+
sock.once("error", onError);
|
|
12777
|
+
sock.once("connect", () => {
|
|
12778
|
+
sock.removeListener("error", onError);
|
|
12779
|
+
sock.on("data", (chunk) => this.onData(chunk));
|
|
12780
|
+
sock.on("error", (err) => this.onSocketError(err));
|
|
12781
|
+
sock.on("close", () => this.onSocketClose());
|
|
12782
|
+
this.socket = sock;
|
|
12783
|
+
resolve4(sock);
|
|
12784
|
+
});
|
|
12785
|
+
sock.connect({ path: this.socketPath });
|
|
12786
|
+
});
|
|
12787
|
+
try {
|
|
12788
|
+
return await this.connecting;
|
|
12789
|
+
} finally {
|
|
12790
|
+
this.connecting = null;
|
|
12791
|
+
}
|
|
12792
|
+
}
|
|
12793
|
+
onData(chunk) {
|
|
12794
|
+
this.buffer += chunk.toString("utf8");
|
|
12795
|
+
let idx;
|
|
12796
|
+
while ((idx = this.buffer.indexOf(`
|
|
12797
|
+
`)) !== -1) {
|
|
12798
|
+
const line = this.buffer.slice(0, idx);
|
|
12799
|
+
this.buffer = this.buffer.slice(idx + 1);
|
|
12800
|
+
if (line.length === 0)
|
|
12801
|
+
continue;
|
|
12802
|
+
let resp;
|
|
12803
|
+
try {
|
|
12804
|
+
resp = decodeResponse(line);
|
|
12805
|
+
} catch (err) {
|
|
12806
|
+
const msg = `unparseable auth-broker response: ${err instanceof Error ? err.message : String(err)}`;
|
|
12807
|
+
this.failAll(new AuthBrokerUnreachableError(msg, this.socketPath));
|
|
12808
|
+
return;
|
|
12809
|
+
}
|
|
12810
|
+
const p = this.pending.get(resp.id);
|
|
12811
|
+
if (!p) {
|
|
12812
|
+
continue;
|
|
12813
|
+
}
|
|
12814
|
+
this.pending.delete(resp.id);
|
|
12815
|
+
clearTimeout(p.timer);
|
|
12816
|
+
p.resolve(resp);
|
|
12817
|
+
}
|
|
12818
|
+
}
|
|
12819
|
+
onSocketError(err) {
|
|
12820
|
+
this.failAll(new AuthBrokerUnreachableError(err.message, this.socketPath));
|
|
12821
|
+
if (this.socket) {
|
|
12822
|
+
this.socket.destroy();
|
|
12823
|
+
this.socket = null;
|
|
12824
|
+
}
|
|
12825
|
+
}
|
|
12826
|
+
onSocketClose() {
|
|
12827
|
+
if (this.pending.size > 0) {
|
|
12828
|
+
this.failAll(new AuthBrokerUnreachableError("connection closed mid-request", this.socketPath));
|
|
12829
|
+
}
|
|
12830
|
+
this.socket = null;
|
|
12831
|
+
}
|
|
12832
|
+
failAll(err) {
|
|
12833
|
+
for (const [, p] of this.pending) {
|
|
12834
|
+
clearTimeout(p.timer);
|
|
12835
|
+
p.reject(err);
|
|
12836
|
+
}
|
|
12837
|
+
this.pending.clear();
|
|
12838
|
+
}
|
|
12839
|
+
async send(req) {
|
|
12840
|
+
const sock = await this.ensureConnected();
|
|
12841
|
+
const id = req.id;
|
|
12842
|
+
const frame = encodeRequest(req);
|
|
12843
|
+
return new Promise((resolve4, reject) => {
|
|
12844
|
+
const timer = setTimeout(() => {
|
|
12845
|
+
this.pending.delete(id);
|
|
12846
|
+
reject(new AuthBrokerUnreachableError(`request ${req.op} timed out after ${this.timeoutMs}ms`, this.socketPath));
|
|
12847
|
+
}, this.timeoutMs);
|
|
12848
|
+
this.pending.set(id, {
|
|
12849
|
+
resolve: (resp) => {
|
|
12850
|
+
if (resp.ok) {
|
|
12851
|
+
resolve4(resp.data);
|
|
12852
|
+
} else {
|
|
12853
|
+
reject(new AuthBrokerError(resp.error.code, resp.error.message));
|
|
12854
|
+
}
|
|
12855
|
+
},
|
|
12856
|
+
reject,
|
|
12857
|
+
timer
|
|
12858
|
+
});
|
|
12859
|
+
sock.write(frame, (err) => {
|
|
12860
|
+
if (err) {
|
|
12861
|
+
const p = this.pending.get(id);
|
|
12862
|
+
if (p) {
|
|
12863
|
+
clearTimeout(p.timer);
|
|
12864
|
+
this.pending.delete(id);
|
|
12865
|
+
}
|
|
12866
|
+
reject(new AuthBrokerUnreachableError(`failed to send ${req.op}: ${err.message}`, this.socketPath));
|
|
12867
|
+
}
|
|
12868
|
+
});
|
|
12869
|
+
});
|
|
12870
|
+
}
|
|
12871
|
+
}
|
|
12872
|
+
|
|
12298
12873
|
// src/agent-scheduler/ipc-client.ts
|
|
12299
12874
|
import { createConnection } from "node:net";
|
|
12300
12875
|
function createInjectIpcClient(options) {
|
|
@@ -12734,12 +13309,55 @@ function readRecentFires(jsonlPath) {
|
|
|
12734
13309
|
}
|
|
12735
13310
|
|
|
12736
13311
|
// src/agent-scheduler/index.ts
|
|
13312
|
+
var DEFAULT_MAX_QUOTA_DEFER_ATTEMPTS = 3;
|
|
13313
|
+
function defaultQuotaDeferBackoffMs(attempt) {
|
|
13314
|
+
return [60000, 180000, 300000][attempt] ?? 300000;
|
|
13315
|
+
}
|
|
12737
13316
|
function registerAgentSchedule(opts) {
|
|
12738
13317
|
const tasks = [];
|
|
12739
13318
|
const now = opts.now ?? Date.now;
|
|
13319
|
+
const maxAttempts = opts.maxQuotaDeferAttempts ?? DEFAULT_MAX_QUOTA_DEFER_ATTEMPTS;
|
|
13320
|
+
const backoff = opts.quotaDeferBackoffMs ?? defaultQuotaDeferBackoffMs;
|
|
13321
|
+
const scheduleRetry = opts.scheduleRetry ?? ((fn, ms) => {
|
|
13322
|
+
const t = setTimeout(fn, ms);
|
|
13323
|
+
if (typeof t.unref === "function")
|
|
13324
|
+
t.unref();
|
|
13325
|
+
return { cancel: () => clearTimeout(t) };
|
|
13326
|
+
});
|
|
12740
13327
|
for (const entry of opts.entries) {
|
|
12741
|
-
const
|
|
13328
|
+
const pendingRetries = new Set;
|
|
13329
|
+
const attemptFire = async (attempt) => {
|
|
12742
13330
|
const startedAt = now();
|
|
13331
|
+
if (opts.quotaGate) {
|
|
13332
|
+
let decision;
|
|
13333
|
+
try {
|
|
13334
|
+
decision = await opts.quotaGate(entry.agent);
|
|
13335
|
+
} catch {
|
|
13336
|
+
decision = { defer: false, reason: "quota gate error (fail-open)" };
|
|
13337
|
+
}
|
|
13338
|
+
if (decision.defer) {
|
|
13339
|
+
const more = attempt + 1 < maxAttempts;
|
|
13340
|
+
const summary2 = `deferred (quota): ${decision.reason} ` + `[attempt ${attempt + 1}/${maxAttempts}]` + (more ? "" : " — giving up; will re-run on next scheduled occurrence");
|
|
13341
|
+
opts.sink.recordFire({
|
|
13342
|
+
agent: entry.agent,
|
|
13343
|
+
scheduleIndex: entry.scheduleIndex,
|
|
13344
|
+
promptKey: entry.promptKey,
|
|
13345
|
+
exitCode: -2,
|
|
13346
|
+
outputSummary: summary2,
|
|
13347
|
+
startedAt,
|
|
13348
|
+
finishedAt: now()
|
|
13349
|
+
});
|
|
13350
|
+
if (more) {
|
|
13351
|
+
let handle;
|
|
13352
|
+
handle = scheduleRetry(() => {
|
|
13353
|
+
pendingRetries.delete(handle);
|
|
13354
|
+
attemptFire(attempt + 1);
|
|
13355
|
+
}, backoff(attempt));
|
|
13356
|
+
pendingRetries.add(handle);
|
|
13357
|
+
}
|
|
13358
|
+
return;
|
|
13359
|
+
}
|
|
13360
|
+
}
|
|
12743
13361
|
let delivered = false;
|
|
12744
13362
|
let summary = "";
|
|
12745
13363
|
try {
|
|
@@ -12760,8 +13378,19 @@ function registerAgentSchedule(opts) {
|
|
|
12760
13378
|
startedAt,
|
|
12761
13379
|
finishedAt
|
|
12762
13380
|
});
|
|
13381
|
+
};
|
|
13382
|
+
const task = opts.cronLib.schedule(entry.cron, () => attemptFire(0));
|
|
13383
|
+
tasks.push({
|
|
13384
|
+
entry,
|
|
13385
|
+
task: {
|
|
13386
|
+
stop: () => {
|
|
13387
|
+
for (const h of pendingRetries)
|
|
13388
|
+
h.cancel();
|
|
13389
|
+
pendingRetries.clear();
|
|
13390
|
+
task.stop();
|
|
13391
|
+
}
|
|
13392
|
+
}
|
|
12763
13393
|
});
|
|
12764
|
-
tasks.push({ entry, task });
|
|
12765
13394
|
}
|
|
12766
13395
|
return tasks;
|
|
12767
13396
|
}
|
|
@@ -12785,7 +13414,7 @@ async function main() {
|
|
|
12785
13414
|
}
|
|
12786
13415
|
const configPath = process.env.SWITCHROOM_CONFIG ?? "/state/config/switchroom.yaml";
|
|
12787
13416
|
const stateDir = process.env.TELEGRAM_STATE_DIR ?? "/state/agent/telegram";
|
|
12788
|
-
const socketPath = process.env.SWITCHROOM_GATEWAY_SOCKET ??
|
|
13417
|
+
const socketPath = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join2(stateDir, "gateway.sock");
|
|
12789
13418
|
const jsonlPath = process.env.SWITCHROOM_AGENT_SCHEDULER_JSONL ?? "/state/agent/scheduler.jsonl";
|
|
12790
13419
|
const lockPath = process.env.SWITCHROOM_AGENT_SCHEDULER_LOCK ?? "/state/agent/scheduler.lock";
|
|
12791
13420
|
const lock = acquireLock(lockPath);
|
|
@@ -12913,12 +13542,22 @@ Briefly and plainly tell the user these scheduled runs did not ` + "happen so th
|
|
|
12913
13542
|
}
|
|
12914
13543
|
}
|
|
12915
13544
|
const cronLib = __require("node-cron");
|
|
13545
|
+
const quotaPreflightEnabled = process.env.SWITCHROOM_DISABLE_CRON_QUOTA_PREFLIGHT !== "1";
|
|
13546
|
+
const quotaGate = quotaPreflightEnabled ? async () => {
|
|
13547
|
+
const client = new AuthBrokerClient;
|
|
13548
|
+
try {
|
|
13549
|
+
return decideQuotaPreflight(await client.listState());
|
|
13550
|
+
} finally {
|
|
13551
|
+
await client.close().catch(() => {});
|
|
13552
|
+
}
|
|
13553
|
+
} : undefined;
|
|
12916
13554
|
const tasks = registerAgentSchedule({
|
|
12917
13555
|
entries,
|
|
12918
13556
|
channel,
|
|
12919
13557
|
sink,
|
|
12920
13558
|
cronLib,
|
|
12921
|
-
dispatcher
|
|
13559
|
+
dispatcher,
|
|
13560
|
+
...quotaGate ? { quotaGate } : {}
|
|
12922
13561
|
});
|
|
12923
13562
|
process.stdout.write(`agent-scheduler: ${agentName} registered ${tasks.length} task(s); ` + `chat=${channel.chatId} thread=${channel.threadId ?? "(none)"} ` + `socket=${socketPath} jsonl=${jsonlPath}
|
|
12924
13563
|
`);
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -49700,8 +49700,8 @@ var {
|
|
|
49700
49700
|
} = import__.default;
|
|
49701
49701
|
|
|
49702
49702
|
// src/build-info.ts
|
|
49703
|
-
var VERSION = "0.14.
|
|
49704
|
-
var COMMIT_SHA = "
|
|
49703
|
+
var VERSION = "0.14.82";
|
|
49704
|
+
var COMMIT_SHA = "91bc41d1";
|
|
49705
49705
|
|
|
49706
49706
|
// src/cli/agent.ts
|
|
49707
49707
|
init_source();
|
package/package.json
CHANGED
|
@@ -52810,9 +52810,9 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
52810
52810
|
}
|
|
52811
52811
|
|
|
52812
52812
|
// ../src/build-info.ts
|
|
52813
|
-
var VERSION = "0.14.
|
|
52814
|
-
var COMMIT_SHA = "
|
|
52815
|
-
var COMMIT_DATE = "2026-06-
|
|
52813
|
+
var VERSION = "0.14.82";
|
|
52814
|
+
var COMMIT_SHA = "91bc41d1";
|
|
52815
|
+
var COMMIT_DATE = "2026-06-07T12:22:49+10:00";
|
|
52816
52816
|
var LATEST_PR = null;
|
|
52817
52817
|
var COMMITS_AHEAD_OF_TAG = 2;
|
|
52818
52818
|
|