switchroom 0.18.13 → 0.18.15
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 +49 -9
- package/dist/auth-broker/index.js +152 -46
- package/dist/cli/autoaccept-poll.js +23 -0
- package/dist/cli/drive-write-pretool.mjs +24 -1
- package/dist/cli/foreground-hog-pretool.mjs +264 -0
- package/dist/cli/notion-write-pretool.mjs +0 -1
- package/dist/cli/switchroom.js +1185 -1072
- package/dist/host-control/main.js +53 -52
- package/dist/vault/approvals/kernel-server.js +16 -13
- package/dist/vault/broker/server.js +672 -669
- package/package.json +1 -1
- package/profiles/coding/CLAUDE.md.hbs +2 -0
- package/profiles/default/CLAUDE.md.hbs +2 -0
- package/skills/switchroom-architecture/telegram.md +0 -1
- package/telegram-plugin/auth-snapshot-format.ts +37 -5
- package/telegram-plugin/auto-fallback-fleet.ts +29 -1
- package/telegram-plugin/bridge/bridge.ts +2 -0
- package/telegram-plugin/dist/bridge/bridge.js +23 -0
- package/telegram-plugin/dist/gateway/gateway.js +765 -67
- package/telegram-plugin/dist/server.js +24 -1
- package/telegram-plugin/gateway/auth-broker-client.ts +1 -0
- package/telegram-plugin/gateway/auth-command.ts +14 -0
- package/telegram-plugin/gateway/forward-origin.ts +235 -0
- package/telegram-plugin/gateway/gateway.ts +270 -10
- package/telegram-plugin/gateway/throttle-tier-wiring.ts +268 -0
- package/telegram-plugin/history.ts +55 -6
- package/telegram-plugin/model-unavailable.ts +234 -2
- package/telegram-plugin/render/rich-render.ts +40 -32
- package/telegram-plugin/runtime-metrics.ts +31 -0
- package/telegram-plugin/session-tail.ts +14 -2
- package/telegram-plugin/stream-controller.ts +3 -2
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +72 -0
- package/telegram-plugin/tests/forward-origin.test.ts +309 -0
- package/telegram-plugin/tests/history.test.ts +157 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +187 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +55 -0
- package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +6 -4
- package/telegram-plugin/tests/render/rich-render.test.ts +41 -22
- package/telegram-plugin/tests/runtime-metrics.test.ts +24 -0
- package/telegram-plugin/tests/single-mode-stream-reply.test.ts +5 -3
- package/telegram-plugin/tests/status-accent.test.ts +5 -3
- package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +20 -20
- package/telegram-plugin/tests/stream-reply-handler.test.ts +5 -2
- package/telegram-plugin/tests/throttle-tier-wiring.test.ts +290 -0
- package/telegram-plugin/tests/throttle-tier.test.ts +454 -0
- package/telegram-plugin/throttle-tier.ts +323 -0
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +8 -7
|
@@ -19458,7 +19458,7 @@ function renderFallbackAnnouncement(input) {
|
|
|
19458
19458
|
const tz = input.tz ?? "UTC";
|
|
19459
19459
|
const lines = [];
|
|
19460
19460
|
const limitWord = input.oldQuota ? limitWordFor(input.oldQuota) : "quota";
|
|
19461
|
-
const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
|
|
19461
|
+
const headerLimit = input.cause === "rate-limit" ? "rate limit" : limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
|
|
19462
19462
|
if (!input.newLabel) {
|
|
19463
19463
|
lines.push(`\uD83D\uDD34 **All accounts blocked \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
|
|
19464
19464
|
lines.push("");
|
|
@@ -19479,8 +19479,8 @@ function renderFallbackAnnouncement(input) {
|
|
|
19479
19479
|
lines.push("");
|
|
19480
19480
|
lines.push(`Earliest recovery: \`${codeSpanSafe(earliest.label)}\` ` + `${formatAbsolute(earliest.at, tz)} (in ${formatRelative(earliest.at, now)})`);
|
|
19481
19481
|
}
|
|
19482
|
-
} else
|
|
19483
|
-
const recovery = recoveryAtFor(input.oldQuota);
|
|
19482
|
+
} else {
|
|
19483
|
+
const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
|
|
19484
19484
|
if (recovery) {
|
|
19485
19485
|
lines.push(`${escapeMarkdown(input.oldLabel)} recovers ${formatAbsolute(recovery, tz)} ` + `(in ${formatRelative(recovery, now)})`);
|
|
19486
19486
|
}
|
|
@@ -19495,8 +19495,8 @@ function renderFallbackAnnouncement(input) {
|
|
|
19495
19495
|
lines.push(`\`${codeSpanSafe(input.oldLabel)}\` \u2192 \`${codeSpanSafe(input.newLabel)}\``);
|
|
19496
19496
|
lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
|
|
19497
19497
|
lines.push("");
|
|
19498
|
-
|
|
19499
|
-
const recovery = recoveryAtFor(input.oldQuota);
|
|
19498
|
+
{
|
|
19499
|
+
const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
|
|
19500
19500
|
if (recovery) {
|
|
19501
19501
|
lines.push(`\`${codeSpanSafe(input.oldLabel)}\` recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
|
|
19502
19502
|
}
|
|
@@ -19661,7 +19661,7 @@ function decodeResponse2(line) {
|
|
|
19661
19661
|
}
|
|
19662
19662
|
return ResponseSchema2.parse(parsed);
|
|
19663
19663
|
}
|
|
19664
|
-
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;
|
|
19664
|
+
var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
19665
19665
|
var init_protocol2 = __esm(() => {
|
|
19666
19666
|
init_zod();
|
|
19667
19667
|
MAX_FRAME_BYTES2 = 64 * 1024;
|
|
@@ -19690,6 +19690,12 @@ var init_protocol2 = __esm(() => {
|
|
|
19690
19690
|
id: exports_external.string().min(1),
|
|
19691
19691
|
until: exports_external.number().int().positive().optional()
|
|
19692
19692
|
});
|
|
19693
|
+
MarkThrottledRequestSchema = exports_external.object({
|
|
19694
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
19695
|
+
op: exports_external.literal("mark-throttled"),
|
|
19696
|
+
id: exports_external.string().min(1),
|
|
19697
|
+
until: exports_external.number().int().positive()
|
|
19698
|
+
});
|
|
19693
19699
|
RefreshAccountRequestSchema = exports_external.object({
|
|
19694
19700
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
19695
19701
|
op: exports_external.literal("refresh-account"),
|
|
@@ -19790,6 +19796,7 @@ var init_protocol2 = __esm(() => {
|
|
|
19790
19796
|
ListStateRequestSchema,
|
|
19791
19797
|
SetActiveRequestSchema,
|
|
19792
19798
|
MarkExhaustedRequestSchema,
|
|
19799
|
+
MarkThrottledRequestSchema,
|
|
19793
19800
|
RefreshAccountRequestSchema,
|
|
19794
19801
|
AddAccountRequestSchema,
|
|
19795
19802
|
RmAccountRequestSchema,
|
|
@@ -19809,6 +19816,7 @@ var init_protocol2 = __esm(() => {
|
|
|
19809
19816
|
expiresAt: exports_external.number().optional(),
|
|
19810
19817
|
exhausted: exports_external.boolean(),
|
|
19811
19818
|
exhausted_until: exports_external.number().optional(),
|
|
19819
|
+
throttled_until: exports_external.number().optional(),
|
|
19812
19820
|
threshold_violations: exports_external.number().int().nonnegative().optional(),
|
|
19813
19821
|
last_refreshed_at: exports_external.number().optional()
|
|
19814
19822
|
});
|
|
@@ -19839,6 +19847,12 @@ var init_protocol2 = __esm(() => {
|
|
|
19839
19847
|
rolled: exports_external.array(exports_external.string()),
|
|
19840
19848
|
rolledTo: exports_external.string().nullable().optional()
|
|
19841
19849
|
});
|
|
19850
|
+
MarkThrottledDataSchema = exports_external.object({
|
|
19851
|
+
account: exports_external.string(),
|
|
19852
|
+
throttled_until: exports_external.number(),
|
|
19853
|
+
escalated: exports_external.boolean(),
|
|
19854
|
+
rolledTo: exports_external.string().nullable().optional()
|
|
19855
|
+
});
|
|
19842
19856
|
RefreshAccountDataSchema = exports_external.object({
|
|
19843
19857
|
account: exports_external.string(),
|
|
19844
19858
|
expiresAt: exports_external.number().optional()
|
|
@@ -20037,6 +20051,15 @@ class AuthBrokerClient {
|
|
|
20037
20051
|
const data = await this.send(req);
|
|
20038
20052
|
return data;
|
|
20039
20053
|
}
|
|
20054
|
+
async markThrottled(until) {
|
|
20055
|
+
const data = await this.send({
|
|
20056
|
+
v: PROTOCOL_VERSION,
|
|
20057
|
+
id: randomUUID(),
|
|
20058
|
+
op: "mark-throttled",
|
|
20059
|
+
until
|
|
20060
|
+
});
|
|
20061
|
+
return data;
|
|
20062
|
+
}
|
|
20040
20063
|
async claimNotification(key, windowMs) {
|
|
20041
20064
|
const data = await this.send({
|
|
20042
20065
|
v: PROTOCOL_VERSION,
|
|
@@ -20474,7 +20497,6 @@ var init_schema = __esm(() => {
|
|
|
20474
20497
|
enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
|
|
20475
20498
|
plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' \u2014 the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
|
|
20476
20499
|
format: exports_external.enum(["html", "markdownv2", "text"]).optional().describe("Default reply format passed to the plugin"),
|
|
20477
|
-
rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
|
|
20478
20500
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI \u2014 " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events \u2014 stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
20479
20501
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive in-place stream edits " + "during a turn. Lower = more responsive stream, higher = fewer API " + "calls. Floored at 250 by draft-stream itself. Default 400 ms for DMs " + "and 1000 ms for groups/forums (respects Telegram's ~1 edit/sec/message " + "practical ceiling). Override per-agent if a particular agent needs " + "snappier or quieter streaming."),
|
|
20480
20502
|
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message \u2014 Reading X, Searching the web for Y, \u2026) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) \u2014 no post-then-delete. Per-agent " + "override; cascades defaults \u2192 profile \u2192 agent (per-key)."),
|
|
@@ -28164,7 +28186,16 @@ function initHistory(stateDir, retentionDays = 30) {
|
|
|
28164
28186
|
CREATE INDEX IF NOT EXISTS idx_messages_recent
|
|
28165
28187
|
ON messages (chat_id, thread_id, ts DESC)
|
|
28166
28188
|
`);
|
|
28167
|
-
for (const column of [
|
|
28189
|
+
for (const column of [
|
|
28190
|
+
"reply_to_message_id INTEGER",
|
|
28191
|
+
"reply_to_text TEXT",
|
|
28192
|
+
"user_reaction TEXT",
|
|
28193
|
+
"forwarded_from TEXT",
|
|
28194
|
+
"forwarded_from_type TEXT",
|
|
28195
|
+
"forwarded_from_id TEXT",
|
|
28196
|
+
"forwarded_date TEXT",
|
|
28197
|
+
"forwarded_message_id INTEGER"
|
|
28198
|
+
]) {
|
|
28168
28199
|
try {
|
|
28169
28200
|
db.exec(`ALTER TABLE messages ADD COLUMN ${column}`);
|
|
28170
28201
|
} catch (err) {
|
|
@@ -28264,10 +28295,10 @@ function recordInbound(args) {
|
|
|
28264
28295
|
return;
|
|
28265
28296
|
const stmt = requireDb().prepare(`
|
|
28266
28297
|
INSERT OR REPLACE INTO messages
|
|
28267
|
-
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id, reply_to_message_id, reply_to_text)
|
|
28268
|
-
VALUES (?, ?, ?, 'user', ?, ?, ?, ?, ?, NULL, ?, ?)
|
|
28298
|
+
(chat_id, thread_id, message_id, role, user, user_id, ts, text, attachment_kind, group_id, reply_to_message_id, reply_to_text, forwarded_from, forwarded_from_type, forwarded_from_id, forwarded_date, forwarded_message_id)
|
|
28299
|
+
VALUES (?, ?, ?, 'user', ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?)
|
|
28269
28300
|
`);
|
|
28270
|
-
stmt.run(args.chat_id, args.thread_id ?? null, args.message_id, args.user ?? null, args.user_id ?? null, args.ts, redact(args.text), args.attachment_kind ?? null, args.reply_to_message_id ?? null, args.reply_to_text != null ? redact(args.reply_to_text) : args.reply_to_text ?? null);
|
|
28301
|
+
stmt.run(args.chat_id, args.thread_id ?? null, args.message_id, args.user ?? null, args.user_id ?? null, args.ts, redact(args.text), args.attachment_kind ?? null, args.reply_to_message_id ?? null, args.reply_to_text != null ? redact(args.reply_to_text) : args.reply_to_text ?? null, args.forwarded_from != null ? redact(args.forwarded_from) : null, args.forwarded_from_type ?? null, args.forwarded_from_id ?? null, args.forwarded_date ?? null, args.forwarded_message_id ?? null);
|
|
28271
28302
|
}
|
|
28272
28303
|
function recordOutbound(args) {
|
|
28273
28304
|
if (args.message_ids.length === 0)
|
|
@@ -36143,7 +36174,7 @@ function renderFallbackAnnouncement2(input) {
|
|
|
36143
36174
|
const tz = input.tz ?? "UTC";
|
|
36144
36175
|
const lines = [];
|
|
36145
36176
|
const limitWord = input.oldQuota ? limitWordFor2(input.oldQuota) : "quota";
|
|
36146
|
-
const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
|
|
36177
|
+
const headerLimit = input.cause === "rate-limit" ? "rate limit" : limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
|
|
36147
36178
|
if (!input.newLabel) {
|
|
36148
36179
|
lines.push(`\uD83D\uDD34 **All accounts blocked \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
|
|
36149
36180
|
lines.push("");
|
|
@@ -36164,8 +36195,8 @@ function renderFallbackAnnouncement2(input) {
|
|
|
36164
36195
|
lines.push("");
|
|
36165
36196
|
lines.push(`Earliest recovery: \`${codeSpanSafe(earliest.label)}\` ` + `${formatAbsolute2(earliest.at, tz)} (in ${formatRelative2(earliest.at, now)})`);
|
|
36166
36197
|
}
|
|
36167
|
-
} else
|
|
36168
|
-
const recovery = recoveryAtFor2(input.oldQuota);
|
|
36198
|
+
} else {
|
|
36199
|
+
const recovery = (input.oldQuota ? recoveryAtFor2(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
|
|
36169
36200
|
if (recovery) {
|
|
36170
36201
|
lines.push(`${escapeMarkdown(input.oldLabel)} recovers ${formatAbsolute2(recovery, tz)} ` + `(in ${formatRelative2(recovery, now)})`);
|
|
36171
36202
|
}
|
|
@@ -36180,8 +36211,8 @@ function renderFallbackAnnouncement2(input) {
|
|
|
36180
36211
|
lines.push(`\`${codeSpanSafe(input.oldLabel)}\` \u2192 \`${codeSpanSafe(input.newLabel)}\``);
|
|
36181
36212
|
lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
|
|
36182
36213
|
lines.push("");
|
|
36183
|
-
|
|
36184
|
-
const recovery = recoveryAtFor2(input.oldQuota);
|
|
36214
|
+
{
|
|
36215
|
+
const recovery = (input.oldQuota ? recoveryAtFor2(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
|
|
36185
36216
|
if (recovery) {
|
|
36186
36217
|
lines.push(`\`${codeSpanSafe(input.oldLabel)}\` recovers ` + `${formatAbsolute2(recovery, tz)} (in ${formatRelative2(recovery, now)})`);
|
|
36187
36218
|
}
|
|
@@ -39275,6 +39306,133 @@ function buildExtraAttachmentMeta(resolved) {
|
|
|
39275
39306
|
return out;
|
|
39276
39307
|
}
|
|
39277
39308
|
|
|
39309
|
+
// steering.ts
|
|
39310
|
+
function escapeXmlAttribute(s) {
|
|
39311
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
39312
|
+
}
|
|
39313
|
+
|
|
39314
|
+
// gateway/forward-origin.ts
|
|
39315
|
+
var FORWARDED_FROM_NAME_MAX = 100;
|
|
39316
|
+
function truncateName(name) {
|
|
39317
|
+
return name.length > FORWARDED_FROM_NAME_MAX ? name.slice(0, FORWARDED_FROM_NAME_MAX - 1) + "\u2026" : name;
|
|
39318
|
+
}
|
|
39319
|
+
function personName(parts) {
|
|
39320
|
+
const name = [parts.first_name, parts.last_name].filter((p) => typeof p === "string" && p.length > 0).join(" ");
|
|
39321
|
+
const handle = parts.username ? `(@${parts.username})` : "";
|
|
39322
|
+
return [name, handle].filter((p) => p.length > 0).join(" ");
|
|
39323
|
+
}
|
|
39324
|
+
function chatTitle(chat) {
|
|
39325
|
+
if (chat.title && chat.title.length > 0) {
|
|
39326
|
+
const handle = chat.username ? `(@${chat.username})` : "";
|
|
39327
|
+
return [chat.title, handle].filter((p) => p.length > 0).join(" ");
|
|
39328
|
+
}
|
|
39329
|
+
return personName(chat);
|
|
39330
|
+
}
|
|
39331
|
+
function parseForwardOrigin(origin) {
|
|
39332
|
+
if (origin == null || typeof origin !== "object")
|
|
39333
|
+
return;
|
|
39334
|
+
const date = typeof origin.date === "number" ? origin.date : undefined;
|
|
39335
|
+
switch (origin.type) {
|
|
39336
|
+
case "user": {
|
|
39337
|
+
const u = origin.sender_user;
|
|
39338
|
+
if (u == null || typeof u !== "object")
|
|
39339
|
+
return;
|
|
39340
|
+
const name = personName(u);
|
|
39341
|
+
if (name.length === 0) {
|
|
39342
|
+
if (typeof u.id !== "number")
|
|
39343
|
+
return;
|
|
39344
|
+
return { name: String(u.id), type: "user", id: u.id, date };
|
|
39345
|
+
}
|
|
39346
|
+
return {
|
|
39347
|
+
name: truncateName(name),
|
|
39348
|
+
type: "user",
|
|
39349
|
+
...typeof u.id === "number" ? { id: u.id } : {},
|
|
39350
|
+
date
|
|
39351
|
+
};
|
|
39352
|
+
}
|
|
39353
|
+
case "hidden_user": {
|
|
39354
|
+
const name = typeof origin.sender_user_name === "string" ? origin.sender_user_name : "";
|
|
39355
|
+
if (name.length === 0)
|
|
39356
|
+
return;
|
|
39357
|
+
return { name: truncateName(name), type: "hidden_user", date };
|
|
39358
|
+
}
|
|
39359
|
+
case "chat": {
|
|
39360
|
+
const c = origin.sender_chat;
|
|
39361
|
+
if (c == null || typeof c !== "object")
|
|
39362
|
+
return;
|
|
39363
|
+
const name = chatTitle(c);
|
|
39364
|
+
if (name.length === 0) {
|
|
39365
|
+
if (typeof c.id !== "number")
|
|
39366
|
+
return;
|
|
39367
|
+
return { name: String(c.id), type: "chat", id: c.id, date };
|
|
39368
|
+
}
|
|
39369
|
+
return {
|
|
39370
|
+
name: truncateName(name),
|
|
39371
|
+
type: "chat",
|
|
39372
|
+
...typeof c.id === "number" ? { id: c.id } : {},
|
|
39373
|
+
date
|
|
39374
|
+
};
|
|
39375
|
+
}
|
|
39376
|
+
case "channel": {
|
|
39377
|
+
const c = origin.chat;
|
|
39378
|
+
if (c == null || typeof c !== "object")
|
|
39379
|
+
return;
|
|
39380
|
+
const name = chatTitle(c);
|
|
39381
|
+
const messageId = typeof origin.message_id === "number" ? origin.message_id : undefined;
|
|
39382
|
+
if (name.length === 0) {
|
|
39383
|
+
if (typeof c.id !== "number")
|
|
39384
|
+
return;
|
|
39385
|
+
return { name: String(c.id), type: "channel", id: c.id, date, messageId };
|
|
39386
|
+
}
|
|
39387
|
+
return {
|
|
39388
|
+
name: truncateName(name),
|
|
39389
|
+
type: "channel",
|
|
39390
|
+
...typeof c.id === "number" ? { id: c.id } : {},
|
|
39391
|
+
date,
|
|
39392
|
+
messageId
|
|
39393
|
+
};
|
|
39394
|
+
}
|
|
39395
|
+
default:
|
|
39396
|
+
return;
|
|
39397
|
+
}
|
|
39398
|
+
}
|
|
39399
|
+
function forwardOriginKey(o) {
|
|
39400
|
+
return o.id != null ? `${o.type}:${o.id}` : `${o.type}:${o.name}`;
|
|
39401
|
+
}
|
|
39402
|
+
function dedupeForwardOrigins(origins) {
|
|
39403
|
+
const seen = new Set;
|
|
39404
|
+
const out = [];
|
|
39405
|
+
for (const o of origins) {
|
|
39406
|
+
if (o == null)
|
|
39407
|
+
continue;
|
|
39408
|
+
const key = forwardOriginKey(o);
|
|
39409
|
+
if (seen.has(key))
|
|
39410
|
+
continue;
|
|
39411
|
+
seen.add(key);
|
|
39412
|
+
out.push(o);
|
|
39413
|
+
}
|
|
39414
|
+
return out;
|
|
39415
|
+
}
|
|
39416
|
+
function buildForwardOriginMeta(origins) {
|
|
39417
|
+
const out = {};
|
|
39418
|
+
origins.forEach((o, i) => {
|
|
39419
|
+
const suffix = i === 0 ? "" : `_${i + 1}`;
|
|
39420
|
+
out[`forwarded_from${suffix}`] = escapeXmlAttribute(truncateName(o.name));
|
|
39421
|
+
out[`forwarded_from_type${suffix}`] = o.type;
|
|
39422
|
+
if (o.id != null)
|
|
39423
|
+
out[`forwarded_from_id${suffix}`] = String(o.id);
|
|
39424
|
+
if (o.date != null) {
|
|
39425
|
+
out[`forwarded_date${suffix}`] = new Date(o.date * 1000).toISOString();
|
|
39426
|
+
}
|
|
39427
|
+
});
|
|
39428
|
+
return out;
|
|
39429
|
+
}
|
|
39430
|
+
function forwardOriginDateIso(o) {
|
|
39431
|
+
if (o?.date == null)
|
|
39432
|
+
return null;
|
|
39433
|
+
return new Date(o.date * 1000).toISOString();
|
|
39434
|
+
}
|
|
39435
|
+
|
|
39278
39436
|
// status-reactions.ts
|
|
39279
39437
|
var TELEGRAM_REACTION_WHITELIST = new Set([
|
|
39280
39438
|
"\uD83D\uDC4D",
|
|
@@ -42019,6 +42177,7 @@ function createAuthBrokerClient() {
|
|
|
42019
42177
|
listState: () => broker.listState(),
|
|
42020
42178
|
setActive: (label) => broker.setActive(label),
|
|
42021
42179
|
markExhausted: (until) => broker.markExhausted(until),
|
|
42180
|
+
markThrottled: (until) => broker.markThrottled(until),
|
|
42022
42181
|
rmAccount: (label) => broker.rmAccount(label),
|
|
42023
42182
|
refreshAccount: (label) => broker.refreshAccount(label),
|
|
42024
42183
|
setOverride: (agent, account) => broker.setOverride(agent, account),
|
|
@@ -54665,9 +54824,9 @@ function renderSafe(doc, source, maxLen = RICH_MESSAGE_MAX_CHARS) {
|
|
|
54665
54824
|
var PLAIN_TEXT_MAX_CHARS = 4096;
|
|
54666
54825
|
function parseRichRenderEnabled(raw) {
|
|
54667
54826
|
if (raw == null)
|
|
54668
|
-
return
|
|
54827
|
+
return true;
|
|
54669
54828
|
const v = raw.trim().toLowerCase();
|
|
54670
|
-
return v === "
|
|
54829
|
+
return !(v === "0" || v === "false" || v === "off" || v === "no");
|
|
54671
54830
|
}
|
|
54672
54831
|
function richRenderEnabled(env = process.env) {
|
|
54673
54832
|
return parseRichRenderEnabled(env.SWITCHROOM_RICH_RENDER);
|
|
@@ -62755,6 +62914,7 @@ function createAuthBrokerClient2() {
|
|
|
62755
62914
|
listState: () => broker.listState(),
|
|
62756
62915
|
setActive: (label) => broker.setActive(label),
|
|
62757
62916
|
markExhausted: (until) => broker.markExhausted(until),
|
|
62917
|
+
markThrottled: (until) => broker.markThrottled(until),
|
|
62758
62918
|
rmAccount: (label) => broker.rmAccount(label),
|
|
62759
62919
|
refreshAccount: (label) => broker.refreshAccount(label),
|
|
62760
62920
|
setOverride: (agent, account) => broker.setOverride(agent, account),
|
|
@@ -63161,6 +63321,15 @@ class AuthBrokerClient2 {
|
|
|
63161
63321
|
const data = await this.send(req);
|
|
63162
63322
|
return data;
|
|
63163
63323
|
}
|
|
63324
|
+
async markThrottled(until) {
|
|
63325
|
+
const data = await this.send({
|
|
63326
|
+
v: PROTOCOL_VERSION,
|
|
63327
|
+
id: randomUUID4(),
|
|
63328
|
+
op: "mark-throttled",
|
|
63329
|
+
until
|
|
63330
|
+
});
|
|
63331
|
+
return data;
|
|
63332
|
+
}
|
|
63164
63333
|
async claimNotification(key, windowMs) {
|
|
63165
63334
|
const data = await this.send({
|
|
63166
63335
|
v: PROTOCOL_VERSION,
|
|
@@ -64205,7 +64374,7 @@ function parseSteerPrefix(body) {
|
|
|
64205
64374
|
return { steering: false, body };
|
|
64206
64375
|
return { steering: true, body: m[2].trim() };
|
|
64207
64376
|
}
|
|
64208
|
-
function
|
|
64377
|
+
function escapeXmlAttribute2(s) {
|
|
64209
64378
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
64210
64379
|
}
|
|
64211
64380
|
function decodeXmlEntities(s) {
|
|
@@ -64216,13 +64385,13 @@ function formatPriorAssistantPreview(text4, maxChars = 200) {
|
|
|
64216
64385
|
const decoded = decodeXmlEntities(stripped);
|
|
64217
64386
|
const collapsed = decoded.replace(/\s+/g, " ").trim();
|
|
64218
64387
|
const truncated = collapsed.length > maxChars ? collapsed.slice(0, maxChars) : collapsed;
|
|
64219
|
-
return
|
|
64388
|
+
return escapeXmlAttribute2(truncated);
|
|
64220
64389
|
}
|
|
64221
64390
|
function formatReplyToText(text4, maxChars) {
|
|
64222
64391
|
if (text4 == null)
|
|
64223
64392
|
return;
|
|
64224
64393
|
const truncated = text4.length > maxChars ? text4.slice(0, maxChars - 1) + "\u2026" : text4;
|
|
64225
|
-
return
|
|
64394
|
+
return escapeXmlAttribute2(truncated);
|
|
64226
64395
|
}
|
|
64227
64396
|
|
|
64228
64397
|
// gateway/auto-classify-mid-turn.ts
|
|
@@ -64471,6 +64640,26 @@ var transientUpstreamSignals = [
|
|
|
64471
64640
|
"would exceed your account\u2019s rate limit",
|
|
64472
64641
|
"would exceed your account's rate limit"
|
|
64473
64642
|
];
|
|
64643
|
+
var litellmProxyLocal429Signals = [
|
|
64644
|
+
"deployment over user-defined ratelimit",
|
|
64645
|
+
"model rate limit exceeded. tpm limit",
|
|
64646
|
+
"model rate limit exceeded. rpm limit",
|
|
64647
|
+
"deployment over defined rpm limit",
|
|
64648
|
+
"no deployments available for selected model",
|
|
64649
|
+
"litellm rate limit handler",
|
|
64650
|
+
"crossed tpm / rpm",
|
|
64651
|
+
"max parallel request limit reached"
|
|
64652
|
+
];
|
|
64653
|
+
var litellmV3LimiterSignalPair = ["rate limit exceeded for ", "limit type:"];
|
|
64654
|
+
function isLitellmProxyLocal429(text4) {
|
|
64655
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
64656
|
+
return false;
|
|
64657
|
+
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
64658
|
+
const lower = sample.toLowerCase();
|
|
64659
|
+
if (litellmProxyLocal429Signals.some((s) => lower.includes(s)))
|
|
64660
|
+
return true;
|
|
64661
|
+
return litellmV3LimiterSignalPair.every((s) => lower.includes(s));
|
|
64662
|
+
}
|
|
64474
64663
|
function detectModelUnavailable(stderr) {
|
|
64475
64664
|
if (typeof stderr !== "string" || stderr.length === 0)
|
|
64476
64665
|
return null;
|
|
@@ -64480,6 +64669,10 @@ function detectModelUnavailable(stderr) {
|
|
|
64480
64669
|
const resetAt = parseResetTime(sample);
|
|
64481
64670
|
return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
|
|
64482
64671
|
}
|
|
64672
|
+
if (isLitellmProxyLocal429(sample)) {
|
|
64673
|
+
const resetAt = parseResetTime(sample);
|
|
64674
|
+
return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
|
|
64675
|
+
}
|
|
64483
64676
|
const quotaSignals = [
|
|
64484
64677
|
"out of extra usage",
|
|
64485
64678
|
"extra usage",
|
|
@@ -64684,6 +64877,8 @@ function formatReason(d, now) {
|
|
|
64684
64877
|
return `quota exhausted${reset2}`;
|
|
64685
64878
|
case "overload":
|
|
64686
64879
|
return `model overloaded${reset2}`;
|
|
64880
|
+
case "rate_limited":
|
|
64881
|
+
return `account rate-limited${reset2}`;
|
|
64687
64882
|
case "network":
|
|
64688
64883
|
return "network unreachable";
|
|
64689
64884
|
}
|
|
@@ -64703,6 +64898,433 @@ function resolveModelUnavailableFromOperatorEvent(ev) {
|
|
|
64703
64898
|
return detectModelUnavailable(detail);
|
|
64704
64899
|
}
|
|
64705
64900
|
|
|
64901
|
+
// throttle-tier.ts
|
|
64902
|
+
init_card_format();
|
|
64903
|
+
init_quota_check();
|
|
64904
|
+
|
|
64905
|
+
// model-unavailable.ts
|
|
64906
|
+
init_quota_check();
|
|
64907
|
+
init_card_format();
|
|
64908
|
+
var transientUpstreamSignals2 = [
|
|
64909
|
+
"not your usage limit",
|
|
64910
|
+
"not your account",
|
|
64911
|
+
"not your account's",
|
|
64912
|
+
"temporarily limiting requests",
|
|
64913
|
+
"temporarily rate",
|
|
64914
|
+
"server is temporarily",
|
|
64915
|
+
"would exceed your account\u2019s rate limit",
|
|
64916
|
+
"would exceed your account's rate limit"
|
|
64917
|
+
];
|
|
64918
|
+
function isTransientUpstreamSignal(text4) {
|
|
64919
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
64920
|
+
return false;
|
|
64921
|
+
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
64922
|
+
const lower = sample.toLowerCase();
|
|
64923
|
+
return transientUpstreamSignals2.some((s) => lower.includes(s));
|
|
64924
|
+
}
|
|
64925
|
+
var litellmProxyLocal429Signals2 = [
|
|
64926
|
+
"deployment over user-defined ratelimit",
|
|
64927
|
+
"model rate limit exceeded. tpm limit",
|
|
64928
|
+
"model rate limit exceeded. rpm limit",
|
|
64929
|
+
"deployment over defined rpm limit",
|
|
64930
|
+
"no deployments available for selected model",
|
|
64931
|
+
"litellm rate limit handler",
|
|
64932
|
+
"crossed tpm / rpm",
|
|
64933
|
+
"max parallel request limit reached"
|
|
64934
|
+
];
|
|
64935
|
+
var litellmV3LimiterSignalPair2 = ["rate limit exceeded for ", "limit type:"];
|
|
64936
|
+
function isLitellmProxyLocal4292(text4) {
|
|
64937
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
64938
|
+
return false;
|
|
64939
|
+
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
64940
|
+
const lower = sample.toLowerCase();
|
|
64941
|
+
if (litellmProxyLocal429Signals2.some((s) => lower.includes(s)))
|
|
64942
|
+
return true;
|
|
64943
|
+
return litellmV3LimiterSignalPair2.every((s) => lower.includes(s));
|
|
64944
|
+
}
|
|
64945
|
+
function parseLitellmLimitDetail(text4, parseTimeNow = new Date) {
|
|
64946
|
+
const empty2 = { limitType: null, limit: null, currentUsage: null, resetAtMs: null };
|
|
64947
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
64948
|
+
return empty2;
|
|
64949
|
+
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
64950
|
+
const lower = sample.toLowerCase();
|
|
64951
|
+
let limitType = null;
|
|
64952
|
+
let limit = null;
|
|
64953
|
+
const eqLimit = lower.match(/\b([tr]pm)[ _]limit[=:]\s*(\d+)/);
|
|
64954
|
+
if (eqLimit) {
|
|
64955
|
+
limitType = eqLimit[1];
|
|
64956
|
+
limit = Number(eqLimit[2]);
|
|
64957
|
+
}
|
|
64958
|
+
if (limitType == null) {
|
|
64959
|
+
const v3Type = lower.match(/limit type:\s*(tokens|requests|max_parallel_requests)/);
|
|
64960
|
+
if (v3Type)
|
|
64961
|
+
limitType = v3Type[1];
|
|
64962
|
+
}
|
|
64963
|
+
if (limit == null) {
|
|
64964
|
+
const v3Limit = lower.match(/current limit:\s*(\d+)/);
|
|
64965
|
+
if (v3Limit)
|
|
64966
|
+
limit = Number(v3Limit[1]);
|
|
64967
|
+
}
|
|
64968
|
+
let currentUsage = null;
|
|
64969
|
+
const usage = lower.match(/current usage=(\d+)/);
|
|
64970
|
+
if (usage)
|
|
64971
|
+
currentUsage = Number(usage[1]);
|
|
64972
|
+
let resetAtMs = null;
|
|
64973
|
+
const resetsAt = sample.match(/limit resets at:\s*(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) UTC/i);
|
|
64974
|
+
if (resetsAt) {
|
|
64975
|
+
const d = new Date(`${resetsAt[1]}T${resetsAt[2]}Z`);
|
|
64976
|
+
if (!Number.isNaN(d.getTime()))
|
|
64977
|
+
resetAtMs = d.getTime();
|
|
64978
|
+
}
|
|
64979
|
+
if (resetAtMs == null) {
|
|
64980
|
+
const tryAgain = lower.match(/try again in\s+(\d+(?:\.\d+)?)\s*seconds/);
|
|
64981
|
+
if (tryAgain) {
|
|
64982
|
+
const secs = Number(tryAgain[1]);
|
|
64983
|
+
if (Number.isFinite(secs) && secs > 0 && secs < 7 * 24 * 3600) {
|
|
64984
|
+
resetAtMs = parseTimeNow.getTime() + Math.round(secs * 1000);
|
|
64985
|
+
}
|
|
64986
|
+
}
|
|
64987
|
+
}
|
|
64988
|
+
return {
|
|
64989
|
+
limitType,
|
|
64990
|
+
limit: limit != null && Number.isFinite(limit) ? limit : null,
|
|
64991
|
+
currentUsage: currentUsage != null && Number.isFinite(currentUsage) ? currentUsage : null,
|
|
64992
|
+
resetAtMs
|
|
64993
|
+
};
|
|
64994
|
+
}
|
|
64995
|
+
function parseResetTime2(text4, parseTimeNow = new Date) {
|
|
64996
|
+
const lower = text4.toLowerCase();
|
|
64997
|
+
const retryAfter = lower.match(/retry[\s-]*after[:\s]+(\d+)\s*(seconds?|s\b|minutes?|m\b|hours?|h\b)?/);
|
|
64998
|
+
if (retryAfter) {
|
|
64999
|
+
const n = Number(retryAfter[1]);
|
|
65000
|
+
if (Number.isFinite(n) && n > 0 && n < 7 * 24 * 3600) {
|
|
65001
|
+
const unit = (retryAfter[2] ?? "seconds").toLowerCase();
|
|
65002
|
+
const ms = unit.startsWith("h") ? n * 3600000 : unit.startsWith("m") ? n * 60000 : n * 1000;
|
|
65003
|
+
return new Date(parseTimeNow.getTime() + ms);
|
|
65004
|
+
}
|
|
65005
|
+
}
|
|
65006
|
+
const relReset = lower.match(/resets?\s+in\s+([0-9hms\s]+)/);
|
|
65007
|
+
if (relReset) {
|
|
65008
|
+
const ms = parseRelativeDuration2(relReset[1]);
|
|
65009
|
+
if (ms != null)
|
|
65010
|
+
return new Date(parseTimeNow.getTime() + ms);
|
|
65011
|
+
}
|
|
65012
|
+
const iso = text4.match(/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b/);
|
|
65013
|
+
if (iso) {
|
|
65014
|
+
const d = new Date(iso[0]);
|
|
65015
|
+
if (!Number.isNaN(d.getTime()))
|
|
65016
|
+
return d;
|
|
65017
|
+
}
|
|
65018
|
+
const calReset = text4.match(/resets?\s+(?:at\s+)?([A-Z][a-z]{2,8}\s+\d{1,2}(?:,?\s*(?:\d{1,2}(?::\d{2})?\s*(?:am|pm|AM|PM)?))?)/);
|
|
65019
|
+
if (calReset) {
|
|
65020
|
+
const candidate = `${calReset[1]} ${parseTimeNow.getUTCFullYear()}`;
|
|
65021
|
+
const d = new Date(candidate);
|
|
65022
|
+
if (!Number.isNaN(d.getTime()))
|
|
65023
|
+
return d;
|
|
65024
|
+
}
|
|
65025
|
+
const timeOnly = text4.match(/resets?\s+(?:at\s+)?(?!(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b)(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*(?:\(([^)]+)\))?/i);
|
|
65026
|
+
if (timeOnly) {
|
|
65027
|
+
const d = resolveNextWallClock2(Number(timeOnly[1]), timeOnly[2] ? Number(timeOnly[2]) : 0, timeOnly[3]?.toLowerCase(), timeOnly[4]?.trim(), parseTimeNow);
|
|
65028
|
+
if (d != null)
|
|
65029
|
+
return d;
|
|
65030
|
+
}
|
|
65031
|
+
return;
|
|
65032
|
+
}
|
|
65033
|
+
function resolveNextWallClock2(hour12or24, minute, ampm, tz, nowDate) {
|
|
65034
|
+
let hour = hour12or24;
|
|
65035
|
+
if (ampm === "pm" && hour < 12)
|
|
65036
|
+
hour += 12;
|
|
65037
|
+
if (ampm === "am" && hour === 12)
|
|
65038
|
+
hour = 0;
|
|
65039
|
+
if (!Number.isFinite(hour) || hour > 23 || hour < 0)
|
|
65040
|
+
return;
|
|
65041
|
+
if (!Number.isFinite(minute) || minute > 59 || minute < 0)
|
|
65042
|
+
return;
|
|
65043
|
+
const nowMs2 = nowDate.getTime();
|
|
65044
|
+
const base = new Date(nowMs2);
|
|
65045
|
+
for (let dayOffset = 0;dayOffset <= 2; dayOffset++) {
|
|
65046
|
+
const dateParts = tzDateParts2(new Date(nowMs2 + dayOffset * 86400000), tz);
|
|
65047
|
+
if (dateParts == null)
|
|
65048
|
+
return;
|
|
65049
|
+
const epoch = wallClockToEpoch2(dateParts.year, dateParts.month, dateParts.day, hour, minute, tz);
|
|
65050
|
+
if (epoch != null && epoch > nowMs2)
|
|
65051
|
+
return new Date(epoch);
|
|
65052
|
+
}
|
|
65053
|
+
return;
|
|
65054
|
+
}
|
|
65055
|
+
function tzDateParts2(d, tz) {
|
|
65056
|
+
if (!tz) {
|
|
65057
|
+
return { year: d.getUTCFullYear(), month: d.getUTCMonth(), day: d.getUTCDate() };
|
|
65058
|
+
}
|
|
65059
|
+
try {
|
|
65060
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
65061
|
+
timeZone: tz,
|
|
65062
|
+
year: "numeric",
|
|
65063
|
+
month: "2-digit",
|
|
65064
|
+
day: "2-digit"
|
|
65065
|
+
});
|
|
65066
|
+
const parts = Object.fromEntries(fmt.formatToParts(d).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
|
|
65067
|
+
return {
|
|
65068
|
+
year: Number(parts.year),
|
|
65069
|
+
month: Number(parts.month) - 1,
|
|
65070
|
+
day: Number(parts.day)
|
|
65071
|
+
};
|
|
65072
|
+
} catch {
|
|
65073
|
+
return null;
|
|
65074
|
+
}
|
|
65075
|
+
}
|
|
65076
|
+
function wallClockToEpoch2(year, month, day, hour, minute, tz) {
|
|
65077
|
+
const asUtc = Date.UTC(year, month, day, hour, minute, 0);
|
|
65078
|
+
if (!tz)
|
|
65079
|
+
return asUtc;
|
|
65080
|
+
try {
|
|
65081
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
65082
|
+
timeZone: tz,
|
|
65083
|
+
year: "numeric",
|
|
65084
|
+
month: "2-digit",
|
|
65085
|
+
day: "2-digit",
|
|
65086
|
+
hour: "2-digit",
|
|
65087
|
+
minute: "2-digit",
|
|
65088
|
+
second: "2-digit",
|
|
65089
|
+
hour12: false
|
|
65090
|
+
});
|
|
65091
|
+
const parts = Object.fromEntries(fmt.formatToParts(new Date(asUtc)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
|
|
65092
|
+
const shown = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour) % 24, Number(parts.minute), Number(parts.second));
|
|
65093
|
+
const offset = shown - asUtc;
|
|
65094
|
+
return asUtc - offset;
|
|
65095
|
+
} catch {
|
|
65096
|
+
return null;
|
|
65097
|
+
}
|
|
65098
|
+
}
|
|
65099
|
+
function parseRelativeDuration2(s) {
|
|
65100
|
+
let total = 0;
|
|
65101
|
+
let matched = false;
|
|
65102
|
+
const re = /(\d+)\s*(h|hours?|m|minutes?|s|seconds?)/g;
|
|
65103
|
+
let m;
|
|
65104
|
+
while ((m = re.exec(s)) != null) {
|
|
65105
|
+
matched = true;
|
|
65106
|
+
const n = Number(m[1]);
|
|
65107
|
+
const unit = m[2].toLowerCase();
|
|
65108
|
+
if (unit.startsWith("h"))
|
|
65109
|
+
total += n * 3600000;
|
|
65110
|
+
else if (unit.startsWith("m"))
|
|
65111
|
+
total += n * 60000;
|
|
65112
|
+
else
|
|
65113
|
+
total += n * 1000;
|
|
65114
|
+
}
|
|
65115
|
+
return matched && total > 0 ? total : null;
|
|
65116
|
+
}
|
|
65117
|
+
|
|
65118
|
+
// throttle-tier.ts
|
|
65119
|
+
var accountScopedThrottleSignals = [
|
|
65120
|
+
"would exceed your account's rate limit",
|
|
65121
|
+
"would exceed your account\u2019s rate limit",
|
|
65122
|
+
"not your account"
|
|
65123
|
+
];
|
|
65124
|
+
function isAccountScopedThrottle(text4) {
|
|
65125
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
65126
|
+
return false;
|
|
65127
|
+
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
65128
|
+
const lower = sample.toLowerCase();
|
|
65129
|
+
return accountScopedThrottleSignals.some((s) => lower.includes(s));
|
|
65130
|
+
}
|
|
65131
|
+
function classify429Detail(text4) {
|
|
65132
|
+
if (isAccountScopedThrottle(text4))
|
|
65133
|
+
return "account-scoped";
|
|
65134
|
+
if (isLitellmProxyLocal4292(text4))
|
|
65135
|
+
return "litellm-local";
|
|
65136
|
+
return "generic-transient";
|
|
65137
|
+
}
|
|
65138
|
+
function build429ClassifiedMetric(opts) {
|
|
65139
|
+
const detail = typeof opts.detail === "string" ? opts.detail : "";
|
|
65140
|
+
const litellm = parseLitellmLimitDetail(detail, new Date(opts.now));
|
|
65141
|
+
const anthropicResetMs = parseResetTime2(detail, new Date(opts.now))?.getTime() ?? null;
|
|
65142
|
+
const resetAtMs = anthropicResetMs ?? litellm.resetAtMs;
|
|
65143
|
+
return {
|
|
65144
|
+
kind: "rate_limit_429_classified",
|
|
65145
|
+
agent: opts.agent,
|
|
65146
|
+
classification: opts.classification,
|
|
65147
|
+
action: opts.action,
|
|
65148
|
+
reset_at_ms: resetAtMs,
|
|
65149
|
+
reset_in_ms: resetAtMs != null ? Math.max(0, resetAtMs - opts.now) : null,
|
|
65150
|
+
limit_type: litellm.limitType,
|
|
65151
|
+
limit: litellm.limit,
|
|
65152
|
+
current_usage: litellm.currentUsage
|
|
65153
|
+
};
|
|
65154
|
+
}
|
|
65155
|
+
var THROTTLE_RETRY_IN_PLACE_MAX_MS_DEFAULT = 5 * 60000;
|
|
65156
|
+
var THROTTLE_DEFAULT_WAIT_MS = 60000;
|
|
65157
|
+
function throttleRetryInPlaceMaxMs(env = process.env) {
|
|
65158
|
+
const raw = env.SWITCHROOM_THROTTLE_RETRY_IN_PLACE_MAX_MS;
|
|
65159
|
+
if (raw == null || raw === "")
|
|
65160
|
+
return THROTTLE_RETRY_IN_PLACE_MAX_MS_DEFAULT;
|
|
65161
|
+
const n = Number(raw);
|
|
65162
|
+
return Number.isFinite(n) && n > 0 ? n : THROTTLE_RETRY_IN_PLACE_MAX_MS_DEFAULT;
|
|
65163
|
+
}
|
|
65164
|
+
function decideThrottleTier(opts) {
|
|
65165
|
+
const { detail, now, thresholdMs } = opts;
|
|
65166
|
+
if (!isAccountScopedThrottle(detail))
|
|
65167
|
+
return { action: "none" };
|
|
65168
|
+
const resetAt = parseResetTime2(detail, new Date(now));
|
|
65169
|
+
const resetAtMs = resetAt?.getTime();
|
|
65170
|
+
if (resetAtMs == null || !Number.isFinite(resetAtMs) || resetAtMs <= now) {
|
|
65171
|
+
return {
|
|
65172
|
+
action: "throttle",
|
|
65173
|
+
throttledUntilMs: now + THROTTLE_DEFAULT_WAIT_MS,
|
|
65174
|
+
resetParsed: false
|
|
65175
|
+
};
|
|
65176
|
+
}
|
|
65177
|
+
if (resetAtMs - now <= thresholdMs) {
|
|
65178
|
+
return { action: "throttle", throttledUntilMs: resetAtMs, resetParsed: true };
|
|
65179
|
+
}
|
|
65180
|
+
return { action: "failover", resetAtMs };
|
|
65181
|
+
}
|
|
65182
|
+
var THROTTLE_NOTICE_COOLDOWN_MS = 10 * 60000;
|
|
65183
|
+
|
|
65184
|
+
// throttle-tier.ts
|
|
65185
|
+
init_card_format();
|
|
65186
|
+
init_quota_check();
|
|
65187
|
+
var THROTTLE_RETRY_IN_PLACE_MAX_MS_DEFAULT2 = 5 * 60000;
|
|
65188
|
+
var THROTTLE_NOTICE_COOLDOWN_MS2 = 10 * 60000;
|
|
65189
|
+
function evaluateThrottleNotice(prev, account, now, cooldownMs = THROTTLE_NOTICE_COOLDOWN_MS2) {
|
|
65190
|
+
const last = prev.lastSentAtMsByAccount[account] ?? 0;
|
|
65191
|
+
if (now - last >= cooldownMs) {
|
|
65192
|
+
return {
|
|
65193
|
+
send: true,
|
|
65194
|
+
next: {
|
|
65195
|
+
lastSentAtMsByAccount: {
|
|
65196
|
+
...prev.lastSentAtMsByAccount,
|
|
65197
|
+
[account]: now
|
|
65198
|
+
}
|
|
65199
|
+
}
|
|
65200
|
+
};
|
|
65201
|
+
}
|
|
65202
|
+
return { send: false, next: prev };
|
|
65203
|
+
}
|
|
65204
|
+
function renderThrottleNotice(opts) {
|
|
65205
|
+
const now = opts.now ?? new Date;
|
|
65206
|
+
const resetStr = formatResetRelative(new Date(opts.throttledUntilMs), now);
|
|
65207
|
+
const acct = opts.account ? `\`${escapeMarkdown(opts.account)}\`` : "the active account";
|
|
65208
|
+
const lines = [
|
|
65209
|
+
`\uD83D\uDEA6 **Rate-limited, staying put** \u2014 ${acct} hit a transient rate limit on **${escapeMarkdown(opts.agent)}**.`,
|
|
65210
|
+
`This is a short throttle, not a quota wall \u2014 ${opts.resetParsed ? resetStr : `no reset given, retrying in ~60s`}.`,
|
|
65211
|
+
`_Staying on ${acct}; no failover needed. The turn retries automatically after the reset._`
|
|
65212
|
+
];
|
|
65213
|
+
return lines.join(`
|
|
65214
|
+
`);
|
|
65215
|
+
}
|
|
65216
|
+
function renderThrottleEscalationNotice(opts) {
|
|
65217
|
+
const acct = opts.account ? `\`${escapeMarkdown(opts.account)}\`` : "the active account";
|
|
65218
|
+
const head = `\u26d4\ufe0f **Rate limit was actually a wall** \u2014 repeated 429s on ${acct} ` + `(trigger: **${escapeMarkdown(opts.agent)}**) were corroborated by a live quota probe.`;
|
|
65219
|
+
const tail = opts.rolledTo ? `Marked exhausted and rolled to \`${escapeMarkdown(opts.rolledTo)}\`.` : `Marked exhausted \u2014 no fallback account had quota (all blocked). ` + `Use \`/auth add <label>\` to attach another subscription.`;
|
|
65220
|
+
return `${head}
|
|
65221
|
+
${tail}`;
|
|
65222
|
+
}
|
|
65223
|
+
|
|
65224
|
+
// gateway/throttle-tier-wiring.ts
|
|
65225
|
+
var THROTTLE_RETRY_NUDGE_SLACK_MS = 5000;
|
|
65226
|
+
var THROTTLE_RETRY_NUDGE_JITTER_MAX_MS = 30000;
|
|
65227
|
+
function createThrottleTierRunner(deps) {
|
|
65228
|
+
const now = deps.now ?? (() => Date.now());
|
|
65229
|
+
const schedule = deps.schedule ?? ((fn, ms) => {
|
|
65230
|
+
const t = setTimeout(fn, ms);
|
|
65231
|
+
if (typeof t.unref === "function")
|
|
65232
|
+
t.unref();
|
|
65233
|
+
return { cancel: () => clearTimeout(t) };
|
|
65234
|
+
});
|
|
65235
|
+
const jitterMs = deps.jitterMs ?? (() => Math.floor(Math.random() * THROTTLE_RETRY_NUDGE_JITTER_MAX_MS));
|
|
65236
|
+
let noticeState = { lastSentAtMsByAccount: {} };
|
|
65237
|
+
let pendingNudge = null;
|
|
65238
|
+
async function broadcastDeduped(client3, keyPrefix, account, markdown) {
|
|
65239
|
+
for (const chatId of deps.listNoticeChats()) {
|
|
65240
|
+
let granted = true;
|
|
65241
|
+
if (client3 && account) {
|
|
65242
|
+
try {
|
|
65243
|
+
granted = (await client3.claimNotification(`${keyPrefix}:${account}:${chatId}`, THROTTLE_NOTICE_COOLDOWN_MS2)).granted;
|
|
65244
|
+
} catch {
|
|
65245
|
+
granted = true;
|
|
65246
|
+
}
|
|
65247
|
+
}
|
|
65248
|
+
if (granted) {
|
|
65249
|
+
deps.sendNotice(chatId, markdown);
|
|
65250
|
+
} else {
|
|
65251
|
+
deps.log(`[throttle-tier] notice suppressed (fleet claim) chat=${chatId}`);
|
|
65252
|
+
}
|
|
65253
|
+
}
|
|
65254
|
+
}
|
|
65255
|
+
function nudgeResume(reason, armedAtMs) {
|
|
65256
|
+
const newest = deps.newestActiveTurnStartedAtMs();
|
|
65257
|
+
if (deps.turnInFlight()) {
|
|
65258
|
+
if (newest != null && newest > armedAtMs) {
|
|
65259
|
+
deps.log(`[throttle-tier] resume skipped (superseded by a live newer turn) reason=${reason}`);
|
|
65260
|
+
return;
|
|
65261
|
+
}
|
|
65262
|
+
deps.log(`[throttle-tier] resume deferred to turn-complete reason=${reason}`);
|
|
65263
|
+
deps.deferRestartToTurnComplete(deps.agentName, reason);
|
|
65264
|
+
return;
|
|
65265
|
+
}
|
|
65266
|
+
const verdict = deps.resumeDecide(newest);
|
|
65267
|
+
if (verdict === "resume") {
|
|
65268
|
+
deps.log(`[throttle-tier] resuming dead turn via self-restart reason=${reason}`);
|
|
65269
|
+
deps.restartNow(deps.agentName, reason);
|
|
65270
|
+
} else {
|
|
65271
|
+
deps.log(`[throttle-tier] resume suppressed (${verdict}) reason=${reason}`);
|
|
65272
|
+
}
|
|
65273
|
+
}
|
|
65274
|
+
async function fire(triggerAgent, throttledUntilMs, resetParsed) {
|
|
65275
|
+
const armedAtMs = now();
|
|
65276
|
+
let client3 = null;
|
|
65277
|
+
let account = null;
|
|
65278
|
+
let escalated = false;
|
|
65279
|
+
let rolledTo = null;
|
|
65280
|
+
try {
|
|
65281
|
+
client3 = await deps.getBrokerClient();
|
|
65282
|
+
if (client3) {
|
|
65283
|
+
const r = await client3.markThrottled(throttledUntilMs);
|
|
65284
|
+
account = r.account;
|
|
65285
|
+
escalated = r.escalated;
|
|
65286
|
+
rolledTo = r.rolledTo ?? null;
|
|
65287
|
+
} else {
|
|
65288
|
+
deps.log(`[throttle-tier] broker unreachable \u2014 notice only, no ledger record agent=${triggerAgent}`);
|
|
65289
|
+
}
|
|
65290
|
+
} catch (err) {
|
|
65291
|
+
deps.log(`[throttle-tier] markThrottled failed agent=${triggerAgent}: ${err?.message ?? err}`);
|
|
65292
|
+
}
|
|
65293
|
+
if (escalated) {
|
|
65294
|
+
deps.log(`[throttle-tier] escalated to wall account=${account ?? "?"} ` + `rolledTo=${rolledTo ?? "none (all blocked)"}`);
|
|
65295
|
+
await broadcastDeduped(client3, "throttle-escalation", account, renderThrottleEscalationNotice({ account, agent: triggerAgent, rolledTo }));
|
|
65296
|
+
if (rolledTo)
|
|
65297
|
+
nudgeResume("throttle-escalation-resume", armedAtMs);
|
|
65298
|
+
return;
|
|
65299
|
+
}
|
|
65300
|
+
const cooldownKey = account ?? `agent:${triggerAgent}`;
|
|
65301
|
+
const verdict = evaluateThrottleNotice(noticeState, cooldownKey, now());
|
|
65302
|
+
if (verdict.send) {
|
|
65303
|
+
noticeState = verdict.next;
|
|
65304
|
+
await broadcastDeduped(client3, "throttle-notice", account, renderThrottleNotice({
|
|
65305
|
+
account,
|
|
65306
|
+
agent: triggerAgent,
|
|
65307
|
+
throttledUntilMs,
|
|
65308
|
+
resetParsed,
|
|
65309
|
+
now: new Date(now())
|
|
65310
|
+
}));
|
|
65311
|
+
} else {
|
|
65312
|
+
deps.log(`[throttle-tier] notice suppressed (cooldown) key=${cooldownKey}`);
|
|
65313
|
+
}
|
|
65314
|
+
const delayMs = Math.max(throttledUntilMs - now(), 0) + THROTTLE_RETRY_NUDGE_SLACK_MS + jitterMs();
|
|
65315
|
+
if (pendingNudge)
|
|
65316
|
+
pendingNudge.cancel();
|
|
65317
|
+
pendingNudge = schedule(() => {
|
|
65318
|
+
pendingNudge = null;
|
|
65319
|
+
nudgeResume("throttle-retry-resume", armedAtMs);
|
|
65320
|
+
}, delayMs);
|
|
65321
|
+
}
|
|
65322
|
+
return {
|
|
65323
|
+
fire,
|
|
65324
|
+
inspect: () => ({ noticeState, nudgePending: pendingNudge != null })
|
|
65325
|
+
};
|
|
65326
|
+
}
|
|
65327
|
+
|
|
64706
65328
|
// auto-fallback-fleet.ts
|
|
64707
65329
|
init_card_format();
|
|
64708
65330
|
init_auth_snapshot_format();
|
|
@@ -64738,7 +65360,7 @@ async function runFleetAutoFallback(deps) {
|
|
|
64738
65360
|
};
|
|
64739
65361
|
}
|
|
64740
65362
|
const oldHealth = classifyHealth(oldSnap, now);
|
|
64741
|
-
if (oldHealth === "healthy") {
|
|
65363
|
+
if (oldHealth === "healthy" && !deps.rateLimitTrigger) {
|
|
64742
65364
|
return {
|
|
64743
65365
|
kind: "no-eligible-target",
|
|
64744
65366
|
oldLabel: oldSnap.label,
|
|
@@ -64759,6 +65381,8 @@ async function runFleetAutoFallback(deps) {
|
|
|
64759
65381
|
newQuota: null,
|
|
64760
65382
|
triggerAgent: deps.triggerAgent,
|
|
64761
65383
|
fleetSnapshots: snapshots,
|
|
65384
|
+
parsedResetAt: deps.parsedResetAt ?? null,
|
|
65385
|
+
cause: deps.rateLimitTrigger ? "rate-limit" : undefined,
|
|
64762
65386
|
tz,
|
|
64763
65387
|
now
|
|
64764
65388
|
})
|
|
@@ -64777,6 +65401,8 @@ async function runFleetAutoFallback(deps) {
|
|
|
64777
65401
|
newLabel: rolledTo,
|
|
64778
65402
|
newQuota,
|
|
64779
65403
|
triggerAgent: deps.triggerAgent,
|
|
65404
|
+
parsedResetAt: deps.parsedResetAt ?? null,
|
|
65405
|
+
cause: deps.rateLimitTrigger ? "rate-limit" : undefined,
|
|
64780
65406
|
tz,
|
|
64781
65407
|
now
|
|
64782
65408
|
})
|
|
@@ -66830,13 +67456,13 @@ function buildFolderPickerCard(input) {
|
|
|
66830
67456
|
validateDriveId(folder.id, "folder.id");
|
|
66831
67457
|
rows.push([
|
|
66832
67458
|
{
|
|
66833
|
-
text: `\u2705 Allow "${
|
|
67459
|
+
text: `\u2705 Allow "${truncateName2(folder.name, 48)}"`,
|
|
66834
67460
|
callback_data: encodeCallback(["drvpick", "grant", input.agent, folder.id])
|
|
66835
67461
|
}
|
|
66836
67462
|
]);
|
|
66837
67463
|
rows.push([
|
|
66838
67464
|
{
|
|
66839
|
-
text: `\uD83D\uDCC2 Browse "${
|
|
67465
|
+
text: `\uD83D\uDCC2 Browse "${truncateName2(folder.name, 46)}"`,
|
|
66840
67466
|
callback_data: encodeCallback(["drvpick", "enter", input.agent, folder.id])
|
|
66841
67467
|
}
|
|
66842
67468
|
]);
|
|
@@ -66885,14 +67511,14 @@ function buildFolderPickerCard(input) {
|
|
|
66885
67511
|
return { body, rows };
|
|
66886
67512
|
}
|
|
66887
67513
|
function renderBreadcrumb(trail2, leafName) {
|
|
66888
|
-
const leaf =
|
|
67514
|
+
const leaf = truncateName2(leafName, 24);
|
|
66889
67515
|
if (trail2.length === 0)
|
|
66890
67516
|
return `/${leaf}`;
|
|
66891
|
-
const segments = trail2.map((t) =>
|
|
67517
|
+
const segments = trail2.map((t) => truncateName2(t.name, 20));
|
|
66892
67518
|
segments.push(leaf);
|
|
66893
67519
|
return `/${segments.join("/")}`;
|
|
66894
67520
|
}
|
|
66895
|
-
function
|
|
67521
|
+
function truncateName2(name, max) {
|
|
66896
67522
|
if (name.length <= max)
|
|
66897
67523
|
return name;
|
|
66898
67524
|
return `${name.slice(0, max - 1)}\u2026`;
|
|
@@ -68918,6 +69544,11 @@ function extractConfirmation(pane) {
|
|
|
68918
69544
|
// ../src/agents/scaffold.ts
|
|
68919
69545
|
import { join as join32, resolve as resolve6 } from "node:path";
|
|
68920
69546
|
init_atomic();
|
|
69547
|
+
|
|
69548
|
+
// ../src/agents/agent-uid.ts
|
|
69549
|
+
init_peercred();
|
|
69550
|
+
|
|
69551
|
+
// ../src/agents/scaffold.ts
|
|
68921
69552
|
init_schema();
|
|
68922
69553
|
|
|
68923
69554
|
// ../src/config/users.ts
|
|
@@ -76405,27 +77036,6 @@ function getNestedObj(obj, key) {
|
|
|
76405
77036
|
var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
|
|
76406
77037
|
var cooldownMap2 = new Map;
|
|
76407
77038
|
|
|
76408
|
-
// model-unavailable.ts
|
|
76409
|
-
init_quota_check();
|
|
76410
|
-
init_card_format();
|
|
76411
|
-
var transientUpstreamSignals2 = [
|
|
76412
|
-
"not your usage limit",
|
|
76413
|
-
"not your account",
|
|
76414
|
-
"not your account's",
|
|
76415
|
-
"temporarily limiting requests",
|
|
76416
|
-
"temporarily rate",
|
|
76417
|
-
"server is temporarily",
|
|
76418
|
-
"would exceed your account\u2019s rate limit",
|
|
76419
|
-
"would exceed your account's rate limit"
|
|
76420
|
-
];
|
|
76421
|
-
function isTransientUpstreamSignal(text4) {
|
|
76422
|
-
if (typeof text4 !== "string" || text4.length === 0)
|
|
76423
|
-
return false;
|
|
76424
|
-
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
76425
|
-
const lower = sample.toLowerCase();
|
|
76426
|
-
return transientUpstreamSignals2.some((s) => lower.includes(s));
|
|
76427
|
-
}
|
|
76428
|
-
|
|
76429
77039
|
// session-tail.ts
|
|
76430
77040
|
function sanitizeCwdToProjectName(cwd) {
|
|
76431
77041
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
@@ -76610,6 +77220,7 @@ function detectErrorInTranscriptLine(line) {
|
|
|
76610
77220
|
const errStr = typeof obj.error === "string" ? obj.error : "";
|
|
76611
77221
|
const text4 = extractAssistantText(obj);
|
|
76612
77222
|
const kind2 = status === 429 ? isTransientUpstreamSignal(`${text4}
|
|
77223
|
+
${errStr}`) || isLitellmProxyLocal4292(`${text4}
|
|
76613
77224
|
${errStr}`) ? "rate-limited" : "quota-exhausted" : classifyClaudeError({ type: errStr, status, message: text4 });
|
|
76614
77225
|
return {
|
|
76615
77226
|
kind: kind2,
|
|
@@ -80470,10 +81081,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
80470
81081
|
}
|
|
80471
81082
|
|
|
80472
81083
|
// ../src/build-info.ts
|
|
80473
|
-
var VERSION = "0.18.
|
|
80474
|
-
var COMMIT_SHA = "
|
|
80475
|
-
var COMMIT_DATE = "2026-07-
|
|
80476
|
-
var LATEST_PR =
|
|
81084
|
+
var VERSION = "0.18.15";
|
|
81085
|
+
var COMMIT_SHA = "2fa611a1";
|
|
81086
|
+
var COMMIT_DATE = "2026-07-12T04:44:56Z";
|
|
81087
|
+
var LATEST_PR = 3171;
|
|
80477
81088
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
80478
81089
|
|
|
80479
81090
|
// gateway/boot-version.ts
|
|
@@ -85140,22 +85751,71 @@ var inboundCoalescer = createInboundCoalescer({
|
|
|
85140
85751
|
merge: (entries) => {
|
|
85141
85752
|
const last = entries[entries.length - 1];
|
|
85142
85753
|
const { primary, extras } = splitCoalescedAttachments(entries, (e) => e.downloadImage != null || e.attachment != null, coalesceMaxAttachments());
|
|
85754
|
+
const forwardOrigins = dedupeForwardOrigins(entries.map((e) => e.forwardOrigin));
|
|
85143
85755
|
return {
|
|
85144
85756
|
text: entries.map((e) => e.text).filter((t) => t.length > 0).join(`
|
|
85145
85757
|
`),
|
|
85146
85758
|
ctx: last.ctx,
|
|
85147
85759
|
downloadImage: primary?.downloadImage,
|
|
85148
85760
|
attachment: primary?.attachment,
|
|
85149
|
-
extraAttachments: extras.length > 0 ? extras.map((e) => ({ downloadImage: e.downloadImage, attachment: e.attachment })) : undefined
|
|
85761
|
+
extraAttachments: extras.length > 0 ? extras.map((e) => ({ downloadImage: e.downloadImage, attachment: e.attachment })) : undefined,
|
|
85762
|
+
forwardOrigins: forwardOrigins.length > 0 ? forwardOrigins : undefined
|
|
85150
85763
|
};
|
|
85151
85764
|
},
|
|
85152
85765
|
onFlush: (key, merged) => {
|
|
85153
85766
|
bufferedAttachmentKeys.delete(key);
|
|
85154
|
-
handleInbound(merged.ctx, merged.text, merged.downloadImage, merged.attachment, merged.extraAttachments);
|
|
85767
|
+
handleInbound(merged.ctx, merged.text, merged.downloadImage, merged.attachment, merged.extraAttachments, merged.forwardOrigins);
|
|
85155
85768
|
}
|
|
85156
85769
|
});
|
|
85157
85770
|
function emitGatewayOperatorEvent(event) {
|
|
85158
85771
|
const { agent, kind } = event;
|
|
85772
|
+
let throttleEscalation = null;
|
|
85773
|
+
let escalationFired = false;
|
|
85774
|
+
const rateLimit429Classification = kind === "rate-limited" ? classify429Detail(event.detail) : null;
|
|
85775
|
+
if (rateLimit429Classification != null && rateLimit429Classification !== "account-scoped") {
|
|
85776
|
+
emitRuntimeMetric(build429ClassifiedMetric({
|
|
85777
|
+
agent,
|
|
85778
|
+
detail: event.detail,
|
|
85779
|
+
classification: rateLimit429Classification,
|
|
85780
|
+
action: "calm",
|
|
85781
|
+
now: Date.now()
|
|
85782
|
+
}));
|
|
85783
|
+
if (rateLimit429Classification === "litellm-local") {
|
|
85784
|
+
process.stderr.write(`telegram gateway: 429 classified litellm-proxy-local agent=${agent} \u2014 ` + `calm path, no account attribution, no failover
|
|
85785
|
+
`);
|
|
85786
|
+
}
|
|
85787
|
+
}
|
|
85788
|
+
if (rateLimit429Classification === "account-scoped") {
|
|
85789
|
+
const throttleDecision = decideThrottleTier({
|
|
85790
|
+
detail: event.detail,
|
|
85791
|
+
now: Date.now(),
|
|
85792
|
+
thresholdMs: throttleRetryInPlaceMaxMs()
|
|
85793
|
+
});
|
|
85794
|
+
emitRuntimeMetric(build429ClassifiedMetric({
|
|
85795
|
+
agent,
|
|
85796
|
+
detail: event.detail,
|
|
85797
|
+
classification: "account-scoped",
|
|
85798
|
+
action: throttleDecision.action === "failover" ? "failover" : "throttle",
|
|
85799
|
+
now: Date.now()
|
|
85800
|
+
}));
|
|
85801
|
+
if (throttleDecision.action === "throttle") {
|
|
85802
|
+
process.stderr.write(`telegram gateway: throttle-tier staying-put agent=${agent} until=${new Date(throttleDecision.throttledUntilMs).toISOString()} parsedReset=${throttleDecision.resetParsed}
|
|
85803
|
+
`);
|
|
85804
|
+
try {
|
|
85805
|
+
recordOperatorEvent(event);
|
|
85806
|
+
} catch {}
|
|
85807
|
+
throttleTierRunner.fire(agent, throttleDecision.throttledUntilMs, throttleDecision.resetParsed);
|
|
85808
|
+
return;
|
|
85809
|
+
}
|
|
85810
|
+
if (throttleDecision.action === "failover") {
|
|
85811
|
+
const resetAt = new Date(throttleDecision.resetAtMs);
|
|
85812
|
+
throttleEscalation = { kind: "rate_limited", resetAt, raw: event.detail };
|
|
85813
|
+
if (wouldFireFleetAutoFallback()) {
|
|
85814
|
+
escalationFired = true;
|
|
85815
|
+
fireFleetAutoFallback(agent, resolveExhaustUntil(resetAt.getTime()), resetAt, "rate-limit");
|
|
85816
|
+
}
|
|
85817
|
+
}
|
|
85818
|
+
}
|
|
85159
85819
|
if (!shouldEmitOperatorEvent(agent, kind)) {
|
|
85160
85820
|
process.stderr.write(`telegram gateway: operator-event suppressed (cooldown) agent=${agent} kind=${kind}
|
|
85161
85821
|
`);
|
|
@@ -85167,13 +85827,13 @@ function emitGatewayOperatorEvent(event) {
|
|
|
85167
85827
|
process.stderr.write(`telegram gateway: recordOperatorEvent failed agent=${agent} kind=${kind}: ${err.message}
|
|
85168
85828
|
`);
|
|
85169
85829
|
}
|
|
85170
|
-
const modelUnavailable = resolveModelUnavailableFromOperatorEvent(event);
|
|
85830
|
+
const modelUnavailable = throttleEscalation ?? resolveModelUnavailableFromOperatorEvent(event);
|
|
85171
85831
|
let renderedText;
|
|
85172
85832
|
let renderedKeyboard;
|
|
85173
85833
|
let cardPromisedFallback = false;
|
|
85174
85834
|
if (modelUnavailable) {
|
|
85175
|
-
const isAutoKind = modelUnavailable.kind === "quota_exhausted";
|
|
85176
|
-
const willActuallyFire = isAutoKind && wouldFireFleetAutoFallback();
|
|
85835
|
+
const isAutoKind = modelUnavailable.kind === "quota_exhausted" || modelUnavailable.kind === "rate_limited";
|
|
85836
|
+
const willActuallyFire = throttleEscalation != null ? escalationFired : isAutoKind && wouldFireFleetAutoFallback();
|
|
85177
85837
|
process.stderr.write(`telegram gateway: operator-event suppressing-raw-stderr-for-model-unavailable agent=${agent} kind=${kind} detected=${modelUnavailable.kind} autoKind=${isAutoKind} willFire=${willActuallyFire}
|
|
85178
85838
|
`);
|
|
85179
85839
|
renderedText = formatModelUnavailableCard(modelUnavailable, agent, {
|
|
@@ -85181,9 +85841,9 @@ function emitGatewayOperatorEvent(event) {
|
|
|
85181
85841
|
});
|
|
85182
85842
|
renderedKeyboard = undefined;
|
|
85183
85843
|
cardPromisedFallback = willActuallyFire;
|
|
85184
|
-
if (willActuallyFire) {
|
|
85844
|
+
if (willActuallyFire && throttleEscalation == null) {
|
|
85185
85845
|
const untilMs = resolveExhaustUntil(modelUnavailable.resetAt?.getTime());
|
|
85186
|
-
fireFleetAutoFallback(agent, untilMs);
|
|
85846
|
+
fireFleetAutoFallback(agent, untilMs, modelUnavailable.resetAt);
|
|
85187
85847
|
}
|
|
85188
85848
|
} else {
|
|
85189
85849
|
try {
|
|
@@ -86899,7 +87559,7 @@ var ipcServer = createIpcServer({
|
|
|
86899
87559
|
const untilMs = resolveExhaustUntil(msg.resetAt);
|
|
86900
87560
|
process.stderr.write(`telegram gateway: quota_wall_detected agent=${msg.agentName} until=${new Date(untilMs).toISOString()}` + (msg.resetAt == null ? " (reset unparsed \u2192 +7d default)" : "") + ` \u2014 triggering fleet auto-fallback
|
|
86901
87561
|
`);
|
|
86902
|
-
fireFleetAutoFallback(msg.agentName, untilMs);
|
|
87562
|
+
fireFleetAutoFallback(msg.agentName, untilMs, msg.resetAt != null ? new Date(msg.resetAt) : undefined);
|
|
86903
87563
|
},
|
|
86904
87564
|
onQueryPendingPermission(client3, msg) {
|
|
86905
87565
|
const self = process.env.SWITCHROOM_AGENT_NAME;
|
|
@@ -90392,7 +91052,13 @@ async function handleInboundCoalesced(ctx, text5, downloadImage, attachment) {
|
|
|
90392
91052
|
maybeEarlyAckReaction(ctx, from);
|
|
90393
91053
|
maybePokeFloorForMidTurnInbound(ctx, from);
|
|
90394
91054
|
const key = inboundCoalesceKey(String(ctx.chat.id), ctx.message?.message_thread_id, String(from.id));
|
|
90395
|
-
const result = inboundCoalescer.enqueue(key, {
|
|
91055
|
+
const result = inboundCoalescer.enqueue(key, {
|
|
91056
|
+
text: text5,
|
|
91057
|
+
ctx,
|
|
91058
|
+
downloadImage,
|
|
91059
|
+
attachment,
|
|
91060
|
+
forwardOrigin: parseForwardOrigin(ctx.message?.forward_origin)
|
|
91061
|
+
});
|
|
90396
91062
|
if (result.bypass)
|
|
90397
91063
|
return handleInbound(ctx, text5, downloadImage, attachment);
|
|
90398
91064
|
if (hasAttachment)
|
|
@@ -90430,7 +91096,7 @@ function maybePokeFloorForMidTurnInbound(ctx, from) {
|
|
|
90430
91096
|
return;
|
|
90431
91097
|
pokeFloorNow(key, Date.now());
|
|
90432
91098
|
}
|
|
90433
|
-
async function handleInbound(ctx, text5, downloadImage, attachment, extraAttachments) {
|
|
91099
|
+
async function handleInbound(ctx, text5, downloadImage, attachment, extraAttachments, coalescedForwardOrigins) {
|
|
90434
91100
|
markIdleActivity();
|
|
90435
91101
|
const isTopicMessage = ctx.message?.is_topic_message ?? false;
|
|
90436
91102
|
const messageThreadId = ctx.message?.message_thread_id;
|
|
@@ -91056,6 +91722,9 @@ ${preBlock(write.output)}`;
|
|
|
91056
91722
|
const replyToTextRaw = replyToMsg ? replyToMsg.text ?? replyToMsg.caption ?? undefined : undefined;
|
|
91057
91723
|
const replyToText = replyToTextRaw != null ? replyToTextRaw.length > REPLY_TO_TEXT_MAX ? replyToTextRaw.slice(0, REPLY_TO_TEXT_MAX - 1) + "\u2026" : replyToTextRaw : undefined;
|
|
91058
91724
|
const replyToTextEscaped = formatReplyToText(replyToTextRaw, REPLY_TO_TEXT_MAX);
|
|
91725
|
+
const forwardOrigins = coalescedForwardOrigins ?? dedupeForwardOrigins([parseForwardOrigin(ctx.message?.forward_origin)]);
|
|
91726
|
+
const forwardOriginMeta = buildForwardOriginMeta(forwardOrigins);
|
|
91727
|
+
const primaryForwardOrigin = forwardOrigins[0];
|
|
91059
91728
|
if (HISTORY_ENABLED) {
|
|
91060
91729
|
try {
|
|
91061
91730
|
recordInbound({
|
|
@@ -91068,7 +91737,12 @@ ${preBlock(write.output)}`;
|
|
|
91068
91737
|
text: effectiveText,
|
|
91069
91738
|
attachment_kind: attachment?.kind,
|
|
91070
91739
|
reply_to_message_id: replyToMessageId ?? null,
|
|
91071
|
-
reply_to_text: replyToText ?? null
|
|
91740
|
+
reply_to_text: replyToText ?? null,
|
|
91741
|
+
forwarded_from: primaryForwardOrigin?.name ?? null,
|
|
91742
|
+
forwarded_from_type: primaryForwardOrigin?.type ?? null,
|
|
91743
|
+
forwarded_from_id: primaryForwardOrigin?.id != null ? String(primaryForwardOrigin.id) : null,
|
|
91744
|
+
forwarded_date: forwardOriginDateIso(primaryForwardOrigin),
|
|
91745
|
+
forwarded_message_id: primaryForwardOrigin?.messageId ?? null
|
|
91072
91746
|
});
|
|
91073
91747
|
} catch (err) {
|
|
91074
91748
|
process.stderr.write(`telegram gateway: history recordInbound failed: ${err}
|
|
@@ -91134,6 +91808,7 @@ ${preBlock(write.output)}`;
|
|
|
91134
91808
|
...imagePath ? { image_path: imagePath } : {},
|
|
91135
91809
|
...replyToMessageId != null ? { reply_to_message_id: String(replyToMessageId) } : {},
|
|
91136
91810
|
...replyToTextEscaped != null && replyToTextEscaped.length > 0 ? { reply_to_text: replyToTextEscaped } : {},
|
|
91811
|
+
...forwardOriginMeta,
|
|
91137
91812
|
...isQueuedMidTurn || isQueuedPrefix ? { queued: "true" } : {},
|
|
91138
91813
|
...isSteering ? { steering: "true" } : {},
|
|
91139
91814
|
...priorTurnInProgress ? { prior_turn_in_progress: "true" } : {},
|
|
@@ -93238,12 +93913,33 @@ function newestActiveTurnStartedAtMs() {
|
|
|
93238
93913
|
function wouldFireFleetAutoFallback() {
|
|
93239
93914
|
return fleetFallbackGate.wouldFire();
|
|
93240
93915
|
}
|
|
93241
|
-
async function fireFleetAutoFallback(triggerAgent, untilMs) {
|
|
93242
|
-
return fleetFallbackGate.fire(() => doFireFleetAutoFallback(triggerAgent, untilMs), (err) => {
|
|
93916
|
+
async function fireFleetAutoFallback(triggerAgent, untilMs, parsedResetAt, trigger) {
|
|
93917
|
+
return fleetFallbackGate.fire(() => doFireFleetAutoFallback(triggerAgent, untilMs, parsedResetAt, trigger), (err) => {
|
|
93243
93918
|
process.stderr.write(`telegram gateway: [fleet-fallback] error agent=${triggerAgent}: ${err?.message ?? err}
|
|
93244
93919
|
`);
|
|
93245
93920
|
});
|
|
93246
93921
|
}
|
|
93922
|
+
var throttleTierRunner = createThrottleTierRunner({
|
|
93923
|
+
agentName: process.env.SWITCHROOM_AGENT_NAME ?? "",
|
|
93924
|
+
getBrokerClient: () => getAuthBrokerClient2(process.env.SWITCHROOM_AGENT_NAME ?? ""),
|
|
93925
|
+
listNoticeChats: () => loadAccess().allowFrom,
|
|
93926
|
+
sendNotice: (chat_id, markdown) => {
|
|
93927
|
+
swallowingApiCall(() => bot.api.sendRichMessage(chat_id, richMessage2(markdown), { disable_notification: true }), { chat_id: String(chat_id), verb: "throttle-tier:notify" });
|
|
93928
|
+
},
|
|
93929
|
+
resumeDecide: (ts) => fleetFallbackResumeGate.decide(ts),
|
|
93930
|
+
newestActiveTurnStartedAtMs,
|
|
93931
|
+
turnInFlight: () => turnInFlightForGate(),
|
|
93932
|
+
deferRestartToTurnComplete: (agentName3, reason) => {
|
|
93933
|
+
process.stderr.write(`telegram gateway: [throttle-tier] restart deferred to turn-complete agent=${agentName3} reason=${reason}
|
|
93934
|
+
`);
|
|
93935
|
+
pendingRestarts.set(agentName3, Date.now());
|
|
93936
|
+
},
|
|
93937
|
+
restartNow: (agentName3, reason) => {
|
|
93938
|
+
triggerSelfRestart(agentName3, reason);
|
|
93939
|
+
},
|
|
93940
|
+
log: (m) => process.stderr.write(`telegram gateway: ${m}
|
|
93941
|
+
`)
|
|
93942
|
+
});
|
|
93247
93943
|
var fallbackFailureNoticeState = { lastSentAtMs: 0 };
|
|
93248
93944
|
var fallbackAllBlockedNoticeState = { lastSentAtMs: 0 };
|
|
93249
93945
|
function broadcastFleetFallbackFailure(triggerAgent, reason) {
|
|
@@ -93264,7 +93960,7 @@ function broadcastFleetFallbackFailure(triggerAgent, reason) {
|
|
|
93264
93960
|
swallowingApiCall(() => bot.api.sendRichMessage(chat_id, richMessage2(html), {}), { chat_id, verb: "fleet-fallback:failure-notify" });
|
|
93265
93961
|
}
|
|
93266
93962
|
}
|
|
93267
|
-
async function doFireFleetAutoFallback(triggerAgent, untilMs) {
|
|
93963
|
+
async function doFireFleetAutoFallback(triggerAgent, untilMs, parsedResetAt, trigger) {
|
|
93268
93964
|
try {
|
|
93269
93965
|
const client3 = await getAuthBrokerClient2(triggerAgent);
|
|
93270
93966
|
if (!client3) {
|
|
@@ -93288,7 +93984,9 @@ async function doFireFleetAutoFallback(triggerAgent, untilMs) {
|
|
|
93288
93984
|
return { rolledTo: r.rolledTo ?? null, rolled: r.rolled };
|
|
93289
93985
|
},
|
|
93290
93986
|
triggerAgent,
|
|
93291
|
-
tz
|
|
93987
|
+
tz,
|
|
93988
|
+
parsedResetAt,
|
|
93989
|
+
rateLimitTrigger: trigger === "rate-limit"
|
|
93292
93990
|
});
|
|
93293
93991
|
process.stderr.write(`telegram gateway: [fleet-fallback] outcome=${outcome.kind} agent=${triggerAgent}` + (outcome.kind === "switched" ? ` old=${outcome.oldLabel} new=${outcome.newLabel}` : "") + `
|
|
93294
93992
|
`);
|