switchroom 0.18.15 → 0.18.17
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 +3 -0
- package/dist/auth-broker/index.js +432 -10
- package/dist/cli/notion-write-pretool.mjs +3 -0
- package/dist/cli/switchroom.js +50 -1
- package/dist/host-control/main.js +4 -1
- package/dist/vault/approvals/kernel-server.js +3 -0
- package/dist/vault/broker/server.js +3 -0
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +81 -139
- package/telegram-plugin/dist/gateway/gateway.js +386 -259
- package/telegram-plugin/draft-stream.ts +78 -3
- package/telegram-plugin/gateway/bridge-dead-watchdog.ts +3 -4
- package/telegram-plugin/gateway/effort-command.ts +9 -7
- package/telegram-plugin/gateway/gateway.ts +265 -220
- package/telegram-plugin/gateway/litellm-local-notice-wiring.ts +200 -0
- package/telegram-plugin/gateway/model-command.ts +96 -18
- package/telegram-plugin/gateway/pending-session-command.ts +10 -8
- package/telegram-plugin/gateway/session-model-file.ts +38 -172
- package/telegram-plugin/litellm-local-notice.ts +189 -0
- package/telegram-plugin/quota-watch.ts +16 -4
- package/telegram-plugin/runtime-metrics.ts +16 -0
- package/telegram-plugin/send-gate-degraded.test.ts +9 -7
- package/telegram-plugin/send-gate.ts +34 -4
- package/telegram-plugin/stream-controller.ts +143 -20
- package/telegram-plugin/stream-reply-handler.ts +12 -2
- package/telegram-plugin/tests/bot-api.harness.ts +7 -2
- package/telegram-plugin/tests/draft-stream.test.ts +110 -1
- package/telegram-plugin/tests/effort-command.test.ts +4 -4
- package/telegram-plugin/tests/flood-windows-persistence.test.ts +2 -2
- package/telegram-plugin/tests/gateway-pending-command-wiring.test.ts +33 -19
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +47 -127
- package/telegram-plugin/tests/litellm-local-notice.test.ts +417 -0
- package/telegram-plugin/tests/model-command.test.ts +84 -1
- package/telegram-plugin/tests/quota-watch.test.ts +21 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +2 -2
- package/telegram-plugin/tests/session-model-file.test.ts +7 -155
- package/telegram-plugin/tests/stream-controller-send-gate.test.ts +521 -0
- package/telegram-plugin/tests/stream-reply-handler.test.ts +44 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +207 -0
- package/telegram-plugin/worker-activity-feed.ts +83 -8
|
@@ -20534,6 +20534,9 @@ var init_schema = __esm(() => {
|
|
|
20534
20534
|
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged)."),
|
|
20535
20535
|
max_attachments: exports_external.number().int().positive().optional().describe("Maximum number of media attachments carried into ONE coalesced " + "Claude turn. Default 10 \u2014 a full Telegram album (media_group caps " + "at 10) or a text+multi-image forwarded burst arrives as a single " + "turn; the agent sees numbered attachment fields (image_path, " + "image_path_2, \u2026). Set 1 to restore the historical " + "single-attachment-per-turn behaviour. Excess attachments beyond " + "the cap spill into the next turn. Each attachment is downloaded, " + "so a high cap on a slow link delays turn start.")
|
|
20536
20536
|
}).optional().describe("Inbound coalescing \u2014 how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
20537
|
+
litellm_notice: exports_external.object({
|
|
20538
|
+
window_ms: exports_external.number().int().positive().optional().describe("Per-agent cooldown window (ms) for the litellm-local 429 notice. " + "When the agent trips the LiteLLM proxy's OWN tpm_limit/rpm_limit " + "cap (a `litellm-local` classified 429 \u2014 see docs/auth.md \u00a7 " + "LiteLLM-proxy-local 429s), the gateway posts ONE calm notice " + "naming the fleet token limiter, then counts further hits silently " + "for this long; the first notice after the window expires says " + "how many were absorbed. Default 900000 (15 min). Invalid values " + "fall back to the default.")
|
|
20539
|
+
}).optional().describe("Debounce tuning for the litellm-local throttle notice \u2014 the calm " + "'fleet token limiter engaged' message posted when the LiteLLM " + "proxy's own rate cap trips (never an Anthropic account limit). " + "Cascades from defaults.channels.telegram.litellm_notice."),
|
|
20537
20540
|
interrupt: exports_external.object({
|
|
20538
20541
|
safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
|
|
20539
20542
|
max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short \u2014 the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
|
|
@@ -40236,6 +40239,60 @@ function renderActivityFeedWithNested(lines, childLines, final = false, liveSuff
|
|
|
40236
40239
|
});
|
|
40237
40240
|
}
|
|
40238
40241
|
|
|
40242
|
+
// retry-api-call.ts
|
|
40243
|
+
var import_grammy = __toESM(require_mod2(), 1);
|
|
40244
|
+
var FLOOD_WAIT_ACTIVE = "FLOOD_WAIT_ACTIVE";
|
|
40245
|
+
function makeFloodWaitActiveError(retryAfterSec, untilTs, original) {
|
|
40246
|
+
return Object.assign(new Error(FLOOD_WAIT_ACTIVE), {
|
|
40247
|
+
retryAfterSec,
|
|
40248
|
+
untilTs,
|
|
40249
|
+
error_code: 429,
|
|
40250
|
+
parameters: { retry_after: retryAfterSec },
|
|
40251
|
+
original
|
|
40252
|
+
});
|
|
40253
|
+
}
|
|
40254
|
+
function isFloodWaitActiveError(err) {
|
|
40255
|
+
return err instanceof Error && err.message === FLOOD_WAIT_ACTIVE;
|
|
40256
|
+
}
|
|
40257
|
+
async function retryWithThreadFallback(retry, send, opts) {
|
|
40258
|
+
try {
|
|
40259
|
+
return await retry(() => send(opts.threadId), {
|
|
40260
|
+
...opts.threadId != null ? { threadId: opts.threadId } : {},
|
|
40261
|
+
chat_id: opts.chat_id,
|
|
40262
|
+
...opts.verb != null ? { verb: opts.verb } : {}
|
|
40263
|
+
});
|
|
40264
|
+
} catch (err) {
|
|
40265
|
+
if (err instanceof Error && err.message === "THREAD_NOT_FOUND") {
|
|
40266
|
+
return await retry(() => send(undefined), {
|
|
40267
|
+
chat_id: opts.chat_id,
|
|
40268
|
+
...opts.verb != null ? { verb: opts.verb } : {}
|
|
40269
|
+
});
|
|
40270
|
+
}
|
|
40271
|
+
throw err;
|
|
40272
|
+
}
|
|
40273
|
+
}
|
|
40274
|
+
function isHtmlParseRejectError(err) {
|
|
40275
|
+
if (!(err instanceof import_grammy.GrammyError) || err.error_code !== 400)
|
|
40276
|
+
return false;
|
|
40277
|
+
if (isMessageTooLongError(err))
|
|
40278
|
+
return false;
|
|
40279
|
+
const d = (err.description || "").toLowerCase();
|
|
40280
|
+
return d.includes("can't parse entities") || d.includes("can\u2019t parse entities") || d.includes("can't parse") || d.includes("can\u2019t parse") || d.includes("parse markdown") || d.includes("parse rich") || d.includes("unsupported start tag") || d.includes("unclosed start tag") || d.includes("can't find end of the entity") || d.includes("can\u2019t find end of the entity") || d.includes("expected end tag");
|
|
40281
|
+
}
|
|
40282
|
+
function isMessageTooLongError(err) {
|
|
40283
|
+
if (!(err instanceof import_grammy.GrammyError) || err.error_code !== 400)
|
|
40284
|
+
return false;
|
|
40285
|
+
const d = (err.description || "").toLowerCase();
|
|
40286
|
+
return d.includes("rich_message_text_too_long") || d.includes("message_too_long") || d.includes("text_too_long") || d.includes("message is too long") || d.includes("text is too long");
|
|
40287
|
+
}
|
|
40288
|
+
|
|
40289
|
+
// send-gate.ts
|
|
40290
|
+
var SEND_GATE_SHED = Symbol.for("switchroom.send-gate.shed");
|
|
40291
|
+
function isSendGateShed(value) {
|
|
40292
|
+
return value === SEND_GATE_SHED;
|
|
40293
|
+
}
|
|
40294
|
+
var GROUP_TYPES = new Set(["group", "supergroup"]);
|
|
40295
|
+
|
|
40239
40296
|
// worker-activity-feed.ts
|
|
40240
40297
|
function isWorkerActivityFeedEnabled(envVal) {
|
|
40241
40298
|
return envVal !== "0";
|
|
@@ -40310,6 +40367,7 @@ function classifyEditError(err) {
|
|
|
40310
40367
|
function createWorkerActivityFeed(opts) {
|
|
40311
40368
|
const log = opts.log ?? (() => {});
|
|
40312
40369
|
const nowFn = opts.now ?? Date.now;
|
|
40370
|
+
const floodWaitRemainingMs = opts.floodWaitRemainingMs ?? (() => 0);
|
|
40313
40371
|
const minEditInterval = opts.minEditIntervalMs ?? 2500;
|
|
40314
40372
|
const firstPaintMin = opts.firstPaintMinMs ?? 8000;
|
|
40315
40373
|
const heartbeatTickMs = opts.heartbeatTickMs ?? 6000;
|
|
@@ -40347,6 +40405,15 @@ function createWorkerActivityFeed(opts) {
|
|
|
40347
40405
|
h.cooldownUntil = nowFn() + retryAfter * 1000 + COOLDOWN_JITTER_MS;
|
|
40348
40406
|
log(`worker-feed: ${label} 429 \u2014 backing off ${retryAfter}s`);
|
|
40349
40407
|
}
|
|
40408
|
+
function parkIfFloodWindowOpen(h) {
|
|
40409
|
+
const remaining = floodWaitRemainingMs();
|
|
40410
|
+
if (remaining <= 0)
|
|
40411
|
+
return false;
|
|
40412
|
+
const until = nowFn() + remaining + COOLDOWN_JITTER_MS;
|
|
40413
|
+
if (until > h.cooldownUntil)
|
|
40414
|
+
h.cooldownUntil = until;
|
|
40415
|
+
return true;
|
|
40416
|
+
}
|
|
40350
40417
|
function accumulateNarrative(h, view) {
|
|
40351
40418
|
const line = view.latestSummary.trim();
|
|
40352
40419
|
if (line.length === 0)
|
|
@@ -40367,12 +40434,19 @@ function createWorkerActivityFeed(opts) {
|
|
|
40367
40434
|
h.dispatchAtMs = nowFn() - view.elapsedMs;
|
|
40368
40435
|
if (nowFn() < h.cooldownUntil)
|
|
40369
40436
|
return;
|
|
40437
|
+
if (parkIfFloodWindowOpen(h))
|
|
40438
|
+
return;
|
|
40370
40439
|
const body = renderWorkerActivity(merged, liveSuffix);
|
|
40371
40440
|
if (h.messageId == null) {
|
|
40372
40441
|
if (view.elapsedMs < firstPaintMin)
|
|
40373
40442
|
return;
|
|
40374
40443
|
try {
|
|
40375
40444
|
const sent = await opts.bot.sendMessage(h.chatId, body, sendOptsFor(h));
|
|
40445
|
+
if (sent == null || typeof sent.message_id !== "number") {
|
|
40446
|
+
parkIfFloodWindowOpen(h);
|
|
40447
|
+
log(`worker-feed: first paint shed by send gate agent=${h.agentId} \u2014 not delivered`);
|
|
40448
|
+
return;
|
|
40449
|
+
}
|
|
40376
40450
|
h.messageId = sent.message_id;
|
|
40377
40451
|
h.lastBody = body;
|
|
40378
40452
|
h.lastEditAt = nowFn();
|
|
@@ -40388,7 +40462,11 @@ function createWorkerActivityFeed(opts) {
|
|
|
40388
40462
|
if (nowFn() - h.lastEditAt < minEditInterval)
|
|
40389
40463
|
return;
|
|
40390
40464
|
try {
|
|
40391
|
-
await opts.bot.editMessageText(h.chatId, h.messageId, body, sendOptsFor(h));
|
|
40465
|
+
const res = await opts.bot.editMessageText(h.chatId, h.messageId, body, sendOptsFor(h));
|
|
40466
|
+
if (isSendGateShed(res)) {
|
|
40467
|
+
parkIfFloodWindowOpen(h);
|
|
40468
|
+
return;
|
|
40469
|
+
}
|
|
40392
40470
|
h.lastBody = body;
|
|
40393
40471
|
h.lastEditAt = nowFn();
|
|
40394
40472
|
log(`worker-feed: edit agent=${h.agentId} chat=${h.chatId} ` + `thread=${h.threadId ?? "-"} msgId=${h.messageId} bytes=${body.length}`);
|
|
@@ -40418,7 +40496,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40418
40496
|
h.pendingFinish = null;
|
|
40419
40497
|
return;
|
|
40420
40498
|
}
|
|
40421
|
-
if (nowFn() < h.cooldownUntil) {
|
|
40499
|
+
if (parkIfFloodWindowOpen(h) || nowFn() < h.cooldownUntil) {
|
|
40422
40500
|
h.pendingFinish = view;
|
|
40423
40501
|
return;
|
|
40424
40502
|
}
|
|
@@ -40428,7 +40506,12 @@ function createWorkerActivityFeed(opts) {
|
|
|
40428
40506
|
return;
|
|
40429
40507
|
}
|
|
40430
40508
|
try {
|
|
40431
|
-
await opts.bot.editMessageText(h.chatId, h.messageId, body, sendOptsFor(h));
|
|
40509
|
+
const res = await opts.bot.editMessageText(h.chatId, h.messageId, body, sendOptsFor(h));
|
|
40510
|
+
if (isSendGateShed(res)) {
|
|
40511
|
+
parkIfFloodWindowOpen(h);
|
|
40512
|
+
h.pendingFinish = view;
|
|
40513
|
+
return;
|
|
40514
|
+
}
|
|
40432
40515
|
h.lastBody = body;
|
|
40433
40516
|
h.lastEditAt = nowFn();
|
|
40434
40517
|
h.pendingFinish = null;
|
|
@@ -41022,55 +41105,6 @@ var STALE_TAP_NOTICE = "This request already resolved (timed out) \u2014 ask aga
|
|
|
41022
41105
|
var import_grammy2 = __toESM(require_mod2(), 1);
|
|
41023
41106
|
var import_runner = __toESM(require_mod4(), 1);
|
|
41024
41107
|
import { AsyncLocalStorage } from "async_hooks";
|
|
41025
|
-
|
|
41026
|
-
// retry-api-call.ts
|
|
41027
|
-
var import_grammy = __toESM(require_mod2(), 1);
|
|
41028
|
-
var FLOOD_WAIT_ACTIVE = "FLOOD_WAIT_ACTIVE";
|
|
41029
|
-
function makeFloodWaitActiveError(retryAfterSec, untilTs, original) {
|
|
41030
|
-
return Object.assign(new Error(FLOOD_WAIT_ACTIVE), {
|
|
41031
|
-
retryAfterSec,
|
|
41032
|
-
untilTs,
|
|
41033
|
-
error_code: 429,
|
|
41034
|
-
parameters: { retry_after: retryAfterSec },
|
|
41035
|
-
original
|
|
41036
|
-
});
|
|
41037
|
-
}
|
|
41038
|
-
function isFloodWaitActiveError(err) {
|
|
41039
|
-
return err instanceof Error && err.message === FLOOD_WAIT_ACTIVE;
|
|
41040
|
-
}
|
|
41041
|
-
async function retryWithThreadFallback(retry, send, opts) {
|
|
41042
|
-
try {
|
|
41043
|
-
return await retry(() => send(opts.threadId), {
|
|
41044
|
-
...opts.threadId != null ? { threadId: opts.threadId } : {},
|
|
41045
|
-
chat_id: opts.chat_id,
|
|
41046
|
-
...opts.verb != null ? { verb: opts.verb } : {}
|
|
41047
|
-
});
|
|
41048
|
-
} catch (err) {
|
|
41049
|
-
if (err instanceof Error && err.message === "THREAD_NOT_FOUND") {
|
|
41050
|
-
return await retry(() => send(undefined), {
|
|
41051
|
-
chat_id: opts.chat_id,
|
|
41052
|
-
...opts.verb != null ? { verb: opts.verb } : {}
|
|
41053
|
-
});
|
|
41054
|
-
}
|
|
41055
|
-
throw err;
|
|
41056
|
-
}
|
|
41057
|
-
}
|
|
41058
|
-
function isHtmlParseRejectError(err) {
|
|
41059
|
-
if (!(err instanceof import_grammy.GrammyError) || err.error_code !== 400)
|
|
41060
|
-
return false;
|
|
41061
|
-
if (isMessageTooLongError(err))
|
|
41062
|
-
return false;
|
|
41063
|
-
const d = (err.description || "").toLowerCase();
|
|
41064
|
-
return d.includes("can't parse entities") || d.includes("can\u2019t parse entities") || d.includes("can't parse") || d.includes("can\u2019t parse") || d.includes("parse markdown") || d.includes("parse rich") || d.includes("unsupported start tag") || d.includes("unclosed start tag") || d.includes("can't find end of the entity") || d.includes("can\u2019t find end of the entity") || d.includes("expected end tag");
|
|
41065
|
-
}
|
|
41066
|
-
function isMessageTooLongError(err) {
|
|
41067
|
-
if (!(err instanceof import_grammy.GrammyError) || err.error_code !== 400)
|
|
41068
|
-
return false;
|
|
41069
|
-
const d = (err.description || "").toLowerCase();
|
|
41070
|
-
return d.includes("rich_message_text_too_long") || d.includes("message_too_long") || d.includes("text_too_long") || d.includes("message is too long") || d.includes("text is too long");
|
|
41071
|
-
}
|
|
41072
|
-
|
|
41073
|
-
// shared/bot-runtime.ts
|
|
41074
41108
|
init_flood_circuit_breaker();
|
|
41075
41109
|
|
|
41076
41110
|
// shared/gw-trace-gate.ts
|
|
@@ -45307,6 +45341,12 @@ function createTypingEmitter(deps) {
|
|
|
45307
45341
|
|
|
45308
45342
|
// draft-stream.ts
|
|
45309
45343
|
var TELEGRAM_MAX_CHARS = 32768;
|
|
45344
|
+
function makeDraftEditShedError(messageId) {
|
|
45345
|
+
return Object.assign(new Error(`draft edit shed by send gate (id=${messageId ?? "unknown"}); snapshot preserved for re-flush`), { draftEditShed: true });
|
|
45346
|
+
}
|
|
45347
|
+
function isDraftEditShedError(err) {
|
|
45348
|
+
return err instanceof Error && err.draftEditShed === true;
|
|
45349
|
+
}
|
|
45310
45350
|
var DEFAULT_DM_THROTTLE_MS = 400;
|
|
45311
45351
|
var DEFAULT_GROUP_THROTTLE_MS = 1000;
|
|
45312
45352
|
var MIN_THROTTLE_MS = 250;
|
|
@@ -45325,6 +45365,7 @@ function createDraftStream(send, edit, config = {}) {
|
|
|
45325
45365
|
let messageId = config.initialMessageId ?? null;
|
|
45326
45366
|
let pendingText = null;
|
|
45327
45367
|
let lastSentText = null;
|
|
45368
|
+
let shedText = null;
|
|
45328
45369
|
let lastSentAt = 0;
|
|
45329
45370
|
let inFlight = null;
|
|
45330
45371
|
const streamStartedAt = Date.now();
|
|
@@ -45368,11 +45409,16 @@ function createDraftStream(send, edit, config = {}) {
|
|
|
45368
45409
|
await sendViaMessage(textToSend);
|
|
45369
45410
|
lastSentText = textToSend;
|
|
45370
45411
|
lastSentAt = Date.now();
|
|
45412
|
+
shedText = null;
|
|
45371
45413
|
} catch (err) {
|
|
45372
45414
|
const msg = err.message ?? String(err);
|
|
45373
|
-
if (
|
|
45415
|
+
if (isDraftEditShedError(err)) {
|
|
45416
|
+
shedText = textToSend;
|
|
45417
|
+
log?.(`stream \u2192 shed by send gate (id: ${messageId}); snapshot preserved for re-flush`);
|
|
45418
|
+
} else if (/\bmessage is not modified\b/i.test(msg)) {
|
|
45374
45419
|
lastSentText = textToSend;
|
|
45375
45420
|
lastSentAt = Date.now();
|
|
45421
|
+
shedText = null;
|
|
45376
45422
|
log?.(`stream \u2192 not modified (id: ${messageId})`);
|
|
45377
45423
|
} else if (/\bmessage to edit not found\b/i.test(msg) || /\bMESSAGE_ID_INVALID\b/i.test(msg)) {
|
|
45378
45424
|
log?.(`stream \u2192 message not found (id: ${messageId}), re-sending`);
|
|
@@ -45465,10 +45511,12 @@ function createDraftStream(send, edit, config = {}) {
|
|
|
45465
45511
|
}
|
|
45466
45512
|
return waitPromise;
|
|
45467
45513
|
},
|
|
45468
|
-
async finalize() {
|
|
45514
|
+
async finalize(finalText) {
|
|
45469
45515
|
if (final)
|
|
45470
45516
|
return;
|
|
45471
45517
|
final = true;
|
|
45518
|
+
if (finalText != null && !stopped)
|
|
45519
|
+
pendingText = finalText;
|
|
45472
45520
|
if (scheduledTimer != null) {
|
|
45473
45521
|
clearTimeout(scheduledTimer);
|
|
45474
45522
|
scheduledTimer = null;
|
|
@@ -45476,6 +45524,10 @@ function createDraftStream(send, edit, config = {}) {
|
|
|
45476
45524
|
if (inFlight) {
|
|
45477
45525
|
await inFlight;
|
|
45478
45526
|
}
|
|
45527
|
+
if (pendingText == null && shedText != null && !stopped) {
|
|
45528
|
+
pendingText = shedText;
|
|
45529
|
+
}
|
|
45530
|
+
shedText = null;
|
|
45479
45531
|
if (pendingText != null && !stopped) {
|
|
45480
45532
|
await flush();
|
|
45481
45533
|
}
|
|
@@ -54912,28 +54964,41 @@ function createStreamController(cfg) {
|
|
|
54912
54964
|
return bot.api.editMessageText(chatId, id, piece.text, opts);
|
|
54913
54965
|
return bot.api.editMessageText(chatId, id, richMessage(piece.text), opts);
|
|
54914
54966
|
};
|
|
54967
|
+
let handleRef = null;
|
|
54968
|
+
const editGateOpts = (id, payload) => ({
|
|
54969
|
+
threadId,
|
|
54970
|
+
chat_id: chatId,
|
|
54971
|
+
messageId: id,
|
|
54972
|
+
editPayload: payload,
|
|
54973
|
+
priorityClass: handleRef?.isFinal() === true ? "critical" : "cosmetic"
|
|
54974
|
+
});
|
|
54975
|
+
const piecePayload = (piece) => piece.rich ? richMessage(piece.text) : piece.text;
|
|
54915
54976
|
const tailIds = [];
|
|
54916
54977
|
const tailLastText = [];
|
|
54917
54978
|
const upsertTail = async (ti, piece) => {
|
|
54918
54979
|
const existingId = tailIds[ti];
|
|
54919
54980
|
if (existingId != null) {
|
|
54920
54981
|
if (tailLastText[ti] === piece.text)
|
|
54921
|
-
return;
|
|
54982
|
+
return false;
|
|
54922
54983
|
try {
|
|
54923
|
-
await retry(() => editPiece(existingId, piece, baseOpts),
|
|
54984
|
+
const res = await retry(() => editPiece(existingId, piece, baseOpts), editGateOpts(existingId, piecePayload(piece)));
|
|
54985
|
+
if (isSendGateShed(res))
|
|
54986
|
+
return true;
|
|
54924
54987
|
tailLastText[ti] = piece.text;
|
|
54925
54988
|
onEdit?.(existingId, piece.text.length);
|
|
54926
54989
|
} catch (err) {
|
|
54927
54990
|
if (!literalText && piece.rich && isParseEntitiesError(err)) {
|
|
54928
54991
|
warn?.(`stream-controller: tail-piece #${ti + 1} edit parse-entities rejected \u2014 retrying same id=${existingId} as plain text (${err instanceof Error ? err.message : String(err)})`);
|
|
54929
|
-
await retry(() => bot.api.editMessageText(chatId, existingId, piece.text, baseOpts),
|
|
54992
|
+
const res = await retry(() => bot.api.editMessageText(chatId, existingId, piece.text, baseOpts), editGateOpts(existingId, piece.text));
|
|
54993
|
+
if (isSendGateShed(res))
|
|
54994
|
+
return true;
|
|
54930
54995
|
tailLastText[ti] = piece.text;
|
|
54931
54996
|
onEdit?.(existingId, piece.text.length);
|
|
54932
54997
|
} else {
|
|
54933
54998
|
warn?.(`stream-controller: tail-piece #${ti + 1} edit FAILED (id=${existingId}) \u2014 partial delivery, this piece may be stale (${err instanceof Error ? err.message : String(err)})`);
|
|
54934
54999
|
}
|
|
54935
55000
|
}
|
|
54936
|
-
return;
|
|
55001
|
+
return false;
|
|
54937
55002
|
}
|
|
54938
55003
|
try {
|
|
54939
55004
|
const sent = await retry(() => sendPiece(piece, sendOpts), { threadId, chat_id: chatId });
|
|
@@ -54951,8 +55016,9 @@ function createStreamController(cfg) {
|
|
|
54951
55016
|
warn?.(`stream-controller: tail-piece #${ti + 1} send FAILED \u2014 partial delivery, this and later pieces may be missing this flush (${err instanceof Error ? err.message : String(err)})`);
|
|
54952
55017
|
}
|
|
54953
55018
|
}
|
|
55019
|
+
return false;
|
|
54954
55020
|
};
|
|
54955
|
-
|
|
55021
|
+
const handle = createDraftStream(async (text4) => {
|
|
54956
55022
|
const pieces = renderPieces(text4);
|
|
54957
55023
|
const head = pieces[0];
|
|
54958
55024
|
let anchorId;
|
|
@@ -54977,22 +55043,34 @@ function createStreamController(cfg) {
|
|
|
54977
55043
|
}, async (id, text4) => {
|
|
54978
55044
|
const pieces = renderPieces(text4);
|
|
54979
55045
|
const head = pieces[0];
|
|
55046
|
+
let anchorShed = false;
|
|
54980
55047
|
try {
|
|
54981
|
-
await retry(() => editPiece(id, head, baseOpts),
|
|
54982
|
-
|
|
55048
|
+
const res = await retry(() => editPiece(id, head, baseOpts), editGateOpts(id, piecePayload(head)));
|
|
55049
|
+
if (isSendGateShed(res)) {
|
|
55050
|
+
anchorShed = true;
|
|
55051
|
+
} else {
|
|
55052
|
+
onEdit?.(id, head.text.length);
|
|
55053
|
+
}
|
|
54983
55054
|
} catch (err) {
|
|
54984
55055
|
if (!literalText && head.rich && isParseEntitiesError(err)) {
|
|
54985
55056
|
warn?.(`stream-controller: edit parse-entities rejected \u2014 retrying same id=${id} as plain text (${err instanceof Error ? err.message : String(err)})`);
|
|
54986
55057
|
const fallbackBody = pieces.length === 1 ? text4 : head.text;
|
|
54987
|
-
await retry(() => bot.api.editMessageText(chatId, id, fallbackBody, baseOpts),
|
|
54988
|
-
|
|
55058
|
+
const res = await retry(() => bot.api.editMessageText(chatId, id, fallbackBody, baseOpts), editGateOpts(id, fallbackBody));
|
|
55059
|
+
if (isSendGateShed(res))
|
|
55060
|
+
anchorShed = true;
|
|
55061
|
+
else
|
|
55062
|
+
onEdit?.(id, head.text.length);
|
|
54989
55063
|
} else {
|
|
54990
55064
|
throw err;
|
|
54991
55065
|
}
|
|
54992
55066
|
}
|
|
55067
|
+
let anyTailShed = false;
|
|
54993
55068
|
for (let pi = 1;pi < pieces.length; pi++) {
|
|
54994
|
-
await upsertTail(pi - 1, pieces[pi])
|
|
55069
|
+
if (await upsertTail(pi - 1, pieces[pi]))
|
|
55070
|
+
anyTailShed = true;
|
|
54995
55071
|
}
|
|
55072
|
+
if (anchorShed || anyTailShed)
|
|
55073
|
+
throw makeDraftEditShedError(id);
|
|
54996
55074
|
}, {
|
|
54997
55075
|
...throttleMs != null ? { throttleMs } : {},
|
|
54998
55076
|
...idleMs != null ? { idleMs } : {},
|
|
@@ -55002,6 +55080,8 @@ function createStreamController(cfg) {
|
|
|
55002
55080
|
...initialMessageId != null ? { initialMessageId } : {},
|
|
55003
55081
|
chatId
|
|
55004
55082
|
});
|
|
55083
|
+
handleRef = handle;
|
|
55084
|
+
return handle;
|
|
55005
55085
|
}
|
|
55006
55086
|
|
|
55007
55087
|
// pty-partial-handler.ts
|
|
@@ -55285,7 +55365,7 @@ var systemClock = {
|
|
|
55285
55365
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms))
|
|
55286
55366
|
};
|
|
55287
55367
|
var UNTAGGED_SEND_CLASS = "critical";
|
|
55288
|
-
|
|
55368
|
+
var SEND_GATE_SHED2 = Symbol.for("switchroom.send-gate.shed");
|
|
55289
55369
|
class TokenBucket {
|
|
55290
55370
|
capacity;
|
|
55291
55371
|
refillPerMs;
|
|
@@ -55363,7 +55443,7 @@ function stableStringify(value) {
|
|
|
55363
55443
|
return v;
|
|
55364
55444
|
});
|
|
55365
55445
|
}
|
|
55366
|
-
var
|
|
55446
|
+
var GROUP_TYPES2 = new Set(["group", "supergroup"]);
|
|
55367
55447
|
function createSendGate(config) {
|
|
55368
55448
|
const enabled2 = config.enabled;
|
|
55369
55449
|
const clock = config.clock ?? systemClock;
|
|
@@ -55439,7 +55519,7 @@ function createSendGate(config) {
|
|
|
55439
55519
|
const buckets = [globalBucket];
|
|
55440
55520
|
if (opts?.chat_id) {
|
|
55441
55521
|
buckets.push(chatBucket(opts.chat_id));
|
|
55442
|
-
if (opts.chatType &&
|
|
55522
|
+
if (opts.chatType && GROUP_TYPES2.has(opts.chatType)) {
|
|
55443
55523
|
buckets.push(groupBucket(opts.chat_id));
|
|
55444
55524
|
}
|
|
55445
55525
|
}
|
|
@@ -55454,7 +55534,7 @@ function createSendGate(config) {
|
|
|
55454
55534
|
applyWindow("global", untilTs, true);
|
|
55455
55535
|
if (opts?.chat_id) {
|
|
55456
55536
|
applyWindow(`chat:${opts.chat_id}`, untilTs, true);
|
|
55457
|
-
if (opts.chatType &&
|
|
55537
|
+
if (opts.chatType && GROUP_TYPES2.has(opts.chatType)) {
|
|
55458
55538
|
applyWindow(`group:${opts.chat_id}`, untilTs, true);
|
|
55459
55539
|
}
|
|
55460
55540
|
}
|
|
@@ -55668,7 +55748,7 @@ function createSendGate(config) {
|
|
|
55668
55748
|
const msgWait = state.suppressedUntilMs > now ? state.suppressedUntilMs - now : 0;
|
|
55669
55749
|
if (wait > 0 || msgWait > 0) {
|
|
55670
55750
|
counters.shed++;
|
|
55671
|
-
return Promise.resolve(
|
|
55751
|
+
return Promise.resolve(SEND_GATE_SHED2);
|
|
55672
55752
|
}
|
|
55673
55753
|
}
|
|
55674
55754
|
if (state.pending) {
|
|
@@ -55713,7 +55793,7 @@ function createSendGate(config) {
|
|
|
55713
55793
|
const outcome = await admitPriority(bucketsFor(opts), priority);
|
|
55714
55794
|
if (outcome.result === "shed") {
|
|
55715
55795
|
counters.shed++;
|
|
55716
|
-
return;
|
|
55796
|
+
return SEND_GATE_SHED2;
|
|
55717
55797
|
}
|
|
55718
55798
|
if (outcome.result === "expired") {
|
|
55719
55799
|
counters.expired++;
|
|
@@ -65325,6 +65405,132 @@ function createThrottleTierRunner(deps) {
|
|
|
65325
65405
|
};
|
|
65326
65406
|
}
|
|
65327
65407
|
|
|
65408
|
+
// litellm-local-notice.ts
|
|
65409
|
+
init_card_format();
|
|
65410
|
+
var LITELLM_LOCAL_NOTICE_WINDOW_MS_DEFAULT = 15 * 60000;
|
|
65411
|
+
function parseLitellmNoticeWindowMs(raw) {
|
|
65412
|
+
if (typeof raw !== "number")
|
|
65413
|
+
return LITELLM_LOCAL_NOTICE_WINDOW_MS_DEFAULT;
|
|
65414
|
+
if (!Number.isFinite(raw) || raw <= 0)
|
|
65415
|
+
return LITELLM_LOCAL_NOTICE_WINDOW_MS_DEFAULT;
|
|
65416
|
+
return raw;
|
|
65417
|
+
}
|
|
65418
|
+
|
|
65419
|
+
// litellm-local-notice.ts
|
|
65420
|
+
init_card_format();
|
|
65421
|
+
var LITELLM_LOCAL_NOTICE_WINDOW_MS_DEFAULT2 = 15 * 60000;
|
|
65422
|
+
function initialLitellmLocalNoticeState() {
|
|
65423
|
+
return { lastSentAtMsByAgent: {}, suppressedCountByAgent: {} };
|
|
65424
|
+
}
|
|
65425
|
+
function evaluateLitellmLocalNotice(prev, agent, now, windowMs = LITELLM_LOCAL_NOTICE_WINDOW_MS_DEFAULT2) {
|
|
65426
|
+
const last = prev.lastSentAtMsByAgent[agent];
|
|
65427
|
+
if (last == null || now - last >= windowMs) {
|
|
65428
|
+
return {
|
|
65429
|
+
send: true,
|
|
65430
|
+
suppressedSinceLastNotice: prev.suppressedCountByAgent[agent] ?? 0,
|
|
65431
|
+
next: {
|
|
65432
|
+
lastSentAtMsByAgent: { ...prev.lastSentAtMsByAgent, [agent]: now },
|
|
65433
|
+
suppressedCountByAgent: { ...prev.suppressedCountByAgent, [agent]: 0 }
|
|
65434
|
+
}
|
|
65435
|
+
};
|
|
65436
|
+
}
|
|
65437
|
+
return {
|
|
65438
|
+
send: false,
|
|
65439
|
+
suppressedSinceLastNotice: 0,
|
|
65440
|
+
next: {
|
|
65441
|
+
lastSentAtMsByAgent: prev.lastSentAtMsByAgent,
|
|
65442
|
+
suppressedCountByAgent: {
|
|
65443
|
+
...prev.suppressedCountByAgent,
|
|
65444
|
+
[agent]: (prev.suppressedCountByAgent[agent] ?? 0) + 1
|
|
65445
|
+
}
|
|
65446
|
+
}
|
|
65447
|
+
};
|
|
65448
|
+
}
|
|
65449
|
+
function renderLitellmLocalNotice(opts) {
|
|
65450
|
+
const agent = escapeMarkdown(opts.agent);
|
|
65451
|
+
const n = opts.suppressedSinceLastNotice;
|
|
65452
|
+
const lines = [
|
|
65453
|
+
`\uD83D\uDEA6 **Fleet token limiter engaged** \u2014 **${agent}** hit the local LiteLLM proxy cap (\`tpm_limit\`/\`rpm_limit\`).`,
|
|
65454
|
+
`This is switchroom's own fleet limiter smoothing a burst, not an Anthropic account limit \u2014 nothing is exhausted and no account was touched.`
|
|
65455
|
+
];
|
|
65456
|
+
if (n > 0) {
|
|
65457
|
+
lines.push(`Throttled ${n} more ${n === 1 ? "time" : "times"} since the last notice.`);
|
|
65458
|
+
}
|
|
65459
|
+
lines.push(`_The turn retries automatically \u2014 no action needed._`);
|
|
65460
|
+
return lines.join(`
|
|
65461
|
+
`);
|
|
65462
|
+
}
|
|
65463
|
+
function buildLitellmLocalNoticeMetric(opts) {
|
|
65464
|
+
return {
|
|
65465
|
+
kind: "litellm_local_429_notice",
|
|
65466
|
+
agent: opts.agent,
|
|
65467
|
+
suppressed_count: opts.suppressedCount,
|
|
65468
|
+
window_ms: opts.windowMs
|
|
65469
|
+
};
|
|
65470
|
+
}
|
|
65471
|
+
function isLitellmLocalNoticeEligible(classification) {
|
|
65472
|
+
return classification === "litellm-local";
|
|
65473
|
+
}
|
|
65474
|
+
|
|
65475
|
+
// gateway/litellm-local-notice-wiring.ts
|
|
65476
|
+
function decideRateLimitedSurface(opts) {
|
|
65477
|
+
if (isLitellmLocalNoticeEligible(opts.classification))
|
|
65478
|
+
return "litellm-local-notice";
|
|
65479
|
+
return opts.shouldEmitCard(opts.agent) ? "generic-card" : "cooldown-suppressed";
|
|
65480
|
+
}
|
|
65481
|
+
function createLitellmLocalNoticeRunner(deps) {
|
|
65482
|
+
const now = deps.now ?? (() => Date.now());
|
|
65483
|
+
let state3 = initialLitellmLocalNoticeState();
|
|
65484
|
+
function onRateLimited(classification, agent) {
|
|
65485
|
+
try {
|
|
65486
|
+
if (!isLitellmLocalNoticeEligible(classification))
|
|
65487
|
+
return "skipped";
|
|
65488
|
+
const windowMs = deps.windowMs();
|
|
65489
|
+
const verdict = evaluateLitellmLocalNotice(state3, agent, now(), windowMs);
|
|
65490
|
+
if (!verdict.send) {
|
|
65491
|
+
state3 = verdict.next;
|
|
65492
|
+
deps.log(`[litellm-local-notice] suppressed (cooldown) agent=${agent} ` + `suppressedSoFar=${state3.suppressedCountByAgent[agent] ?? 0}`);
|
|
65493
|
+
return "suppressed";
|
|
65494
|
+
}
|
|
65495
|
+
const chats = deps.listNoticeChats();
|
|
65496
|
+
const markdown = renderLitellmLocalNotice({
|
|
65497
|
+
agent,
|
|
65498
|
+
suppressedSinceLastNotice: verdict.suppressedSinceLastNotice
|
|
65499
|
+
});
|
|
65500
|
+
let issued = 0;
|
|
65501
|
+
for (const chatId of chats) {
|
|
65502
|
+
try {
|
|
65503
|
+
deps.sendNotice(chatId, markdown);
|
|
65504
|
+
issued++;
|
|
65505
|
+
} catch (err) {
|
|
65506
|
+
deps.log(`[litellm-local-notice] send failed chat=${chatId} agent=${agent}: ` + `${err?.message ?? err}`);
|
|
65507
|
+
}
|
|
65508
|
+
}
|
|
65509
|
+
if (issued === 0) {
|
|
65510
|
+
deps.log(`[litellm-local-notice] no sends issued (chats=${chats.length}) agent=${agent} \u2014 ` + `window not armed, will retry on the next event`);
|
|
65511
|
+
return "skipped";
|
|
65512
|
+
}
|
|
65513
|
+
state3 = verdict.next;
|
|
65514
|
+
deps.emitMetric(buildLitellmLocalNoticeMetric({
|
|
65515
|
+
agent,
|
|
65516
|
+
suppressedCount: verdict.suppressedSinceLastNotice,
|
|
65517
|
+
windowMs
|
|
65518
|
+
}));
|
|
65519
|
+
deps.log(`[litellm-local-notice] posted agent=${agent} chats=${issued} ` + `suppressedSinceLast=${verdict.suppressedSinceLastNotice} windowMs=${windowMs}`);
|
|
65520
|
+
return "sent";
|
|
65521
|
+
} catch (err) {
|
|
65522
|
+
try {
|
|
65523
|
+
deps.log(`[litellm-local-notice] error agent=${agent}: ${err?.message ?? err}`);
|
|
65524
|
+
} catch {}
|
|
65525
|
+
return "skipped";
|
|
65526
|
+
}
|
|
65527
|
+
}
|
|
65528
|
+
return {
|
|
65529
|
+
onRateLimited,
|
|
65530
|
+
inspect: () => ({ state: state3 })
|
|
65531
|
+
};
|
|
65532
|
+
}
|
|
65533
|
+
|
|
65328
65534
|
// auto-fallback-fleet.ts
|
|
65329
65535
|
init_card_format();
|
|
65330
65536
|
init_auth_snapshot_format();
|
|
@@ -68634,7 +68840,22 @@ function parseModelCommand(text4) {
|
|
|
68634
68840
|
}
|
|
68635
68841
|
return { kind: "set", model: arg };
|
|
68636
68842
|
}
|
|
68637
|
-
|
|
68843
|
+
function isModelCommandBusy(ctx) {
|
|
68844
|
+
return ctx.currentTurnActive || ctx.turnInFlight;
|
|
68845
|
+
}
|
|
68846
|
+
function planModelCommand(parsed, ctx) {
|
|
68847
|
+
if (parsed.kind === "show" && ctx.menuEnabled)
|
|
68848
|
+
return { kind: "menu" };
|
|
68849
|
+
if (parsed.kind === "set" && isModelCommandBusy(ctx)) {
|
|
68850
|
+
return { kind: "queue", target: expandSrAlias(parsed.model) };
|
|
68851
|
+
}
|
|
68852
|
+
return { kind: "apply", parsed };
|
|
68853
|
+
}
|
|
68854
|
+
function modelCommandReceiptLine(agent, parsed, busy) {
|
|
68855
|
+
const arg = parsed.kind === "set" ? parsed.model : parsed.kind === "show" ? "(show)" : "(help)";
|
|
68856
|
+
return `telegram gateway: gw /model received agent=${agent} kind=${parsed.kind} arg=${arg} busy=${busy}`;
|
|
68857
|
+
}
|
|
68858
|
+
var PERSIST_NOTE = "_Session-only \u2014 this override lasts until the agent\u2019s next restart, then reverts to the configured `model:`. `/model default` clears it now. To change the default permanently, set `model:` in switchroom.yaml._";
|
|
68638
68859
|
function helpText2(deps, reason) {
|
|
68639
68860
|
const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `\`${a}\``).join(" \u00b7 ");
|
|
68640
68861
|
const lines = [];
|
|
@@ -69188,12 +69409,7 @@ function isValidModelArg2(arg) {
|
|
|
69188
69409
|
|
|
69189
69410
|
// gateway/session-model-file.ts
|
|
69190
69411
|
var SESSION_MODEL_FILE = ".session-model";
|
|
69191
|
-
var RELAUNCH_MODEL_INTENT_FILE = ".relaunch-model-intent";
|
|
69192
69412
|
var CONFIGURED_DEFAULT_MODEL_FILE = ".configured-default-model";
|
|
69193
|
-
var SESSION_MODEL_BOOT_ATTEMPTS_FILE = ".session-model-boot-attempts";
|
|
69194
|
-
function intentForRestartReason(_reason) {
|
|
69195
|
-
return "keep";
|
|
69196
|
-
}
|
|
69197
69413
|
function atomicWrite(path2, content3) {
|
|
69198
69414
|
const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
|
|
69199
69415
|
writeFileSync21(tmp, content3, "utf8");
|
|
@@ -69207,17 +69423,6 @@ function serializeSessionModel(rec) {
|
|
|
69207
69423
|
})}
|
|
69208
69424
|
`;
|
|
69209
69425
|
}
|
|
69210
|
-
function parseSessionModel(text4) {
|
|
69211
|
-
try {
|
|
69212
|
-
const raw = JSON.parse(text4);
|
|
69213
|
-
if (typeof raw.model !== "string" || typeof raw.configuredDefaultAtWrite !== "string" || typeof raw.ts !== "number" || !isValidModelArg2(raw.model)) {
|
|
69214
|
-
return null;
|
|
69215
|
-
}
|
|
69216
|
-
return { model: raw.model, configuredDefaultAtWrite: raw.configuredDefaultAtWrite, ts: raw.ts };
|
|
69217
|
-
} catch {
|
|
69218
|
-
return null;
|
|
69219
|
-
}
|
|
69220
|
-
}
|
|
69221
69426
|
function writeSessionModelFile(agentDir, model, configuredDefaultAtWrite) {
|
|
69222
69427
|
if (!isValidModelArg2(model)) {
|
|
69223
69428
|
throw new Error(`refusing to persist non-canonical session model token: ${JSON.stringify(model)}`);
|
|
@@ -69231,20 +69436,9 @@ function readSessionModelFileRaw(agentDir) {
|
|
|
69231
69436
|
return null;
|
|
69232
69437
|
}
|
|
69233
69438
|
}
|
|
69234
|
-
function readSessionModelFile(agentDir) {
|
|
69235
|
-
const raw = readSessionModelFileRaw(agentDir);
|
|
69236
|
-
return raw == null ? null : parseSessionModel(raw);
|
|
69237
|
-
}
|
|
69238
69439
|
function clearSessionModelFile(agentDir) {
|
|
69239
69440
|
try {
|
|
69240
69441
|
rmSync4(join29(agentDir, SESSION_MODEL_FILE), { force: true });
|
|
69241
|
-
rmSync4(join29(agentDir, ".session-model-kept-notified"), { force: true });
|
|
69242
|
-
rmSync4(join29(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
|
|
69243
|
-
} catch {}
|
|
69244
|
-
}
|
|
69245
|
-
function clearSessionModelBootAttempts(agentDir) {
|
|
69246
|
-
try {
|
|
69247
|
-
rmSync4(join29(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
|
|
69248
69442
|
} catch {}
|
|
69249
69443
|
}
|
|
69250
69444
|
function restoreSessionModelFileRaw(agentDir, raw) {
|
|
@@ -69256,40 +69450,6 @@ function restoreSessionModelFileRaw(agentDir, raw) {
|
|
|
69256
69450
|
atomicWrite(join29(agentDir, SESSION_MODEL_FILE), raw);
|
|
69257
69451
|
} catch {}
|
|
69258
69452
|
}
|
|
69259
|
-
function writeRelaunchModelIntent(agentDir, intent, reason) {
|
|
69260
|
-
try {
|
|
69261
|
-
atomicWrite(join29(agentDir, RELAUNCH_MODEL_INTENT_FILE), `${JSON.stringify({ intent, reason, ts: Date.now() })}
|
|
69262
|
-
`);
|
|
69263
|
-
} catch (err) {
|
|
69264
|
-
process.stderr.write(`telegram gateway: relaunch-model-intent write failed (boot will revert): ${err?.message ?? String(err)}
|
|
69265
|
-
`);
|
|
69266
|
-
}
|
|
69267
|
-
}
|
|
69268
|
-
var GATEWAY_SHUTDOWN_INTENT_REASON_PREFIX = "gateway-shutdown:";
|
|
69269
|
-
function readRelaunchModelIntent(agentDir) {
|
|
69270
|
-
try {
|
|
69271
|
-
const raw = readFileSync25(join29(agentDir, RELAUNCH_MODEL_INTENT_FILE), "utf8");
|
|
69272
|
-
const parsed = JSON.parse(raw);
|
|
69273
|
-
if (parsed.intent !== "keep" && parsed.intent !== "revert" || typeof parsed.reason !== "string" || typeof parsed.ts !== "number") {
|
|
69274
|
-
return null;
|
|
69275
|
-
}
|
|
69276
|
-
return { intent: parsed.intent, reason: parsed.reason, ts: parsed.ts };
|
|
69277
|
-
} catch {
|
|
69278
|
-
return null;
|
|
69279
|
-
}
|
|
69280
|
-
}
|
|
69281
|
-
function clearStaleGatewayShutdownIntent(agentDir) {
|
|
69282
|
-
const rec = readRelaunchModelIntent(agentDir);
|
|
69283
|
-
if (rec == null || !rec.reason.startsWith(GATEWAY_SHUTDOWN_INTENT_REASON_PREFIX))
|
|
69284
|
-
return false;
|
|
69285
|
-
clearRelaunchModelIntent(agentDir);
|
|
69286
|
-
return true;
|
|
69287
|
-
}
|
|
69288
|
-
function clearRelaunchModelIntent(agentDir) {
|
|
69289
|
-
try {
|
|
69290
|
-
rmSync4(join29(agentDir, RELAUNCH_MODEL_INTENT_FILE), { force: true });
|
|
69291
|
-
} catch {}
|
|
69292
|
-
}
|
|
69293
69453
|
function readConfiguredDefaultModel(agentDir) {
|
|
69294
69454
|
try {
|
|
69295
69455
|
const v = readFileSync25(join29(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
|
|
@@ -69300,17 +69460,6 @@ function readConfiguredDefaultModel(agentDir) {
|
|
|
69300
69460
|
}
|
|
69301
69461
|
var SESSION_EFFORT_FILE = ".session-effort";
|
|
69302
69462
|
var EFFORT_LEVEL_RE = /^(low|medium|high|xhigh|max)$/;
|
|
69303
|
-
function parseSessionEffort(text4) {
|
|
69304
|
-
try {
|
|
69305
|
-
const raw = JSON.parse(text4);
|
|
69306
|
-
if (typeof raw.level !== "string" || typeof raw.configuredDefaultAtWrite !== "string" || typeof raw.ts !== "number" || !EFFORT_LEVEL_RE.test(raw.level)) {
|
|
69307
|
-
return null;
|
|
69308
|
-
}
|
|
69309
|
-
return { level: raw.level, configuredDefaultAtWrite: raw.configuredDefaultAtWrite, ts: raw.ts };
|
|
69310
|
-
} catch {
|
|
69311
|
-
return null;
|
|
69312
|
-
}
|
|
69313
|
-
}
|
|
69314
69463
|
function writeSessionEffortFile(agentDir, level, configuredDefaultAtWrite) {
|
|
69315
69464
|
if (!EFFORT_LEVEL_RE.test(level)) {
|
|
69316
69465
|
throw new Error(`refusing to persist non-allowlisted effort level: ${JSON.stringify(level)}`);
|
|
@@ -69318,13 +69467,6 @@ function writeSessionEffortFile(agentDir, level, configuredDefaultAtWrite) {
|
|
|
69318
69467
|
atomicWrite(join29(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
|
|
69319
69468
|
`);
|
|
69320
69469
|
}
|
|
69321
|
-
function readSessionEffortFile(agentDir) {
|
|
69322
|
-
try {
|
|
69323
|
-
return parseSessionEffort(readFileSync25(join29(agentDir, SESSION_EFFORT_FILE), "utf8"));
|
|
69324
|
-
} catch {
|
|
69325
|
-
return null;
|
|
69326
|
-
}
|
|
69327
|
-
}
|
|
69328
69470
|
function clearSessionEffortFile(agentDir) {
|
|
69329
69471
|
try {
|
|
69330
69472
|
rmSync4(join29(agentDir, SESSION_EFFORT_FILE), { force: true });
|
|
@@ -69699,7 +69841,7 @@ function parseEffortCommand(text4) {
|
|
|
69699
69841
|
}
|
|
69700
69842
|
return { kind: "set", level: arg.toLowerCase() };
|
|
69701
69843
|
}
|
|
69702
|
-
var PERSIST_NOTE2 = "
|
|
69844
|
+
var PERSIST_NOTE2 = "_Session-only \u2014 this override lasts until the agent\u2019s next restart, then reverts to the configured `thinking_effort:`. `/effort default` clears it now. To change the default permanently, set `thinking_effort:` in switchroom.yaml._";
|
|
69703
69845
|
var LEVELS_INLINE = EFFORT_LEVELS.map((l) => `\`${l}\``).join(" \u00b7 ");
|
|
69704
69846
|
function helpText3(deps, reason) {
|
|
69705
69847
|
const lines = [];
|
|
@@ -80744,7 +80886,8 @@ function buildFleetRollMessage(roll, now) {
|
|
|
80744
80886
|
const pctPart = typeof roll.pct === "number" ? ` at ${fmtPct(roll.pct)}` : "";
|
|
80745
80887
|
const resetPart = typeof roll.exhausted_until === "number" && roll.exhausted_until > now ? ` (resets ${formatRelative(new Date(roll.exhausted_until), new Date(now))})` : "";
|
|
80746
80888
|
const softAvoid = roll.reason === "soft-avoid";
|
|
80747
|
-
const
|
|
80889
|
+
const modelTierWall = roll.reason === "model-tier-wall";
|
|
80890
|
+
const causeLine = softAvoid ? `Proactive switch \u2014 \`${codeSpanSafe(roll.from)}\` is approaching its limits (${winLabel}${pctPart})${resetPart}, so the fleet moved early instead of hitting the wall.` : modelTierWall ? `Flagship (premium) tier weekly limit reached on \`${codeSpanSafe(roll.from)}\`${resetPart} \u2014 opus/haiku on that account are unaffected.` : `${winLabel}${pctPart} on \`${codeSpanSafe(roll.from)}\`${resetPart}.`;
|
|
80748
80891
|
return [
|
|
80749
80892
|
`\uD83D\uDD01 **Switched fleet to \`${codeSpanSafe(roll.to)}\`**`,
|
|
80750
80893
|
``,
|
|
@@ -81081,10 +81224,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
81081
81224
|
}
|
|
81082
81225
|
|
|
81083
81226
|
// ../src/build-info.ts
|
|
81084
|
-
var VERSION = "0.18.
|
|
81085
|
-
var COMMIT_SHA = "
|
|
81086
|
-
var COMMIT_DATE = "2026-07-
|
|
81087
|
-
var LATEST_PR =
|
|
81227
|
+
var VERSION = "0.18.17";
|
|
81228
|
+
var COMMIT_SHA = "372a4968";
|
|
81229
|
+
var COMMIT_DATE = "2026-07-12T09:18:05Z";
|
|
81230
|
+
var LATEST_PR = 3190;
|
|
81088
81231
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
81089
81232
|
|
|
81090
81233
|
// gateway/boot-version.ts
|
|
@@ -82969,11 +83112,6 @@ function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
|
|
|
82969
83112
|
`);
|
|
82970
83113
|
return false;
|
|
82971
83114
|
}
|
|
82972
|
-
{
|
|
82973
|
-
const smDir = resolveAgentDirFromEnv();
|
|
82974
|
-
if (smDir)
|
|
82975
|
-
writeRelaunchModelIntent(smDir, intentForRestartReason(reason), reason);
|
|
82976
|
-
}
|
|
82977
83115
|
process.stderr.write(`telegram gateway: restart-via-SIGTERM-PID1 agent=${targetAgent} reason=${reason} (docker)
|
|
82978
83116
|
`);
|
|
82979
83117
|
setTimeout(() => {
|
|
@@ -82986,11 +83124,6 @@ function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
|
|
|
82986
83124
|
}, delayMs).unref();
|
|
82987
83125
|
return true;
|
|
82988
83126
|
}
|
|
82989
|
-
if (targetAgent === selfAgent) {
|
|
82990
|
-
const smDir = resolveAgentDirFromEnv();
|
|
82991
|
-
if (smDir)
|
|
82992
|
-
writeRelaunchModelIntent(smDir, intentForRestartReason(reason), reason);
|
|
82993
|
-
}
|
|
82994
83127
|
process.stderr.write(`telegram gateway: restart-via-systemctl agent=${targetAgent} reason=${reason}
|
|
82995
83128
|
`);
|
|
82996
83129
|
try {
|
|
@@ -83005,13 +83138,6 @@ function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
|
|
|
83005
83138
|
return false;
|
|
83006
83139
|
}
|
|
83007
83140
|
}
|
|
83008
|
-
{
|
|
83009
|
-
const bootSmDir = resolveAgentDirFromEnv();
|
|
83010
|
-
if (bootSmDir != null && clearStaleGatewayShutdownIntent(bootSmDir)) {
|
|
83011
|
-
process.stderr.write(`telegram gateway: cleared stale gateway-shutdown relaunch-model intent (previous bounce was gateway-only \u2014 container never restarted)
|
|
83012
|
-
`);
|
|
83013
|
-
}
|
|
83014
|
-
}
|
|
83015
83141
|
var cachedClaudeCliVersion = undefined;
|
|
83016
83142
|
function getClaudeCliVersion() {
|
|
83017
83143
|
if (cachedClaudeCliVersion !== undefined)
|
|
@@ -83215,6 +83341,7 @@ function readAccessFile() {
|
|
|
83215
83341
|
parseMode: parsed.parseMode,
|
|
83216
83342
|
disableLinkPreview: parsed.disableLinkPreview,
|
|
83217
83343
|
coalescingGapMs: parsed.coalescingGapMs,
|
|
83344
|
+
litellmNoticeWindowMs: parsed.litellmNoticeWindowMs,
|
|
83218
83345
|
coalesceMaxAttachments: parsed.coalesceMaxAttachments,
|
|
83219
83346
|
interruptSafeBoundary: parsed.interruptSafeBoundary,
|
|
83220
83347
|
interruptMaxWaitMs: parsed.interruptMaxWaitMs,
|
|
@@ -85771,6 +85898,7 @@ function emitGatewayOperatorEvent(event) {
|
|
|
85771
85898
|
const { agent, kind } = event;
|
|
85772
85899
|
let throttleEscalation = null;
|
|
85773
85900
|
let escalationFired = false;
|
|
85901
|
+
let rateLimitedCooldownConsulted = false;
|
|
85774
85902
|
const rateLimit429Classification = kind === "rate-limited" ? classify429Detail(event.detail) : null;
|
|
85775
85903
|
if (rateLimit429Classification != null && rateLimit429Classification !== "account-scoped") {
|
|
85776
85904
|
emitRuntimeMetric(build429ClassifiedMetric({
|
|
@@ -85780,10 +85908,28 @@ function emitGatewayOperatorEvent(event) {
|
|
|
85780
85908
|
action: "calm",
|
|
85781
85909
|
now: Date.now()
|
|
85782
85910
|
}));
|
|
85783
|
-
|
|
85911
|
+
const surface = decideRateLimitedSurface({
|
|
85912
|
+
classification: rateLimit429Classification,
|
|
85913
|
+
agent,
|
|
85914
|
+
shouldEmitCard: (a) => shouldEmitOperatorEvent(a, "rate-limited")
|
|
85915
|
+
});
|
|
85916
|
+
if (surface === "litellm-local-notice") {
|
|
85784
85917
|
process.stderr.write(`telegram gateway: 429 classified litellm-proxy-local agent=${agent} \u2014 ` + `calm path, no account attribution, no failover
|
|
85785
85918
|
`);
|
|
85919
|
+
const outcome = litellmLocalNoticeRunner.onRateLimited("litellm-local", agent);
|
|
85920
|
+
if (outcome === "sent") {
|
|
85921
|
+
try {
|
|
85922
|
+
recordOperatorEvent(event);
|
|
85923
|
+
} catch {}
|
|
85924
|
+
}
|
|
85925
|
+
return;
|
|
85926
|
+
}
|
|
85927
|
+
if (surface === "cooldown-suppressed") {
|
|
85928
|
+
process.stderr.write(`telegram gateway: operator-event suppressed (cooldown) agent=${agent} kind=${kind}
|
|
85929
|
+
`);
|
|
85930
|
+
return;
|
|
85786
85931
|
}
|
|
85932
|
+
rateLimitedCooldownConsulted = true;
|
|
85787
85933
|
}
|
|
85788
85934
|
if (rateLimit429Classification === "account-scoped") {
|
|
85789
85935
|
const throttleDecision = decideThrottleTier({
|
|
@@ -85816,7 +85962,7 @@ function emitGatewayOperatorEvent(event) {
|
|
|
85816
85962
|
}
|
|
85817
85963
|
}
|
|
85818
85964
|
}
|
|
85819
|
-
if (!shouldEmitOperatorEvent(agent, kind)) {
|
|
85965
|
+
if (!rateLimitedCooldownConsulted && !shouldEmitOperatorEvent(agent, kind)) {
|
|
85820
85966
|
process.stderr.write(`telegram gateway: operator-event suppressed (cooldown) agent=${agent} kind=${kind}
|
|
85821
85967
|
`);
|
|
85822
85968
|
return;
|
|
@@ -86914,11 +87060,6 @@ var ipcServer = createIpcServer({
|
|
|
86914
87060
|
}
|
|
86915
87061
|
const bridgeUpEffects = client3.agentName != null ? shadowEmit({ kind: "bridgeUp", at: Date.now() }) : [];
|
|
86916
87062
|
bridgeDeadWatchdog.noteBridgeRegistered(client3.agentName);
|
|
86917
|
-
if (client3.agentName != null) {
|
|
86918
|
-
const smBootDir = resolveAgentDirFromEnv();
|
|
86919
|
-
if (smBootDir != null)
|
|
86920
|
-
clearSessionModelBootAttempts(smBootDir);
|
|
86921
|
-
}
|
|
86922
87063
|
client3.send({ type: "status", status: "agent_connected" });
|
|
86923
87064
|
if (client3.agentName != null) {
|
|
86924
87065
|
if (isDispatchEnabled()) {
|
|
@@ -92987,11 +93128,6 @@ function buildModelDeps(restartCtx) {
|
|
|
92987
93128
|
});
|
|
92988
93129
|
}
|
|
92989
93130
|
stampUserRestartReason(reason);
|
|
92990
|
-
{
|
|
92991
|
-
const smDir = resolveAgentDirFromEnv();
|
|
92992
|
-
if (smDir)
|
|
92993
|
-
writeRelaunchModelIntent(smDir, "keep", reason);
|
|
92994
|
-
}
|
|
92995
93131
|
await sweepBeforeSelfRestart();
|
|
92996
93132
|
const hostdResp = await tryHostdDispatch2(name, {
|
|
92997
93133
|
v: 1,
|
|
@@ -93006,11 +93142,6 @@ function buildModelDeps(restartCtx) {
|
|
|
93006
93142
|
}
|
|
93007
93143
|
if (hostdResp.result !== "started" && hostdResp.result !== "completed") {
|
|
93008
93144
|
clearRestartMarker();
|
|
93009
|
-
{
|
|
93010
|
-
const smDir = resolveAgentDirFromEnv();
|
|
93011
|
-
if (smDir)
|
|
93012
|
-
clearRelaunchModelIntent(smDir);
|
|
93013
|
-
}
|
|
93014
93145
|
throw new Error(`hostd restart failed (result=${hostdResp.result}): ${hostdResp.error ?? "(no details)"}`);
|
|
93015
93146
|
}
|
|
93016
93147
|
},
|
|
@@ -93028,7 +93159,6 @@ function buildModelDeps(restartCtx) {
|
|
|
93028
93159
|
if (err?.code !== "restart_in_flight") {
|
|
93029
93160
|
restoreSessionModelFileRaw(agentDir, prevFileRaw);
|
|
93030
93161
|
sessionModelSource.setOverride(prevOverride);
|
|
93031
|
-
clearRelaunchModelIntent(agentDir);
|
|
93032
93162
|
}
|
|
93033
93163
|
throw err;
|
|
93034
93164
|
}
|
|
@@ -93047,12 +93177,12 @@ function modelMenuReplyMarkup(reply) {
|
|
|
93047
93177
|
}
|
|
93048
93178
|
return kb;
|
|
93049
93179
|
}
|
|
93050
|
-
function recordTypedModelSwitch(reply, requestedModelArg,
|
|
93180
|
+
function recordTypedModelSwitch(reply, requestedModelArg, _deps) {
|
|
93051
93181
|
const requested = requestedModelArg != null ? expandSrAlias(requestedModelArg) : null;
|
|
93052
93182
|
if (requested?.toLowerCase() === "default") {
|
|
93053
|
-
const
|
|
93054
|
-
if (
|
|
93055
|
-
clearSessionModelFile(
|
|
93183
|
+
const smDir = resolveAgentDirFromEnv();
|
|
93184
|
+
if (smDir)
|
|
93185
|
+
clearSessionModelFile(smDir);
|
|
93056
93186
|
if (reply.selectedModel)
|
|
93057
93187
|
sessionModelSource.setOverride(null);
|
|
93058
93188
|
return "";
|
|
@@ -93060,33 +93190,11 @@ function recordTypedModelSwitch(reply, requestedModelArg, deps) {
|
|
|
93060
93190
|
if (!reply.selectedModel)
|
|
93061
93191
|
return "";
|
|
93062
93192
|
sessionModelSource.setOverride(reply.selectedModel);
|
|
93063
|
-
const smDir = resolveAgentDirFromEnv();
|
|
93064
|
-
if (smDir && requested && isValidModelArg(requested) && !isSrModel(requested)) {
|
|
93065
|
-
try {
|
|
93066
|
-
writeSessionModelFile(smDir, requested, readConfiguredDefaultModel(smDir) ?? resolveMainModel(deps.getConfiguredModel() ?? undefined));
|
|
93067
|
-
} catch (err) {
|
|
93068
|
-
process.stderr.write(`telegram gateway: session-model persist failed (typed /model): ${err?.message ?? String(err)}
|
|
93069
|
-
`);
|
|
93070
|
-
return `
|
|
93071
|
-
\u26A0\uFE0F Couldn\u2019t persist the sticky override \u2014 the switch is live now but won\u2019t survive a relaunch.`;
|
|
93072
|
-
}
|
|
93073
|
-
}
|
|
93074
93193
|
return "";
|
|
93075
93194
|
}
|
|
93076
93195
|
function recordModelMenuSideEffects(outcome, modelDeps, cbChatId, cbThreadId, prevSessionModel) {
|
|
93077
93196
|
if (outcome.selectedModel) {
|
|
93078
93197
|
sessionModelSource.setOverride(outcome.selectedModel);
|
|
93079
|
-
const smDir = resolveAgentDirFromEnv();
|
|
93080
|
-
if (smDir && outcome.selectedModelToken) {
|
|
93081
|
-
try {
|
|
93082
|
-
writeSessionModelFile(smDir, outcome.selectedModelToken, readConfiguredDefaultModel(smDir) ?? resolveMainModel(modelDeps.getConfiguredModel() ?? undefined));
|
|
93083
|
-
} catch (err) {
|
|
93084
|
-
outcome.reply.text += `
|
|
93085
|
-
\u26A0\uFE0F Couldn\u2019t persist the sticky override \u2014 the switch is live now but won\u2019t survive a relaunch.`;
|
|
93086
|
-
process.stderr.write(`telegram gateway: session-model persist failed (menu): ${err?.message ?? String(err)}
|
|
93087
|
-
`);
|
|
93088
|
-
}
|
|
93089
|
-
}
|
|
93090
93198
|
}
|
|
93091
93199
|
if (outcome.clearedDefault) {
|
|
93092
93200
|
const smDir = resolveAgentDirFromEnv();
|
|
@@ -93183,7 +93291,6 @@ function persistQueuedCommandForRestart(action) {
|
|
|
93183
93291
|
}
|
|
93184
93292
|
const configured = readConfiguredDefaultModel(agentDir) ?? resolveMainModel(undefined);
|
|
93185
93293
|
writeSessionModelFile(agentDir, expandSrAlias(action.arg), configured);
|
|
93186
|
-
writeRelaunchModelIntent(agentDir, "keep", "queued /model carried across restart");
|
|
93187
93294
|
break;
|
|
93188
93295
|
}
|
|
93189
93296
|
case "clear-model":
|
|
@@ -93240,14 +93347,38 @@ bot.command("model", async (ctx) => {
|
|
|
93240
93347
|
const parsed = parseModelCommand(text5) ?? { kind: "show" };
|
|
93241
93348
|
const chatId = String(ctx.chat.id);
|
|
93242
93349
|
const threadId = resolveThreadId(chatId, ctx.message?.message_thread_id);
|
|
93350
|
+
const busyNow = currentTurn !== null || turnInFlightForGate();
|
|
93351
|
+
process.stderr.write(modelCommandReceiptLine(getMyAgentName(), parsed, busyNow) + `
|
|
93352
|
+
`);
|
|
93353
|
+
if (HISTORY_ENABLED && ctx.message?.message_id != null) {
|
|
93354
|
+
try {
|
|
93355
|
+
recordInbound({
|
|
93356
|
+
chat_id: chatId,
|
|
93357
|
+
thread_id: threadId ?? null,
|
|
93358
|
+
message_id: ctx.message.message_id,
|
|
93359
|
+
user: ctx.from?.username ?? (ctx.from?.id != null ? String(ctx.from.id) : null),
|
|
93360
|
+
user_id: ctx.from?.id != null ? String(ctx.from.id) : null,
|
|
93361
|
+
ts: ctx.message.date ?? Math.floor(Date.now() / 1000),
|
|
93362
|
+
text: text5
|
|
93363
|
+
});
|
|
93364
|
+
} catch (err) {
|
|
93365
|
+
process.stderr.write(`telegram gateway: /model recordInbound failed: ${err?.message ?? String(err)}
|
|
93366
|
+
`);
|
|
93367
|
+
}
|
|
93368
|
+
}
|
|
93243
93369
|
const deps = buildModelDeps({ chatId, threadId });
|
|
93244
|
-
|
|
93370
|
+
const disposition = planModelCommand(parsed, {
|
|
93371
|
+
currentTurnActive: currentTurn !== null,
|
|
93372
|
+
turnInFlight: turnInFlightForGate(),
|
|
93373
|
+
menuEnabled: process.env.SWITCHROOM_MODEL_MENU !== "0"
|
|
93374
|
+
});
|
|
93375
|
+
if (disposition.kind === "menu") {
|
|
93245
93376
|
const menu = await buildModelMenu(deps);
|
|
93246
93377
|
await switchroomReply(ctx, menu.text, { html: true, reply_markup: modelMenuReplyMarkup(menu) });
|
|
93247
93378
|
return;
|
|
93248
93379
|
}
|
|
93249
|
-
if (
|
|
93250
|
-
const target =
|
|
93380
|
+
if (disposition.kind === "queue") {
|
|
93381
|
+
const target = disposition.target;
|
|
93251
93382
|
const sent = await ctx.replyWithRichMessage(richMessage2(hardenCardBreaks2(ackText("model", target, escapeHtmlForTg2))), threadId != null ? { message_thread_id: threadId } : {});
|
|
93252
93383
|
enqueueSessionCommand({
|
|
93253
93384
|
kind: "model",
|
|
@@ -93266,34 +93397,24 @@ bot.command("model", async (ctx) => {
|
|
|
93266
93397
|
const persistWarning = recordTypedModelSwitch(reply, parsed.kind === "set" ? parsed.model : null, deps);
|
|
93267
93398
|
await switchroomReply(ctx, reply.text + persistWarning, { html: reply.html });
|
|
93268
93399
|
});
|
|
93400
|
+
var sessionEffortOverride = null;
|
|
93269
93401
|
function buildEffortDeps() {
|
|
93270
93402
|
return {
|
|
93271
93403
|
applyEffort: async (agent, level) => {
|
|
93272
93404
|
const result = await applyEffort(agent, level);
|
|
93273
|
-
if (result.ok)
|
|
93274
|
-
|
|
93275
|
-
if (agentDir) {
|
|
93276
|
-
try {
|
|
93277
|
-
writeSessionEffortFile(agentDir, level, getConfiguredEffortForPersist());
|
|
93278
|
-
} catch (err) {
|
|
93279
|
-
process.stderr.write(`telegram gateway: session-effort persist failed level=${level}: ${err?.message ?? String(err)}
|
|
93280
|
-
`);
|
|
93281
|
-
}
|
|
93282
|
-
}
|
|
93283
|
-
}
|
|
93405
|
+
if (result.ok)
|
|
93406
|
+
sessionEffortOverride = level;
|
|
93284
93407
|
return result;
|
|
93285
93408
|
},
|
|
93286
93409
|
getAgentName: getMyAgentName,
|
|
93287
93410
|
getConfiguredEffort: () => getConfiguredEffortForPersist(),
|
|
93288
93411
|
clearSessionEffort: () => {
|
|
93412
|
+
sessionEffortOverride = null;
|
|
93289
93413
|
const agentDir = resolveAgentDirFromEnv();
|
|
93290
93414
|
if (agentDir)
|
|
93291
93415
|
clearSessionEffortFile(agentDir);
|
|
93292
93416
|
},
|
|
93293
|
-
getSessionEffort: () =>
|
|
93294
|
-
const agentDir = resolveAgentDirFromEnv();
|
|
93295
|
-
return agentDir ? readSessionEffortFile(agentDir)?.level ?? null : null;
|
|
93296
|
-
},
|
|
93417
|
+
getSessionEffort: () => sessionEffortOverride,
|
|
93297
93418
|
escapeHtml: escapeHtmlForTg2
|
|
93298
93419
|
};
|
|
93299
93420
|
}
|
|
@@ -93415,11 +93536,6 @@ bot.command("restart", async (ctx) => {
|
|
|
93415
93536
|
} catch {}
|
|
93416
93537
|
writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
|
|
93417
93538
|
stampUserRestartReason("user: /restart from chat");
|
|
93418
|
-
{
|
|
93419
|
-
const smDir = resolveAgentDirFromEnv();
|
|
93420
|
-
if (smDir)
|
|
93421
|
-
writeRelaunchModelIntent(smDir, "keep", "user: /restart from chat");
|
|
93422
|
-
}
|
|
93423
93539
|
await sweepBeforeSelfRestart();
|
|
93424
93540
|
const hostdResp = await tryHostdDispatch2(getMyAgentName(), {
|
|
93425
93541
|
v: 1,
|
|
@@ -93516,9 +93632,6 @@ async function handleNewCommand(ctx) {
|
|
|
93516
93632
|
}
|
|
93517
93633
|
}
|
|
93518
93634
|
stampUserRestartReason(`user: /${kind} from chat`);
|
|
93519
|
-
if (agentDir != null) {
|
|
93520
|
-
writeRelaunchModelIntent(agentDir, "keep", `user: /${kind} from chat`);
|
|
93521
|
-
}
|
|
93522
93635
|
await sweepBeforeSelfRestart();
|
|
93523
93636
|
const hostdResp = await tryHostdDispatch2(getMyAgentName(), {
|
|
93524
93637
|
v: 1,
|
|
@@ -93940,6 +94053,26 @@ var throttleTierRunner = createThrottleTierRunner({
|
|
|
93940
94053
|
log: (m) => process.stderr.write(`telegram gateway: ${m}
|
|
93941
94054
|
`)
|
|
93942
94055
|
});
|
|
94056
|
+
var litellmLocalNoticeRunner = createLitellmLocalNoticeRunner({
|
|
94057
|
+
listNoticeChats: () => loadAccess().allowFrom,
|
|
94058
|
+
sendNotice: (chat_id, markdown) => {
|
|
94059
|
+
const noticeTopic = resolveAgentOutboundTopic({ kind: "compact-watchdog" });
|
|
94060
|
+
const noticeSupergroup = resolveAgentSupergroupChatId();
|
|
94061
|
+
const noticeThread = topicForRecipient({
|
|
94062
|
+
recipientChatId: chat_id,
|
|
94063
|
+
resolvedTopic: noticeTopic,
|
|
94064
|
+
supergroupChatId: noticeSupergroup
|
|
94065
|
+
});
|
|
94066
|
+
swallowingApiCall(() => bot.api.sendRichMessage(chat_id, richMessage2(markdown), {
|
|
94067
|
+
disable_notification: true,
|
|
94068
|
+
...noticeThread != null ? { message_thread_id: noticeThread } : {}
|
|
94069
|
+
}), { chat_id: String(chat_id), verb: "litellm-local-notice:notify" });
|
|
94070
|
+
},
|
|
94071
|
+
windowMs: () => parseLitellmNoticeWindowMs(loadAccess().litellmNoticeWindowMs),
|
|
94072
|
+
emitMetric: (event) => emitRuntimeMetric(event),
|
|
94073
|
+
log: (m) => process.stderr.write(`telegram gateway: ${m}
|
|
94074
|
+
`)
|
|
94075
|
+
});
|
|
93943
94076
|
var fallbackFailureNoticeState = { lastSentAtMs: 0 };
|
|
93944
94077
|
var fallbackAllBlockedNoticeState = { lastSentAtMs: 0 };
|
|
93945
94078
|
function broadcastFleetFallbackFailure(triggerAgent, reason) {
|
|
@@ -96639,21 +96772,6 @@ async function shutdown(signal) {
|
|
|
96639
96772
|
`);
|
|
96640
96773
|
} catch (err) {
|
|
96641
96774
|
process.stderr.write(`telegram gateway: shutdown.clean_marker_write_failed err=${err.message}
|
|
96642
|
-
`);
|
|
96643
|
-
}
|
|
96644
|
-
try {
|
|
96645
|
-
const smDir = resolveAgentDirFromEnv();
|
|
96646
|
-
if (smDir != null) {
|
|
96647
|
-
const hasOverride = readSessionModelFile(smDir) != null;
|
|
96648
|
-
const intentAlreadyStamped = existsSync50(join54(smDir, RELAUNCH_MODEL_INTENT_FILE));
|
|
96649
|
-
if (hasOverride && !intentAlreadyStamped) {
|
|
96650
|
-
writeRelaunchModelIntent(smDir, "keep", `${GATEWAY_SHUTDOWN_INTENT_REASON_PREFIX} graceful ${signal} shutdown (deploy/rolling restart) \u2014 preserving user-chosen session model`);
|
|
96651
|
-
process.stderr.write(`telegram gateway: shutdown.session_model_keep_stamped signal=${signal}
|
|
96652
|
-
`);
|
|
96653
|
-
}
|
|
96654
|
-
}
|
|
96655
|
-
} catch (err) {
|
|
96656
|
-
process.stderr.write(`telegram gateway: shutdown.session_model_keep_stamp_failed err=${err.message}
|
|
96657
96775
|
`);
|
|
96658
96776
|
}
|
|
96659
96777
|
} else {
|
|
@@ -97109,6 +97227,14 @@ var didOneTimeSetup = false;
|
|
|
97109
97227
|
sessionModelSource.setOverride(launched.length > 0 && launched !== configured ? launched : null);
|
|
97110
97228
|
} catch {}
|
|
97111
97229
|
}
|
|
97230
|
+
const activeEffortPath = join54(smAgentDir, ".active-session-effort");
|
|
97231
|
+
if (existsSync50(activeEffortPath)) {
|
|
97232
|
+
try {
|
|
97233
|
+
const launchedEffort = readFileSync54(activeEffortPath, "utf8").trim();
|
|
97234
|
+
const configuredEffort = getConfiguredEffortForPersist();
|
|
97235
|
+
sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
|
|
97236
|
+
} catch {}
|
|
97237
|
+
}
|
|
97112
97238
|
const alertPath = join54(smAgentDir, ".session-model-alert");
|
|
97113
97239
|
if (existsSync50(alertPath)) {
|
|
97114
97240
|
let alertText = null;
|
|
@@ -97227,6 +97353,7 @@ var didOneTimeSetup = false;
|
|
|
97227
97353
|
editPayload: text5
|
|
97228
97354
|
})
|
|
97229
97355
|
},
|
|
97356
|
+
floodWaitRemainingMs: probeFloodWaitRemainingMs,
|
|
97230
97357
|
log: (msg) => process.stderr.write(`telegram gateway: ${msg}
|
|
97231
97358
|
`)
|
|
97232
97359
|
});
|