switchroom 0.18.13 → 0.18.14
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 +111 -7
- 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 +35 -6
- package/dist/host-control/main.js +1 -2
- package/dist/vault/approvals/kernel-server.js +0 -1
- package/dist/vault/broker/server.js +0 -1
- 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 +2 -0
- package/telegram-plugin/dist/gateway/gateway.js +620 -67
- package/telegram-plugin/dist/server.js +2 -0
- 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 +224 -10
- package/telegram-plugin/gateway/throttle-tier-wiring.ts +268 -0
- package/telegram-plugin/history.ts +55 -6
- package/telegram-plugin/model-unavailable.ts +20 -2
- package/telegram-plugin/render/rich-render.ts +40 -32
- 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/render/render-outbound-chunks.test.ts +6 -4
- package/telegram-plugin/tests/render/rich-render.test.ts +41 -22
- 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 +278 -0
- package/telegram-plugin/throttle-tier.ts +226 -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
|
|
@@ -64684,6 +64853,8 @@ function formatReason(d, now) {
|
|
|
64684
64853
|
return `quota exhausted${reset2}`;
|
|
64685
64854
|
case "overload":
|
|
64686
64855
|
return `model overloaded${reset2}`;
|
|
64856
|
+
case "rate_limited":
|
|
64857
|
+
return `account rate-limited${reset2}`;
|
|
64687
64858
|
case "network":
|
|
64688
64859
|
return "network unreachable";
|
|
64689
64860
|
}
|
|
@@ -64703,6 +64874,339 @@ function resolveModelUnavailableFromOperatorEvent(ev) {
|
|
|
64703
64874
|
return detectModelUnavailable(detail);
|
|
64704
64875
|
}
|
|
64705
64876
|
|
|
64877
|
+
// throttle-tier.ts
|
|
64878
|
+
init_card_format();
|
|
64879
|
+
init_quota_check();
|
|
64880
|
+
|
|
64881
|
+
// model-unavailable.ts
|
|
64882
|
+
init_quota_check();
|
|
64883
|
+
init_card_format();
|
|
64884
|
+
var transientUpstreamSignals2 = [
|
|
64885
|
+
"not your usage limit",
|
|
64886
|
+
"not your account",
|
|
64887
|
+
"not your account's",
|
|
64888
|
+
"temporarily limiting requests",
|
|
64889
|
+
"temporarily rate",
|
|
64890
|
+
"server is temporarily",
|
|
64891
|
+
"would exceed your account\u2019s rate limit",
|
|
64892
|
+
"would exceed your account's rate limit"
|
|
64893
|
+
];
|
|
64894
|
+
function isTransientUpstreamSignal(text4) {
|
|
64895
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
64896
|
+
return false;
|
|
64897
|
+
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
64898
|
+
const lower = sample.toLowerCase();
|
|
64899
|
+
return transientUpstreamSignals2.some((s) => lower.includes(s));
|
|
64900
|
+
}
|
|
64901
|
+
function parseResetTime2(text4, parseTimeNow = new Date) {
|
|
64902
|
+
const lower = text4.toLowerCase();
|
|
64903
|
+
const retryAfter = lower.match(/retry[\s-]*after[:\s]+(\d+)\s*(seconds?|s\b|minutes?|m\b|hours?|h\b)?/);
|
|
64904
|
+
if (retryAfter) {
|
|
64905
|
+
const n = Number(retryAfter[1]);
|
|
64906
|
+
if (Number.isFinite(n) && n > 0 && n < 7 * 24 * 3600) {
|
|
64907
|
+
const unit = (retryAfter[2] ?? "seconds").toLowerCase();
|
|
64908
|
+
const ms = unit.startsWith("h") ? n * 3600000 : unit.startsWith("m") ? n * 60000 : n * 1000;
|
|
64909
|
+
return new Date(parseTimeNow.getTime() + ms);
|
|
64910
|
+
}
|
|
64911
|
+
}
|
|
64912
|
+
const relReset = lower.match(/resets?\s+in\s+([0-9hms\s]+)/);
|
|
64913
|
+
if (relReset) {
|
|
64914
|
+
const ms = parseRelativeDuration2(relReset[1]);
|
|
64915
|
+
if (ms != null)
|
|
64916
|
+
return new Date(parseTimeNow.getTime() + ms);
|
|
64917
|
+
}
|
|
64918
|
+
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/);
|
|
64919
|
+
if (iso) {
|
|
64920
|
+
const d = new Date(iso[0]);
|
|
64921
|
+
if (!Number.isNaN(d.getTime()))
|
|
64922
|
+
return d;
|
|
64923
|
+
}
|
|
64924
|
+
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)?))?)/);
|
|
64925
|
+
if (calReset) {
|
|
64926
|
+
const candidate = `${calReset[1]} ${parseTimeNow.getUTCFullYear()}`;
|
|
64927
|
+
const d = new Date(candidate);
|
|
64928
|
+
if (!Number.isNaN(d.getTime()))
|
|
64929
|
+
return d;
|
|
64930
|
+
}
|
|
64931
|
+
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);
|
|
64932
|
+
if (timeOnly) {
|
|
64933
|
+
const d = resolveNextWallClock2(Number(timeOnly[1]), timeOnly[2] ? Number(timeOnly[2]) : 0, timeOnly[3]?.toLowerCase(), timeOnly[4]?.trim(), parseTimeNow);
|
|
64934
|
+
if (d != null)
|
|
64935
|
+
return d;
|
|
64936
|
+
}
|
|
64937
|
+
return;
|
|
64938
|
+
}
|
|
64939
|
+
function resolveNextWallClock2(hour12or24, minute, ampm, tz, nowDate) {
|
|
64940
|
+
let hour = hour12or24;
|
|
64941
|
+
if (ampm === "pm" && hour < 12)
|
|
64942
|
+
hour += 12;
|
|
64943
|
+
if (ampm === "am" && hour === 12)
|
|
64944
|
+
hour = 0;
|
|
64945
|
+
if (!Number.isFinite(hour) || hour > 23 || hour < 0)
|
|
64946
|
+
return;
|
|
64947
|
+
if (!Number.isFinite(minute) || minute > 59 || minute < 0)
|
|
64948
|
+
return;
|
|
64949
|
+
const nowMs2 = nowDate.getTime();
|
|
64950
|
+
const base = new Date(nowMs2);
|
|
64951
|
+
for (let dayOffset = 0;dayOffset <= 2; dayOffset++) {
|
|
64952
|
+
const dateParts = tzDateParts2(new Date(nowMs2 + dayOffset * 86400000), tz);
|
|
64953
|
+
if (dateParts == null)
|
|
64954
|
+
return;
|
|
64955
|
+
const epoch = wallClockToEpoch2(dateParts.year, dateParts.month, dateParts.day, hour, minute, tz);
|
|
64956
|
+
if (epoch != null && epoch > nowMs2)
|
|
64957
|
+
return new Date(epoch);
|
|
64958
|
+
}
|
|
64959
|
+
return;
|
|
64960
|
+
}
|
|
64961
|
+
function tzDateParts2(d, tz) {
|
|
64962
|
+
if (!tz) {
|
|
64963
|
+
return { year: d.getUTCFullYear(), month: d.getUTCMonth(), day: d.getUTCDate() };
|
|
64964
|
+
}
|
|
64965
|
+
try {
|
|
64966
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
64967
|
+
timeZone: tz,
|
|
64968
|
+
year: "numeric",
|
|
64969
|
+
month: "2-digit",
|
|
64970
|
+
day: "2-digit"
|
|
64971
|
+
});
|
|
64972
|
+
const parts = Object.fromEntries(fmt.formatToParts(d).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
|
|
64973
|
+
return {
|
|
64974
|
+
year: Number(parts.year),
|
|
64975
|
+
month: Number(parts.month) - 1,
|
|
64976
|
+
day: Number(parts.day)
|
|
64977
|
+
};
|
|
64978
|
+
} catch {
|
|
64979
|
+
return null;
|
|
64980
|
+
}
|
|
64981
|
+
}
|
|
64982
|
+
function wallClockToEpoch2(year, month, day, hour, minute, tz) {
|
|
64983
|
+
const asUtc = Date.UTC(year, month, day, hour, minute, 0);
|
|
64984
|
+
if (!tz)
|
|
64985
|
+
return asUtc;
|
|
64986
|
+
try {
|
|
64987
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
64988
|
+
timeZone: tz,
|
|
64989
|
+
year: "numeric",
|
|
64990
|
+
month: "2-digit",
|
|
64991
|
+
day: "2-digit",
|
|
64992
|
+
hour: "2-digit",
|
|
64993
|
+
minute: "2-digit",
|
|
64994
|
+
second: "2-digit",
|
|
64995
|
+
hour12: false
|
|
64996
|
+
});
|
|
64997
|
+
const parts = Object.fromEntries(fmt.formatToParts(new Date(asUtc)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
|
|
64998
|
+
const shown = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour) % 24, Number(parts.minute), Number(parts.second));
|
|
64999
|
+
const offset = shown - asUtc;
|
|
65000
|
+
return asUtc - offset;
|
|
65001
|
+
} catch {
|
|
65002
|
+
return null;
|
|
65003
|
+
}
|
|
65004
|
+
}
|
|
65005
|
+
function parseRelativeDuration2(s) {
|
|
65006
|
+
let total = 0;
|
|
65007
|
+
let matched = false;
|
|
65008
|
+
const re = /(\d+)\s*(h|hours?|m|minutes?|s|seconds?)/g;
|
|
65009
|
+
let m;
|
|
65010
|
+
while ((m = re.exec(s)) != null) {
|
|
65011
|
+
matched = true;
|
|
65012
|
+
const n = Number(m[1]);
|
|
65013
|
+
const unit = m[2].toLowerCase();
|
|
65014
|
+
if (unit.startsWith("h"))
|
|
65015
|
+
total += n * 3600000;
|
|
65016
|
+
else if (unit.startsWith("m"))
|
|
65017
|
+
total += n * 60000;
|
|
65018
|
+
else
|
|
65019
|
+
total += n * 1000;
|
|
65020
|
+
}
|
|
65021
|
+
return matched && total > 0 ? total : null;
|
|
65022
|
+
}
|
|
65023
|
+
|
|
65024
|
+
// throttle-tier.ts
|
|
65025
|
+
var accountScopedThrottleSignals = [
|
|
65026
|
+
"would exceed your account's rate limit",
|
|
65027
|
+
"would exceed your account\u2019s rate limit",
|
|
65028
|
+
"not your account"
|
|
65029
|
+
];
|
|
65030
|
+
function isAccountScopedThrottle(text4) {
|
|
65031
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
65032
|
+
return false;
|
|
65033
|
+
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
65034
|
+
const lower = sample.toLowerCase();
|
|
65035
|
+
return accountScopedThrottleSignals.some((s) => lower.includes(s));
|
|
65036
|
+
}
|
|
65037
|
+
var THROTTLE_RETRY_IN_PLACE_MAX_MS_DEFAULT = 5 * 60000;
|
|
65038
|
+
var THROTTLE_DEFAULT_WAIT_MS = 60000;
|
|
65039
|
+
function throttleRetryInPlaceMaxMs(env = process.env) {
|
|
65040
|
+
const raw = env.SWITCHROOM_THROTTLE_RETRY_IN_PLACE_MAX_MS;
|
|
65041
|
+
if (raw == null || raw === "")
|
|
65042
|
+
return THROTTLE_RETRY_IN_PLACE_MAX_MS_DEFAULT;
|
|
65043
|
+
const n = Number(raw);
|
|
65044
|
+
return Number.isFinite(n) && n > 0 ? n : THROTTLE_RETRY_IN_PLACE_MAX_MS_DEFAULT;
|
|
65045
|
+
}
|
|
65046
|
+
function decideThrottleTier(opts) {
|
|
65047
|
+
const { detail, now, thresholdMs } = opts;
|
|
65048
|
+
if (!isAccountScopedThrottle(detail))
|
|
65049
|
+
return { action: "none" };
|
|
65050
|
+
const resetAt = parseResetTime2(detail, new Date(now));
|
|
65051
|
+
const resetAtMs = resetAt?.getTime();
|
|
65052
|
+
if (resetAtMs == null || !Number.isFinite(resetAtMs) || resetAtMs <= now) {
|
|
65053
|
+
return {
|
|
65054
|
+
action: "throttle",
|
|
65055
|
+
throttledUntilMs: now + THROTTLE_DEFAULT_WAIT_MS,
|
|
65056
|
+
resetParsed: false
|
|
65057
|
+
};
|
|
65058
|
+
}
|
|
65059
|
+
if (resetAtMs - now <= thresholdMs) {
|
|
65060
|
+
return { action: "throttle", throttledUntilMs: resetAtMs, resetParsed: true };
|
|
65061
|
+
}
|
|
65062
|
+
return { action: "failover", resetAtMs };
|
|
65063
|
+
}
|
|
65064
|
+
var THROTTLE_NOTICE_COOLDOWN_MS = 10 * 60000;
|
|
65065
|
+
|
|
65066
|
+
// throttle-tier.ts
|
|
65067
|
+
init_card_format();
|
|
65068
|
+
init_quota_check();
|
|
65069
|
+
var THROTTLE_RETRY_IN_PLACE_MAX_MS_DEFAULT2 = 5 * 60000;
|
|
65070
|
+
var THROTTLE_NOTICE_COOLDOWN_MS2 = 10 * 60000;
|
|
65071
|
+
function evaluateThrottleNotice(prev, account, now, cooldownMs = THROTTLE_NOTICE_COOLDOWN_MS2) {
|
|
65072
|
+
const last = prev.lastSentAtMsByAccount[account] ?? 0;
|
|
65073
|
+
if (now - last >= cooldownMs) {
|
|
65074
|
+
return {
|
|
65075
|
+
send: true,
|
|
65076
|
+
next: {
|
|
65077
|
+
lastSentAtMsByAccount: {
|
|
65078
|
+
...prev.lastSentAtMsByAccount,
|
|
65079
|
+
[account]: now
|
|
65080
|
+
}
|
|
65081
|
+
}
|
|
65082
|
+
};
|
|
65083
|
+
}
|
|
65084
|
+
return { send: false, next: prev };
|
|
65085
|
+
}
|
|
65086
|
+
function renderThrottleNotice(opts) {
|
|
65087
|
+
const now = opts.now ?? new Date;
|
|
65088
|
+
const resetStr = formatResetRelative(new Date(opts.throttledUntilMs), now);
|
|
65089
|
+
const acct = opts.account ? `\`${escapeMarkdown(opts.account)}\`` : "the active account";
|
|
65090
|
+
const lines = [
|
|
65091
|
+
`\uD83D\uDEA6 **Rate-limited, staying put** \u2014 ${acct} hit a transient rate limit on **${escapeMarkdown(opts.agent)}**.`,
|
|
65092
|
+
`This is a short throttle, not a quota wall \u2014 ${opts.resetParsed ? resetStr : `no reset given, retrying in ~60s`}.`,
|
|
65093
|
+
`_Staying on ${acct}; no failover needed. The turn retries automatically after the reset._`
|
|
65094
|
+
];
|
|
65095
|
+
return lines.join(`
|
|
65096
|
+
`);
|
|
65097
|
+
}
|
|
65098
|
+
function renderThrottleEscalationNotice(opts) {
|
|
65099
|
+
const acct = opts.account ? `\`${escapeMarkdown(opts.account)}\`` : "the active account";
|
|
65100
|
+
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.`;
|
|
65101
|
+
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.`;
|
|
65102
|
+
return `${head}
|
|
65103
|
+
${tail}`;
|
|
65104
|
+
}
|
|
65105
|
+
|
|
65106
|
+
// gateway/throttle-tier-wiring.ts
|
|
65107
|
+
var THROTTLE_RETRY_NUDGE_SLACK_MS = 5000;
|
|
65108
|
+
var THROTTLE_RETRY_NUDGE_JITTER_MAX_MS = 30000;
|
|
65109
|
+
function createThrottleTierRunner(deps) {
|
|
65110
|
+
const now = deps.now ?? (() => Date.now());
|
|
65111
|
+
const schedule = deps.schedule ?? ((fn, ms) => {
|
|
65112
|
+
const t = setTimeout(fn, ms);
|
|
65113
|
+
if (typeof t.unref === "function")
|
|
65114
|
+
t.unref();
|
|
65115
|
+
return { cancel: () => clearTimeout(t) };
|
|
65116
|
+
});
|
|
65117
|
+
const jitterMs = deps.jitterMs ?? (() => Math.floor(Math.random() * THROTTLE_RETRY_NUDGE_JITTER_MAX_MS));
|
|
65118
|
+
let noticeState = { lastSentAtMsByAccount: {} };
|
|
65119
|
+
let pendingNudge = null;
|
|
65120
|
+
async function broadcastDeduped(client3, keyPrefix, account, markdown) {
|
|
65121
|
+
for (const chatId of deps.listNoticeChats()) {
|
|
65122
|
+
let granted = true;
|
|
65123
|
+
if (client3 && account) {
|
|
65124
|
+
try {
|
|
65125
|
+
granted = (await client3.claimNotification(`${keyPrefix}:${account}:${chatId}`, THROTTLE_NOTICE_COOLDOWN_MS2)).granted;
|
|
65126
|
+
} catch {
|
|
65127
|
+
granted = true;
|
|
65128
|
+
}
|
|
65129
|
+
}
|
|
65130
|
+
if (granted) {
|
|
65131
|
+
deps.sendNotice(chatId, markdown);
|
|
65132
|
+
} else {
|
|
65133
|
+
deps.log(`[throttle-tier] notice suppressed (fleet claim) chat=${chatId}`);
|
|
65134
|
+
}
|
|
65135
|
+
}
|
|
65136
|
+
}
|
|
65137
|
+
function nudgeResume(reason, armedAtMs) {
|
|
65138
|
+
const newest = deps.newestActiveTurnStartedAtMs();
|
|
65139
|
+
if (deps.turnInFlight()) {
|
|
65140
|
+
if (newest != null && newest > armedAtMs) {
|
|
65141
|
+
deps.log(`[throttle-tier] resume skipped (superseded by a live newer turn) reason=${reason}`);
|
|
65142
|
+
return;
|
|
65143
|
+
}
|
|
65144
|
+
deps.log(`[throttle-tier] resume deferred to turn-complete reason=${reason}`);
|
|
65145
|
+
deps.deferRestartToTurnComplete(deps.agentName, reason);
|
|
65146
|
+
return;
|
|
65147
|
+
}
|
|
65148
|
+
const verdict = deps.resumeDecide(newest);
|
|
65149
|
+
if (verdict === "resume") {
|
|
65150
|
+
deps.log(`[throttle-tier] resuming dead turn via self-restart reason=${reason}`);
|
|
65151
|
+
deps.restartNow(deps.agentName, reason);
|
|
65152
|
+
} else {
|
|
65153
|
+
deps.log(`[throttle-tier] resume suppressed (${verdict}) reason=${reason}`);
|
|
65154
|
+
}
|
|
65155
|
+
}
|
|
65156
|
+
async function fire(triggerAgent, throttledUntilMs, resetParsed) {
|
|
65157
|
+
const armedAtMs = now();
|
|
65158
|
+
let client3 = null;
|
|
65159
|
+
let account = null;
|
|
65160
|
+
let escalated = false;
|
|
65161
|
+
let rolledTo = null;
|
|
65162
|
+
try {
|
|
65163
|
+
client3 = await deps.getBrokerClient();
|
|
65164
|
+
if (client3) {
|
|
65165
|
+
const r = await client3.markThrottled(throttledUntilMs);
|
|
65166
|
+
account = r.account;
|
|
65167
|
+
escalated = r.escalated;
|
|
65168
|
+
rolledTo = r.rolledTo ?? null;
|
|
65169
|
+
} else {
|
|
65170
|
+
deps.log(`[throttle-tier] broker unreachable \u2014 notice only, no ledger record agent=${triggerAgent}`);
|
|
65171
|
+
}
|
|
65172
|
+
} catch (err) {
|
|
65173
|
+
deps.log(`[throttle-tier] markThrottled failed agent=${triggerAgent}: ${err?.message ?? err}`);
|
|
65174
|
+
}
|
|
65175
|
+
if (escalated) {
|
|
65176
|
+
deps.log(`[throttle-tier] escalated to wall account=${account ?? "?"} ` + `rolledTo=${rolledTo ?? "none (all blocked)"}`);
|
|
65177
|
+
await broadcastDeduped(client3, "throttle-escalation", account, renderThrottleEscalationNotice({ account, agent: triggerAgent, rolledTo }));
|
|
65178
|
+
if (rolledTo)
|
|
65179
|
+
nudgeResume("throttle-escalation-resume", armedAtMs);
|
|
65180
|
+
return;
|
|
65181
|
+
}
|
|
65182
|
+
const cooldownKey = account ?? `agent:${triggerAgent}`;
|
|
65183
|
+
const verdict = evaluateThrottleNotice(noticeState, cooldownKey, now());
|
|
65184
|
+
if (verdict.send) {
|
|
65185
|
+
noticeState = verdict.next;
|
|
65186
|
+
await broadcastDeduped(client3, "throttle-notice", account, renderThrottleNotice({
|
|
65187
|
+
account,
|
|
65188
|
+
agent: triggerAgent,
|
|
65189
|
+
throttledUntilMs,
|
|
65190
|
+
resetParsed,
|
|
65191
|
+
now: new Date(now())
|
|
65192
|
+
}));
|
|
65193
|
+
} else {
|
|
65194
|
+
deps.log(`[throttle-tier] notice suppressed (cooldown) key=${cooldownKey}`);
|
|
65195
|
+
}
|
|
65196
|
+
const delayMs = Math.max(throttledUntilMs - now(), 0) + THROTTLE_RETRY_NUDGE_SLACK_MS + jitterMs();
|
|
65197
|
+
if (pendingNudge)
|
|
65198
|
+
pendingNudge.cancel();
|
|
65199
|
+
pendingNudge = schedule(() => {
|
|
65200
|
+
pendingNudge = null;
|
|
65201
|
+
nudgeResume("throttle-retry-resume", armedAtMs);
|
|
65202
|
+
}, delayMs);
|
|
65203
|
+
}
|
|
65204
|
+
return {
|
|
65205
|
+
fire,
|
|
65206
|
+
inspect: () => ({ noticeState, nudgePending: pendingNudge != null })
|
|
65207
|
+
};
|
|
65208
|
+
}
|
|
65209
|
+
|
|
64706
65210
|
// auto-fallback-fleet.ts
|
|
64707
65211
|
init_card_format();
|
|
64708
65212
|
init_auth_snapshot_format();
|
|
@@ -64738,7 +65242,7 @@ async function runFleetAutoFallback(deps) {
|
|
|
64738
65242
|
};
|
|
64739
65243
|
}
|
|
64740
65244
|
const oldHealth = classifyHealth(oldSnap, now);
|
|
64741
|
-
if (oldHealth === "healthy") {
|
|
65245
|
+
if (oldHealth === "healthy" && !deps.rateLimitTrigger) {
|
|
64742
65246
|
return {
|
|
64743
65247
|
kind: "no-eligible-target",
|
|
64744
65248
|
oldLabel: oldSnap.label,
|
|
@@ -64759,6 +65263,8 @@ async function runFleetAutoFallback(deps) {
|
|
|
64759
65263
|
newQuota: null,
|
|
64760
65264
|
triggerAgent: deps.triggerAgent,
|
|
64761
65265
|
fleetSnapshots: snapshots,
|
|
65266
|
+
parsedResetAt: deps.parsedResetAt ?? null,
|
|
65267
|
+
cause: deps.rateLimitTrigger ? "rate-limit" : undefined,
|
|
64762
65268
|
tz,
|
|
64763
65269
|
now
|
|
64764
65270
|
})
|
|
@@ -64777,6 +65283,8 @@ async function runFleetAutoFallback(deps) {
|
|
|
64777
65283
|
newLabel: rolledTo,
|
|
64778
65284
|
newQuota,
|
|
64779
65285
|
triggerAgent: deps.triggerAgent,
|
|
65286
|
+
parsedResetAt: deps.parsedResetAt ?? null,
|
|
65287
|
+
cause: deps.rateLimitTrigger ? "rate-limit" : undefined,
|
|
64780
65288
|
tz,
|
|
64781
65289
|
now
|
|
64782
65290
|
})
|
|
@@ -66830,13 +67338,13 @@ function buildFolderPickerCard(input) {
|
|
|
66830
67338
|
validateDriveId(folder.id, "folder.id");
|
|
66831
67339
|
rows.push([
|
|
66832
67340
|
{
|
|
66833
|
-
text: `\u2705 Allow "${
|
|
67341
|
+
text: `\u2705 Allow "${truncateName2(folder.name, 48)}"`,
|
|
66834
67342
|
callback_data: encodeCallback(["drvpick", "grant", input.agent, folder.id])
|
|
66835
67343
|
}
|
|
66836
67344
|
]);
|
|
66837
67345
|
rows.push([
|
|
66838
67346
|
{
|
|
66839
|
-
text: `\uD83D\uDCC2 Browse "${
|
|
67347
|
+
text: `\uD83D\uDCC2 Browse "${truncateName2(folder.name, 46)}"`,
|
|
66840
67348
|
callback_data: encodeCallback(["drvpick", "enter", input.agent, folder.id])
|
|
66841
67349
|
}
|
|
66842
67350
|
]);
|
|
@@ -66885,14 +67393,14 @@ function buildFolderPickerCard(input) {
|
|
|
66885
67393
|
return { body, rows };
|
|
66886
67394
|
}
|
|
66887
67395
|
function renderBreadcrumb(trail2, leafName) {
|
|
66888
|
-
const leaf =
|
|
67396
|
+
const leaf = truncateName2(leafName, 24);
|
|
66889
67397
|
if (trail2.length === 0)
|
|
66890
67398
|
return `/${leaf}`;
|
|
66891
|
-
const segments = trail2.map((t) =>
|
|
67399
|
+
const segments = trail2.map((t) => truncateName2(t.name, 20));
|
|
66892
67400
|
segments.push(leaf);
|
|
66893
67401
|
return `/${segments.join("/")}`;
|
|
66894
67402
|
}
|
|
66895
|
-
function
|
|
67403
|
+
function truncateName2(name, max) {
|
|
66896
67404
|
if (name.length <= max)
|
|
66897
67405
|
return name;
|
|
66898
67406
|
return `${name.slice(0, max - 1)}\u2026`;
|
|
@@ -76405,27 +76913,6 @@ function getNestedObj(obj, key) {
|
|
|
76405
76913
|
var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
|
|
76406
76914
|
var cooldownMap2 = new Map;
|
|
76407
76915
|
|
|
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
76916
|
// session-tail.ts
|
|
76430
76917
|
function sanitizeCwdToProjectName(cwd) {
|
|
76431
76918
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
@@ -80470,10 +80957,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
80470
80957
|
}
|
|
80471
80958
|
|
|
80472
80959
|
// ../src/build-info.ts
|
|
80473
|
-
var VERSION = "0.18.
|
|
80474
|
-
var COMMIT_SHA = "
|
|
80475
|
-
var COMMIT_DATE = "2026-07-
|
|
80476
|
-
var LATEST_PR =
|
|
80960
|
+
var VERSION = "0.18.14";
|
|
80961
|
+
var COMMIT_SHA = "b0db04aa";
|
|
80962
|
+
var COMMIT_DATE = "2026-07-12T12:40:07+10:00";
|
|
80963
|
+
var LATEST_PR = 3165;
|
|
80477
80964
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
80478
80965
|
|
|
80479
80966
|
// gateway/boot-version.ts
|
|
@@ -85140,22 +85627,50 @@ var inboundCoalescer = createInboundCoalescer({
|
|
|
85140
85627
|
merge: (entries) => {
|
|
85141
85628
|
const last = entries[entries.length - 1];
|
|
85142
85629
|
const { primary, extras } = splitCoalescedAttachments(entries, (e) => e.downloadImage != null || e.attachment != null, coalesceMaxAttachments());
|
|
85630
|
+
const forwardOrigins = dedupeForwardOrigins(entries.map((e) => e.forwardOrigin));
|
|
85143
85631
|
return {
|
|
85144
85632
|
text: entries.map((e) => e.text).filter((t) => t.length > 0).join(`
|
|
85145
85633
|
`),
|
|
85146
85634
|
ctx: last.ctx,
|
|
85147
85635
|
downloadImage: primary?.downloadImage,
|
|
85148
85636
|
attachment: primary?.attachment,
|
|
85149
|
-
extraAttachments: extras.length > 0 ? extras.map((e) => ({ downloadImage: e.downloadImage, attachment: e.attachment })) : undefined
|
|
85637
|
+
extraAttachments: extras.length > 0 ? extras.map((e) => ({ downloadImage: e.downloadImage, attachment: e.attachment })) : undefined,
|
|
85638
|
+
forwardOrigins: forwardOrigins.length > 0 ? forwardOrigins : undefined
|
|
85150
85639
|
};
|
|
85151
85640
|
},
|
|
85152
85641
|
onFlush: (key, merged) => {
|
|
85153
85642
|
bufferedAttachmentKeys.delete(key);
|
|
85154
|
-
handleInbound(merged.ctx, merged.text, merged.downloadImage, merged.attachment, merged.extraAttachments);
|
|
85643
|
+
handleInbound(merged.ctx, merged.text, merged.downloadImage, merged.attachment, merged.extraAttachments, merged.forwardOrigins);
|
|
85155
85644
|
}
|
|
85156
85645
|
});
|
|
85157
85646
|
function emitGatewayOperatorEvent(event) {
|
|
85158
85647
|
const { agent, kind } = event;
|
|
85648
|
+
let throttleEscalation = null;
|
|
85649
|
+
let escalationFired = false;
|
|
85650
|
+
if (kind === "rate-limited" && isAccountScopedThrottle(event.detail)) {
|
|
85651
|
+
const throttleDecision = decideThrottleTier({
|
|
85652
|
+
detail: event.detail,
|
|
85653
|
+
now: Date.now(),
|
|
85654
|
+
thresholdMs: throttleRetryInPlaceMaxMs()
|
|
85655
|
+
});
|
|
85656
|
+
if (throttleDecision.action === "throttle") {
|
|
85657
|
+
process.stderr.write(`telegram gateway: throttle-tier staying-put agent=${agent} until=${new Date(throttleDecision.throttledUntilMs).toISOString()} parsedReset=${throttleDecision.resetParsed}
|
|
85658
|
+
`);
|
|
85659
|
+
try {
|
|
85660
|
+
recordOperatorEvent(event);
|
|
85661
|
+
} catch {}
|
|
85662
|
+
throttleTierRunner.fire(agent, throttleDecision.throttledUntilMs, throttleDecision.resetParsed);
|
|
85663
|
+
return;
|
|
85664
|
+
}
|
|
85665
|
+
if (throttleDecision.action === "failover") {
|
|
85666
|
+
const resetAt = new Date(throttleDecision.resetAtMs);
|
|
85667
|
+
throttleEscalation = { kind: "rate_limited", resetAt, raw: event.detail };
|
|
85668
|
+
if (wouldFireFleetAutoFallback()) {
|
|
85669
|
+
escalationFired = true;
|
|
85670
|
+
fireFleetAutoFallback(agent, resolveExhaustUntil(resetAt.getTime()), resetAt, "rate-limit");
|
|
85671
|
+
}
|
|
85672
|
+
}
|
|
85673
|
+
}
|
|
85159
85674
|
if (!shouldEmitOperatorEvent(agent, kind)) {
|
|
85160
85675
|
process.stderr.write(`telegram gateway: operator-event suppressed (cooldown) agent=${agent} kind=${kind}
|
|
85161
85676
|
`);
|
|
@@ -85167,13 +85682,13 @@ function emitGatewayOperatorEvent(event) {
|
|
|
85167
85682
|
process.stderr.write(`telegram gateway: recordOperatorEvent failed agent=${agent} kind=${kind}: ${err.message}
|
|
85168
85683
|
`);
|
|
85169
85684
|
}
|
|
85170
|
-
const modelUnavailable = resolveModelUnavailableFromOperatorEvent(event);
|
|
85685
|
+
const modelUnavailable = throttleEscalation ?? resolveModelUnavailableFromOperatorEvent(event);
|
|
85171
85686
|
let renderedText;
|
|
85172
85687
|
let renderedKeyboard;
|
|
85173
85688
|
let cardPromisedFallback = false;
|
|
85174
85689
|
if (modelUnavailable) {
|
|
85175
|
-
const isAutoKind = modelUnavailable.kind === "quota_exhausted";
|
|
85176
|
-
const willActuallyFire = isAutoKind && wouldFireFleetAutoFallback();
|
|
85690
|
+
const isAutoKind = modelUnavailable.kind === "quota_exhausted" || modelUnavailable.kind === "rate_limited";
|
|
85691
|
+
const willActuallyFire = throttleEscalation != null ? escalationFired : isAutoKind && wouldFireFleetAutoFallback();
|
|
85177
85692
|
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
85693
|
`);
|
|
85179
85694
|
renderedText = formatModelUnavailableCard(modelUnavailable, agent, {
|
|
@@ -85181,9 +85696,9 @@ function emitGatewayOperatorEvent(event) {
|
|
|
85181
85696
|
});
|
|
85182
85697
|
renderedKeyboard = undefined;
|
|
85183
85698
|
cardPromisedFallback = willActuallyFire;
|
|
85184
|
-
if (willActuallyFire) {
|
|
85699
|
+
if (willActuallyFire && throttleEscalation == null) {
|
|
85185
85700
|
const untilMs = resolveExhaustUntil(modelUnavailable.resetAt?.getTime());
|
|
85186
|
-
fireFleetAutoFallback(agent, untilMs);
|
|
85701
|
+
fireFleetAutoFallback(agent, untilMs, modelUnavailable.resetAt);
|
|
85187
85702
|
}
|
|
85188
85703
|
} else {
|
|
85189
85704
|
try {
|
|
@@ -86899,7 +87414,7 @@ var ipcServer = createIpcServer({
|
|
|
86899
87414
|
const untilMs = resolveExhaustUntil(msg.resetAt);
|
|
86900
87415
|
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
87416
|
`);
|
|
86902
|
-
fireFleetAutoFallback(msg.agentName, untilMs);
|
|
87417
|
+
fireFleetAutoFallback(msg.agentName, untilMs, msg.resetAt != null ? new Date(msg.resetAt) : undefined);
|
|
86903
87418
|
},
|
|
86904
87419
|
onQueryPendingPermission(client3, msg) {
|
|
86905
87420
|
const self = process.env.SWITCHROOM_AGENT_NAME;
|
|
@@ -90392,7 +90907,13 @@ async function handleInboundCoalesced(ctx, text5, downloadImage, attachment) {
|
|
|
90392
90907
|
maybeEarlyAckReaction(ctx, from);
|
|
90393
90908
|
maybePokeFloorForMidTurnInbound(ctx, from);
|
|
90394
90909
|
const key = inboundCoalesceKey(String(ctx.chat.id), ctx.message?.message_thread_id, String(from.id));
|
|
90395
|
-
const result = inboundCoalescer.enqueue(key, {
|
|
90910
|
+
const result = inboundCoalescer.enqueue(key, {
|
|
90911
|
+
text: text5,
|
|
90912
|
+
ctx,
|
|
90913
|
+
downloadImage,
|
|
90914
|
+
attachment,
|
|
90915
|
+
forwardOrigin: parseForwardOrigin(ctx.message?.forward_origin)
|
|
90916
|
+
});
|
|
90396
90917
|
if (result.bypass)
|
|
90397
90918
|
return handleInbound(ctx, text5, downloadImage, attachment);
|
|
90398
90919
|
if (hasAttachment)
|
|
@@ -90430,7 +90951,7 @@ function maybePokeFloorForMidTurnInbound(ctx, from) {
|
|
|
90430
90951
|
return;
|
|
90431
90952
|
pokeFloorNow(key, Date.now());
|
|
90432
90953
|
}
|
|
90433
|
-
async function handleInbound(ctx, text5, downloadImage, attachment, extraAttachments) {
|
|
90954
|
+
async function handleInbound(ctx, text5, downloadImage, attachment, extraAttachments, coalescedForwardOrigins) {
|
|
90434
90955
|
markIdleActivity();
|
|
90435
90956
|
const isTopicMessage = ctx.message?.is_topic_message ?? false;
|
|
90436
90957
|
const messageThreadId = ctx.message?.message_thread_id;
|
|
@@ -91056,6 +91577,9 @@ ${preBlock(write.output)}`;
|
|
|
91056
91577
|
const replyToTextRaw = replyToMsg ? replyToMsg.text ?? replyToMsg.caption ?? undefined : undefined;
|
|
91057
91578
|
const replyToText = replyToTextRaw != null ? replyToTextRaw.length > REPLY_TO_TEXT_MAX ? replyToTextRaw.slice(0, REPLY_TO_TEXT_MAX - 1) + "\u2026" : replyToTextRaw : undefined;
|
|
91058
91579
|
const replyToTextEscaped = formatReplyToText(replyToTextRaw, REPLY_TO_TEXT_MAX);
|
|
91580
|
+
const forwardOrigins = coalescedForwardOrigins ?? dedupeForwardOrigins([parseForwardOrigin(ctx.message?.forward_origin)]);
|
|
91581
|
+
const forwardOriginMeta = buildForwardOriginMeta(forwardOrigins);
|
|
91582
|
+
const primaryForwardOrigin = forwardOrigins[0];
|
|
91059
91583
|
if (HISTORY_ENABLED) {
|
|
91060
91584
|
try {
|
|
91061
91585
|
recordInbound({
|
|
@@ -91068,7 +91592,12 @@ ${preBlock(write.output)}`;
|
|
|
91068
91592
|
text: effectiveText,
|
|
91069
91593
|
attachment_kind: attachment?.kind,
|
|
91070
91594
|
reply_to_message_id: replyToMessageId ?? null,
|
|
91071
|
-
reply_to_text: replyToText ?? null
|
|
91595
|
+
reply_to_text: replyToText ?? null,
|
|
91596
|
+
forwarded_from: primaryForwardOrigin?.name ?? null,
|
|
91597
|
+
forwarded_from_type: primaryForwardOrigin?.type ?? null,
|
|
91598
|
+
forwarded_from_id: primaryForwardOrigin?.id != null ? String(primaryForwardOrigin.id) : null,
|
|
91599
|
+
forwarded_date: forwardOriginDateIso(primaryForwardOrigin),
|
|
91600
|
+
forwarded_message_id: primaryForwardOrigin?.messageId ?? null
|
|
91072
91601
|
});
|
|
91073
91602
|
} catch (err) {
|
|
91074
91603
|
process.stderr.write(`telegram gateway: history recordInbound failed: ${err}
|
|
@@ -91134,6 +91663,7 @@ ${preBlock(write.output)}`;
|
|
|
91134
91663
|
...imagePath ? { image_path: imagePath } : {},
|
|
91135
91664
|
...replyToMessageId != null ? { reply_to_message_id: String(replyToMessageId) } : {},
|
|
91136
91665
|
...replyToTextEscaped != null && replyToTextEscaped.length > 0 ? { reply_to_text: replyToTextEscaped } : {},
|
|
91666
|
+
...forwardOriginMeta,
|
|
91137
91667
|
...isQueuedMidTurn || isQueuedPrefix ? { queued: "true" } : {},
|
|
91138
91668
|
...isSteering ? { steering: "true" } : {},
|
|
91139
91669
|
...priorTurnInProgress ? { prior_turn_in_progress: "true" } : {},
|
|
@@ -93238,12 +93768,33 @@ function newestActiveTurnStartedAtMs() {
|
|
|
93238
93768
|
function wouldFireFleetAutoFallback() {
|
|
93239
93769
|
return fleetFallbackGate.wouldFire();
|
|
93240
93770
|
}
|
|
93241
|
-
async function fireFleetAutoFallback(triggerAgent, untilMs) {
|
|
93242
|
-
return fleetFallbackGate.fire(() => doFireFleetAutoFallback(triggerAgent, untilMs), (err) => {
|
|
93771
|
+
async function fireFleetAutoFallback(triggerAgent, untilMs, parsedResetAt, trigger) {
|
|
93772
|
+
return fleetFallbackGate.fire(() => doFireFleetAutoFallback(triggerAgent, untilMs, parsedResetAt, trigger), (err) => {
|
|
93243
93773
|
process.stderr.write(`telegram gateway: [fleet-fallback] error agent=${triggerAgent}: ${err?.message ?? err}
|
|
93244
93774
|
`);
|
|
93245
93775
|
});
|
|
93246
93776
|
}
|
|
93777
|
+
var throttleTierRunner = createThrottleTierRunner({
|
|
93778
|
+
agentName: process.env.SWITCHROOM_AGENT_NAME ?? "",
|
|
93779
|
+
getBrokerClient: () => getAuthBrokerClient2(process.env.SWITCHROOM_AGENT_NAME ?? ""),
|
|
93780
|
+
listNoticeChats: () => loadAccess().allowFrom,
|
|
93781
|
+
sendNotice: (chat_id, markdown) => {
|
|
93782
|
+
swallowingApiCall(() => bot.api.sendRichMessage(chat_id, richMessage2(markdown), { disable_notification: true }), { chat_id: String(chat_id), verb: "throttle-tier:notify" });
|
|
93783
|
+
},
|
|
93784
|
+
resumeDecide: (ts) => fleetFallbackResumeGate.decide(ts),
|
|
93785
|
+
newestActiveTurnStartedAtMs,
|
|
93786
|
+
turnInFlight: () => turnInFlightForGate(),
|
|
93787
|
+
deferRestartToTurnComplete: (agentName3, reason) => {
|
|
93788
|
+
process.stderr.write(`telegram gateway: [throttle-tier] restart deferred to turn-complete agent=${agentName3} reason=${reason}
|
|
93789
|
+
`);
|
|
93790
|
+
pendingRestarts.set(agentName3, Date.now());
|
|
93791
|
+
},
|
|
93792
|
+
restartNow: (agentName3, reason) => {
|
|
93793
|
+
triggerSelfRestart(agentName3, reason);
|
|
93794
|
+
},
|
|
93795
|
+
log: (m) => process.stderr.write(`telegram gateway: ${m}
|
|
93796
|
+
`)
|
|
93797
|
+
});
|
|
93247
93798
|
var fallbackFailureNoticeState = { lastSentAtMs: 0 };
|
|
93248
93799
|
var fallbackAllBlockedNoticeState = { lastSentAtMs: 0 };
|
|
93249
93800
|
function broadcastFleetFallbackFailure(triggerAgent, reason) {
|
|
@@ -93264,7 +93815,7 @@ function broadcastFleetFallbackFailure(triggerAgent, reason) {
|
|
|
93264
93815
|
swallowingApiCall(() => bot.api.sendRichMessage(chat_id, richMessage2(html), {}), { chat_id, verb: "fleet-fallback:failure-notify" });
|
|
93265
93816
|
}
|
|
93266
93817
|
}
|
|
93267
|
-
async function doFireFleetAutoFallback(triggerAgent, untilMs) {
|
|
93818
|
+
async function doFireFleetAutoFallback(triggerAgent, untilMs, parsedResetAt, trigger) {
|
|
93268
93819
|
try {
|
|
93269
93820
|
const client3 = await getAuthBrokerClient2(triggerAgent);
|
|
93270
93821
|
if (!client3) {
|
|
@@ -93288,7 +93839,9 @@ async function doFireFleetAutoFallback(triggerAgent, untilMs) {
|
|
|
93288
93839
|
return { rolledTo: r.rolledTo ?? null, rolled: r.rolled };
|
|
93289
93840
|
},
|
|
93290
93841
|
triggerAgent,
|
|
93291
|
-
tz
|
|
93842
|
+
tz,
|
|
93843
|
+
parsedResetAt,
|
|
93844
|
+
rateLimitTrigger: trigger === "rate-limit"
|
|
93292
93845
|
});
|
|
93293
93846
|
process.stderr.write(`telegram gateway: [fleet-fallback] outcome=${outcome.kind} agent=${triggerAgent}` + (outcome.kind === "switched" ? ` old=${outcome.oldLabel} new=${outcome.newLabel}` : "") + `
|
|
93294
93847
|
`);
|