@wrongstack/telegram 0.292.1 → 0.295.0

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { expectDefined as expectDefined2 } from "@wrongstack/core";
2
+ import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
3
3
 
4
4
  // src/api-client.ts
5
5
  var TelegramApiClientError = class extends Error {
@@ -163,30 +163,34 @@ var TelegramApiClient = class {
163
163
  });
164
164
  }
165
165
  sendMessage(chatId, text, opts) {
166
+ const body = {
167
+ chat_id: String(chatId),
168
+ text,
169
+ disable_web_page_preview: true
170
+ };
171
+ if (opts?.parseMode) body.parse_mode = opts.parseMode;
166
172
  return this.request("sendMessage", {
167
- body: {
168
- chat_id: String(chatId),
169
- text,
170
- disable_web_page_preview: true
171
- },
173
+ body,
172
174
  signal: composedSignal(opts?.signal)
173
175
  });
174
176
  }
175
177
  sendMessageWithKeyboard(chatId, text, buttons, opts) {
178
+ const body = {
179
+ chat_id: String(chatId),
180
+ text,
181
+ disable_web_page_preview: true,
182
+ reply_markup: {
183
+ inline_keyboard: [
184
+ buttons.map((button) => ({
185
+ text: button.text,
186
+ callback_data: button.callback_data
187
+ }))
188
+ ]
189
+ }
190
+ };
191
+ if (opts?.parseMode) body.parse_mode = opts.parseMode;
176
192
  return this.request("sendMessage", {
177
- body: {
178
- chat_id: String(chatId),
179
- text,
180
- disable_web_page_preview: true,
181
- reply_markup: {
182
- inline_keyboard: [
183
- buttons.map((button) => ({
184
- text: button.text,
185
- callback_data: button.callback_data
186
- }))
187
- ]
188
- }
189
- },
193
+ body,
190
194
  signal: composedSignal(opts?.signal)
191
195
  });
192
196
  }
@@ -286,6 +290,7 @@ var TelegramBot = class _TelegramBot {
286
290
  /** Single-poller election across wstack instances sharing this token. */
287
291
  lock;
288
292
  standbyRetryMs;
293
+ getParseMode;
289
294
  standbyTimer = null;
290
295
  standbyAnnounced = false;
291
296
  // Circular buffer for incoming messages
@@ -306,6 +311,7 @@ var TelegramBot = class _TelegramBot {
306
311
  this.offsetStore = opts.offsetStore;
307
312
  this.lock = opts.lock;
308
313
  this.standbyRetryMs = opts.standbyRetryMs ?? 15e3;
314
+ this.getParseMode = opts.getParseMode;
309
315
  if (this.lock) {
310
316
  this.lock.onLost = () => this.handleLockLost();
311
317
  }
@@ -432,7 +438,8 @@ var TelegramBot = class _TelegramBot {
432
438
  try {
433
439
  const timeout = AbortSignal.timeout(1e4);
434
440
  const result = await this.api.sendMessage(chatId, text, {
435
- signal: signal ? AbortSignal.any([signal, timeout]) : timeout
441
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
442
+ parseMode: this.getParseMode?.()
436
443
  });
437
444
  return { ok: true, result };
438
445
  } catch (err) {
@@ -468,7 +475,8 @@ var TelegramBot = class _TelegramBot {
468
475
  try {
469
476
  const timeout = AbortSignal.timeout(1e4);
470
477
  const result = await this.api.sendMessageWithKeyboard(chatId, text, buttons, {
471
- signal: signal ? AbortSignal.any([signal, timeout]) : timeout
478
+ signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
479
+ parseMode: this.getParseMode?.()
472
480
  });
473
481
  return { ok: true, result };
474
482
  } catch (err) {
@@ -802,7 +810,9 @@ function truncateForTelegram(text, maxLen = 4e3) {
802
810
  }
803
811
 
804
812
  // src/config.ts
813
+ import { resolvePluginConfig } from "@wrongstack/core/plugin";
805
814
  var PLUGIN_NAME = "telegram";
815
+ var PLUGIN_CONFIG_ALIASES = ["@wrongstack/telegram"];
806
816
  var INBOUND_MODES = ["disabled", "paired", "allowlist", "public"];
807
817
  var DEFAULT_CONFIG = {
808
818
  inboundMode: "disabled",
@@ -816,7 +826,32 @@ var DEFAULT_CONFIG = {
816
826
  maxMessageLength: 4e3,
817
827
  singleInstanceLock: true,
818
828
  outboundQueuePerChat: 32,
819
- outboundQueueConcurrency: 4
829
+ outboundQueueConcurrency: 4,
830
+ allowGroupApprovals: false,
831
+ rateLimitTokensPerSecond: 0.33,
832
+ rateLimitBurst: 4,
833
+ parseMode: ""
834
+ };
835
+ var TELEGRAM_CONFIG_FIELDS = {
836
+ botToken: { lifecycle: "restart", secret: true },
837
+ notifyChatId: { lifecycle: "restart" },
838
+ inboundMode: { lifecycle: "hot" },
839
+ allowedUsers: { lifecycle: "hot" },
840
+ allowedChats: { lifecycle: "hot" },
841
+ allowedOutboundChats: { lifecycle: "hot" },
842
+ allowGroupApprovals: { lifecycle: "hot" },
843
+ pollIntervalSec: { lifecycle: "hot" },
844
+ notifyOnSessionEnd: { lifecycle: "hot" },
845
+ longToolThresholdMs: { lifecycle: "hot" },
846
+ notifyOnDelegate: { lifecycle: "hot" },
847
+ maxMessageLength: { lifecycle: "hot" },
848
+ offsetStoragePath: { lifecycle: "immutable" },
849
+ singleInstanceLock: { lifecycle: "restart" },
850
+ outboundQueuePerChat: { lifecycle: "restart" },
851
+ outboundQueueConcurrency: { lifecycle: "restart" },
852
+ rateLimitTokensPerSecond: { lifecycle: "hot", description: "Per-chat rate limit (tokens/sec)" },
853
+ rateLimitBurst: { lifecycle: "hot", description: "Per-chat rate limit burst size" },
854
+ parseMode: { lifecycle: "hot", description: "Telegram parse mode: HTML, MarkdownV2, or empty for plain text" }
820
855
  };
821
856
  var telegramConfigSchema = {
822
857
  type: "object",
@@ -857,6 +892,7 @@ var telegramConfigSchema = {
857
892
  longToolThresholdMs: { type: "integer", minimum: 0 },
858
893
  notifyOnDelegate: { type: "boolean" },
859
894
  maxMessageLength: { type: "integer", minimum: 100, maximum: 4096 },
895
+ offsetStoragePath: { type: "string" },
860
896
  singleInstanceLock: {
861
897
  type: "boolean",
862
898
  description: "Elect a single getUpdates poller per bot token across wstack instances (default true)"
@@ -872,24 +908,37 @@ var telegramConfigSchema = {
872
908
  minimum: 1,
873
909
  maximum: 64,
874
910
  description: "Maximum concurrent outbound sends across all chats (default 4)"
911
+ },
912
+ allowGroupApprovals: { type: "boolean" },
913
+ rateLimitTokensPerSecond: {
914
+ type: "number",
915
+ minimum: 0.1,
916
+ maximum: 100,
917
+ description: "Per-chat rate limit in tokens per second (default: 0.33 \u224820 msg/min)"
918
+ },
919
+ rateLimitBurst: {
920
+ type: "integer",
921
+ minimum: 1,
922
+ maximum: 100,
923
+ description: "Per-chat burst size (default: 1)"
924
+ },
925
+ parseMode: {
926
+ type: "string",
927
+ enum: ["", "HTML", "MarkdownV2"],
928
+ description: "Telegram parse mode: HTML, MarkdownV2, or empty for plain text"
875
929
  }
876
930
  },
877
931
  required: ["botToken"]
878
932
  };
879
933
  function readTelegramConfig(api) {
880
- const config = api.config;
881
- const extensions = config.extensions;
882
- const pluginEntries = config.plugins;
883
- const legacyPlugins = pluginEntries;
884
- const legacyOpts = legacyPlugins && !Array.isArray(legacyPlugins) ? legacyPlugins[PLUGIN_NAME] : void 0;
885
- const entryOpts = pluginOptionsFromEntries(pluginEntries);
886
- const extensionOpts = extensions?.[PLUGIN_NAME];
887
- const opts = {
888
- ...legacyOpts ?? entryOpts,
889
- ...extensionOpts ?? {}
890
- };
934
+ const resolution = resolvePluginConfig({
935
+ name: PLUGIN_NAME,
936
+ aliases: PLUGIN_CONFIG_ALIASES,
937
+ config: api.config
938
+ });
939
+ const opts = resolution.options;
891
940
  const inboundMode = resolveInboundMode(opts, {
892
- configured: legacyOpts !== void 0 || entryOpts !== void 0 || extensionOpts !== void 0,
941
+ configured: resolution.configured,
893
942
  warn: api.log?.warn.bind(api.log)
894
943
  });
895
944
  return {
@@ -898,6 +947,9 @@ function readTelegramConfig(api) {
898
947
  inboundMode
899
948
  };
900
949
  }
950
+ function readTelegramConfigFromConfig(cfg) {
951
+ return readTelegramConfig({ config: cfg });
952
+ }
901
953
  function resolveInboundMode(opts, migration) {
902
954
  if (opts.inboundMode !== void 0) {
903
955
  if (!INBOUND_MODES.includes(opts.inboundMode)) {
@@ -927,13 +979,6 @@ function resolveInboundMode(opts, migration) {
927
979
  function hasEntries(values) {
928
980
  return Array.isArray(values) && values.length > 0;
929
981
  }
930
- function pluginOptionsFromEntries(entries) {
931
- if (!Array.isArray(entries)) return void 0;
932
- const found = entries.find(
933
- (entry) => typeof entry === "object" && entry !== null && "name" in entry && (entry.name === "@wrongstack/telegram" || entry.name === PLUGIN_NAME)
934
- );
935
- return found?.options && typeof found.options === "object" ? found.options : void 0;
936
- }
937
982
 
938
983
  // src/redact.ts
939
984
  var SENSITIVE_FLAG_PATTERNS = [
@@ -1246,7 +1291,8 @@ var OffsetStore = class {
1246
1291
  };
1247
1292
 
1248
1293
  // src/security/outbound.ts
1249
- import { DefaultSecretScrubber, ToolValidationError } from "@wrongstack/core";
1294
+ import { DefaultSecretScrubber } from "@wrongstack/core/security";
1295
+ import { ToolValidationError } from "@wrongstack/core/types";
1250
1296
  var TELEGRAM_APPROVAL_CAPABILITY = "net.outbound.telegram.approval";
1251
1297
  var secretScrubber = new DefaultSecretScrubber();
1252
1298
  var RAW_TELEGRAM_BOT_TOKEN = /(?<![A-Za-z0-9])\d{5,15}:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g;
@@ -1288,7 +1334,7 @@ function scrubTelegramOutboundText(text) {
1288
1334
  }
1289
1335
 
1290
1336
  // src/slash-commands/index.ts
1291
- import { expectDefined } from "@wrongstack/core";
1337
+ import { expectDefined } from "@wrongstack/core/utils";
1292
1338
  function tgHealthCommand(bot, cfg) {
1293
1339
  return {
1294
1340
  name: "telegram-health",
@@ -1427,7 +1473,7 @@ function makeTelegramApproveTool(opts) {
1427
1473
  const timeoutMs = Math.min(Math.max(input.timeout_ms ?? 6e4, 1e3), 6e5);
1428
1474
  const configuredUserIds = opts.getAllowedUserIds?.().map(String) ?? [];
1429
1475
  const isGroup = String(chatId).startsWith("-");
1430
- if (isGroup && (opts.allowGroupApprovals !== true || configuredUserIds.length === 0)) {
1476
+ if (isGroup && (opts.getAllowGroupApprovals?.() !== true || configuredUserIds.length === 0)) {
1431
1477
  throw new Error("Telegram group approvals require explicit per-user configuration.");
1432
1478
  }
1433
1479
  const expectedUserIds = configuredUserIds.length > 0 ? configuredUserIds : [String(chatId)];
@@ -1449,7 +1495,7 @@ _Reply by tapping a button. Auto-denies in ${Math.round(timeoutMs / 1e3)}s._`;
1449
1495
  sessionId: ctx?.session.id ?? "unknown-session",
1450
1496
  expectedChatId: chatId,
1451
1497
  expectedUserIds,
1452
- allowGroup: isGroup && opts.allowGroupApprovals === true,
1498
+ allowGroup: isGroup && opts.getAllowGroupApprovals?.() === true,
1453
1499
  expiresAt: Date.now() + timeoutMs,
1454
1500
  signal: toolOpts?.signal
1455
1501
  });
@@ -1668,27 +1714,95 @@ var OutboundQueue = class {
1668
1714
  }
1669
1715
  };
1670
1716
 
1717
+ // src/rate-limiter.ts
1718
+ function createTokenBucket(opts) {
1719
+ const tokensPerSecond = opts?.tokensPerSecond ?? 0.33;
1720
+ const burst = opts?.burst ?? 4;
1721
+ const refillIntervalMs = 1e3 / tokensPerSecond;
1722
+ let tokens = burst;
1723
+ let lastRefill = Date.now();
1724
+ return { waitForToken, fill };
1725
+ async function waitForToken(timeoutMs) {
1726
+ const deadline = timeoutMs !== void 0 ? Date.now() + timeoutMs : Infinity;
1727
+ while (true) {
1728
+ refill();
1729
+ if (tokens >= 1) {
1730
+ tokens -= 1;
1731
+ return;
1732
+ }
1733
+ const now = Date.now();
1734
+ if (now >= deadline) {
1735
+ return;
1736
+ }
1737
+ const nextRefill = lastRefill + refillIntervalMs;
1738
+ const delay = Math.min(
1739
+ Math.max(nextRefill - now, 0),
1740
+ deadline - now,
1741
+ 5e3
1742
+ // safety cap: never sleep longer than 5s
1743
+ );
1744
+ await sleep(delay);
1745
+ }
1746
+ }
1747
+ function refill() {
1748
+ const now = Date.now();
1749
+ const elapsed = now - lastRefill;
1750
+ if (elapsed <= 0) return;
1751
+ const newTokens = elapsed / 1e3 * tokensPerSecond;
1752
+ tokens = Math.min(burst, tokens + newTokens);
1753
+ lastRefill = now;
1754
+ }
1755
+ function fill() {
1756
+ refill();
1757
+ return tokens;
1758
+ }
1759
+ }
1760
+ function sleep(ms) {
1761
+ return new Promise((resolve) => setTimeout(resolve, ms));
1762
+ }
1763
+
1671
1764
  // src/bot-queue.ts
1672
1765
  var TelegramBotOutbound = class {
1673
1766
  #queue;
1674
1767
  #bot;
1675
1768
  #log;
1769
+ #buckets = /* @__PURE__ */ new Map();
1770
+ #getRateTokensPerSecond;
1771
+ #getRateBurst;
1676
1772
  #stopped = false;
1677
1773
  constructor(opts) {
1678
1774
  this.#bot = opts.bot;
1679
1775
  this.#log = opts.log;
1776
+ this.#getRateTokensPerSecond = opts.getRateLimitTokensPerSecond ?? (() => 0.33);
1777
+ this.#getRateBurst = opts.getRateLimitBurst ?? (() => 4);
1680
1778
  this.#queue = new OutboundQueue({
1681
1779
  maxPerChat: opts.maxPerChat,
1682
1780
  maxConcurrency: opts.maxConcurrency,
1683
- send: (chatId, text) => this.#bot.sendMessage(chatId, text).then((res) => {
1684
- if (!res.ok) {
1685
- throw new Error(`Telegram outbound send returned ok=false for chat ${chatId}`);
1686
- }
1687
- return res;
1688
- }),
1781
+ send: (chatId, text) => this.#rateLimitedSend(chatId, text),
1689
1782
  log: opts.log
1690
1783
  });
1691
1784
  }
1785
+ /** Per-chat rate-limited send: waits for a token, then delegates to the bot.
1786
+ * Rate-limit values are resolved lazily when a new bucket is created via
1787
+ * the constructor getters so a live config change applies to subsequent
1788
+ * chats without rebuilding the queue. */
1789
+ async #rateLimitedSend(chatId, text) {
1790
+ const key = String(chatId);
1791
+ let bucket = this.#buckets.get(key);
1792
+ if (!bucket) {
1793
+ bucket = createTokenBucket({
1794
+ tokensPerSecond: this.#getRateTokensPerSecond(),
1795
+ burst: this.#getRateBurst()
1796
+ });
1797
+ this.#buckets.set(key, bucket);
1798
+ }
1799
+ await bucket.waitForToken(5e3);
1800
+ const res = await this.#bot.sendMessage(chatId, text);
1801
+ if (!res.ok) {
1802
+ throw new Error(`Telegram outbound send returned ok=false for chat ${chatId}`);
1803
+ }
1804
+ return res;
1805
+ }
1692
1806
  /** Manual send (telegram_send tool, /telegram:send): never silently dropped. */
1693
1807
  async sendManual(chatId, text) {
1694
1808
  if (this.#stopped) {
@@ -1868,7 +1982,7 @@ function makeTelegramReadTool(opts) {
1868
1982
  }
1869
1983
 
1870
1984
  // src/tools/telegram-send.ts
1871
- import { ToolCapabilities } from "@wrongstack/core";
1985
+ import { ToolCapabilities } from "@wrongstack/core/security";
1872
1986
  function makeTelegramSendTool(opts) {
1873
1987
  return {
1874
1988
  name: "telegram_send",
@@ -1912,6 +2026,19 @@ function makeTelegramSendTool(opts) {
1912
2026
  };
1913
2027
  }
1914
2028
 
2029
+ // src/config-classifier.ts
2030
+ import { diffPluginConfig } from "@wrongstack/core/plugin";
2031
+ function diffConfigKeys(previous, next) {
2032
+ return diffPluginConfig(
2033
+ previous,
2034
+ next,
2035
+ TELEGRAM_CONFIG_FIELDS
2036
+ ).map((change) => ({
2037
+ key: change.key,
2038
+ classification: change.lifecycle === "hot" ? "hot" : "restart-required"
2039
+ }));
2040
+ }
2041
+
1915
2042
  // src/index.ts
1916
2043
  var teardownState = null;
1917
2044
  var DENY_ALL_INBOUND = "__wrongstack_telegram_inbound_disabled__";
@@ -1958,22 +2085,25 @@ function registerCommand(api, command, cleanups) {
1958
2085
  api.slashCommands.unregister(`${PLUGIN_NAME}:${command.name}`);
1959
2086
  });
1960
2087
  }
2088
+ function telegramDefaultConfig() {
2089
+ return structuredClone(DEFAULT_CONFIG);
2090
+ }
1961
2091
  function telegramFromConfig(cfg) {
1962
- const ext = cfg.extensions?.[PLUGIN_NAME] ?? {};
2092
+ const tg = readTelegramConfigFromConfig(cfg);
1963
2093
  return {
1964
- notifyChatId: ext.notifyChatId !== void 0 ? String(ext.notifyChatId) : void 0,
1965
- allowedOutboundChats: Array.isArray(ext.allowedOutboundChats) ? ext.allowedOutboundChats.filter(
1966
- (chatId) => typeof chatId === "string" || typeof chatId === "number"
1967
- ) : [],
1968
- allowedUserIds: Array.isArray(ext.allowedUsers) ? ext.allowedUsers.filter(
1969
- (userId) => typeof userId === "string" || typeof userId === "number"
1970
- ) : [],
1971
- allowGroupApprovals: ext.allowGroupApprovals === true,
1972
- notifyOnSessionEnd: ext.notifyOnSessionEnd === true,
1973
- notifyOnDelegate: ext.notifyOnDelegate !== false,
1974
- // default true
1975
- longToolThresholdMs: typeof ext.longToolThresholdMs === "number" ? ext.longToolThresholdMs : 3e4,
1976
- maxMessageLength: typeof ext.maxMessageLength === "number" ? ext.maxMessageLength : 4e3
2094
+ notifyChatId: tg.notifyChatId !== void 0 ? String(tg.notifyChatId) : void 0,
2095
+ allowedOutboundChats: [...tg.allowedOutboundChats ?? []],
2096
+ allowedUserIds: [...tg.allowedUsers ?? []],
2097
+ allowGroupApprovals: tg.allowGroupApprovals ?? false,
2098
+ notifyOnSessionEnd: tg.notifyOnSessionEnd ?? false,
2099
+ notifyOnDelegate: tg.notifyOnDelegate ?? true,
2100
+ longToolThresholdMs: tg.longToolThresholdMs ?? 3e4,
2101
+ maxMessageLength: tg.maxMessageLength ?? 4e3,
2102
+ outboundQueuePerChat: tg.outboundQueuePerChat ?? 32,
2103
+ outboundQueueConcurrency: tg.outboundQueueConcurrency ?? 4,
2104
+ rateLimitTokensPerSecond: tg.rateLimitTokensPerSecond ?? 0.33,
2105
+ rateLimitBurst: tg.rateLimitBurst ?? 1,
2106
+ parseMode: tg.parseMode ?? ""
1977
2107
  };
1978
2108
  }
1979
2109
  var plugin = {
@@ -1986,31 +2116,29 @@ var plugin = {
1986
2116
  slashCommands: true,
1987
2117
  pipelines: []
1988
2118
  },
2119
+ configAliases: [...PLUGIN_CONFIG_ALIASES],
2120
+ configFields: TELEGRAM_CONFIG_FIELDS,
1989
2121
  configSchema: telegramConfigSchema,
1990
- defaultConfig: {
1991
- allowedOutboundChats: [],
1992
- pollIntervalSec: 2,
1993
- notifyOnSessionEnd: false,
1994
- longToolThresholdMs: 3e4,
1995
- maxMessageLength: 4e3
1996
- },
2122
+ defaultConfig: telegramDefaultConfig(),
1997
2123
  async setup(api) {
1998
2124
  const log = api.log;
1999
2125
  disposeRuntime(log);
2000
2126
  const cfg = readTelegramConfig(api);
2001
2127
  log.info("Starting Telegram plugin...");
2002
- const rawCfg = cfg;
2003
2128
  const runtimeCfg = {
2004
2129
  notifyChatId: cfg.notifyChatId,
2005
2130
  allowedOutboundChats: [...cfg.allowedOutboundChats ?? []],
2006
2131
  allowedUserIds: [...cfg.allowedUsers ?? []],
2007
- allowGroupApprovals: rawCfg.allowGroupApprovals === true,
2132
+ allowGroupApprovals: cfg.allowGroupApprovals ?? false,
2008
2133
  notifyOnSessionEnd: cfg.notifyOnSessionEnd ?? false,
2009
2134
  notifyOnDelegate: cfg.notifyOnDelegate ?? true,
2010
2135
  longToolThresholdMs: cfg.longToolThresholdMs ?? 3e4,
2011
2136
  maxMessageLength: cfg.maxMessageLength ?? 4e3,
2012
2137
  outboundQueuePerChat: cfg.outboundQueuePerChat ?? 32,
2013
- outboundQueueConcurrency: cfg.outboundQueueConcurrency ?? 4
2138
+ outboundQueueConcurrency: cfg.outboundQueueConcurrency ?? 4,
2139
+ rateLimitTokensPerSecond: cfg.rateLimitTokensPerSecond ?? 0.33,
2140
+ rateLimitBurst: cfg.rateLimitBurst ?? 1,
2141
+ parseMode: cfg.parseMode ?? ""
2014
2142
  };
2015
2143
  const lock = cfg.singleInstanceLock === false ? void 0 : new PollLock(lockPathForToken(cfg.botToken), { log });
2016
2144
  const offsetStore = cfg.offsetStoragePath === "" ? void 0 : new OffsetStore({ token: cfg.botToken, path: cfg.offsetStoragePath });
@@ -2022,8 +2150,24 @@ var plugin = {
2022
2150
  log,
2023
2151
  offsetStore,
2024
2152
  lock,
2153
+ getParseMode: () => runtimeCfg.parseMode,
2025
2154
  onMessage(msg) {
2026
2155
  api.emitCustom("telegram:message_received", msg);
2156
+ const mailbox = api.mailbox;
2157
+ if (mailbox) {
2158
+ mailbox.send({
2159
+ from: "telegram",
2160
+ to: "leader",
2161
+ type: "note",
2162
+ subject: scrubTelegramOutboundText(
2163
+ `\u{1F4E8} Telegram from ${msg.userName ?? `user_${msg.userId ?? "unknown"}`}`
2164
+ ),
2165
+ body: scrubTelegramOutboundText(msg.text),
2166
+ priority: "low"
2167
+ }).catch((err) => {
2168
+ log.debug(`Telegram\u2192mailbox bridge delivery failed: ${err.message}`);
2169
+ });
2170
+ }
2027
2171
  log.info(`\u{1F4E8} Telegram message received (${Math.min(bot.bufferCount, 50)} unread)`);
2028
2172
  }
2029
2173
  });
@@ -2042,7 +2186,9 @@ var plugin = {
2042
2186
  bot,
2043
2187
  log,
2044
2188
  maxPerChat: runtimeCfg.outboundQueuePerChat,
2045
- maxConcurrency: runtimeCfg.outboundQueueConcurrency
2189
+ maxConcurrency: runtimeCfg.outboundQueueConcurrency,
2190
+ getRateLimitTokensPerSecond: () => runtimeCfg.rateLimitTokensPerSecond,
2191
+ getRateLimitBurst: () => runtimeCfg.rateLimitBurst
2046
2192
  });
2047
2193
  cleanups.push(() => {
2048
2194
  void outbound.stop();
@@ -2060,7 +2206,7 @@ var plugin = {
2060
2206
  getDefaultChatId: () => runtimeCfg.notifyChatId,
2061
2207
  getAllowedOutboundChatIds: () => runtimeCfg.allowedOutboundChats,
2062
2208
  getAllowedUserIds: () => runtimeCfg.allowedUserIds,
2063
- allowGroupApprovals: runtimeCfg.allowGroupApprovals,
2209
+ getAllowGroupApprovals: () => runtimeCfg.allowGroupApprovals,
2064
2210
  maxMessageLength: runtimeCfg.maxMessageLength,
2065
2211
  log
2066
2212
  });
@@ -2172,29 +2318,75 @@ var plugin = {
2172
2318
  })
2173
2319
  );
2174
2320
  const unlistenConfig = api.onConfigChange((next, prev) => {
2321
+ const nextTg = readTelegramConfigFromConfig(next);
2322
+ const prevTg = readTelegramConfigFromConfig(prev);
2323
+ const changedKeys = diffConfigKeys(prevTg, nextTg);
2324
+ const hotKeys = changedKeys.filter((c) => c.classification === "hot").map((c) => c.key);
2325
+ const restartKeys = changedKeys.filter((c) => c.classification === "restart-required").map((c) => c.key);
2175
2326
  const fresh = telegramFromConfig(next);
2176
2327
  const was = telegramFromConfig(prev);
2177
- runtimeCfg.notifyChatId = fresh.notifyChatId;
2178
- runtimeCfg.allowedOutboundChats = fresh.allowedOutboundChats;
2179
- runtimeCfg.allowedUserIds = fresh.allowedUserIds;
2180
- runtimeCfg.allowGroupApprovals = fresh.allowGroupApprovals;
2181
- runtimeCfg.notifyOnSessionEnd = fresh.notifyOnSessionEnd;
2182
- runtimeCfg.notifyOnDelegate = fresh.notifyOnDelegate;
2183
- runtimeCfg.longToolThresholdMs = fresh.longToolThresholdMs;
2184
- runtimeCfg.maxMessageLength = fresh.maxMessageLength;
2185
- if (fresh.notifyChatId !== was.notifyChatId || fresh.maxMessageLength !== was.maxMessageLength) {
2186
- notifyChannel = fresh.notifyChatId !== void 0 ? new TelegramNotificationChannel({
2328
+ const hotSet = new Set(hotKeys);
2329
+ const HOT_APPLIERS = /* @__PURE__ */ new Map([
2330
+ ["allowedOutboundChats", (r, f) => {
2331
+ r.allowedOutboundChats = f.allowedOutboundChats;
2332
+ }],
2333
+ ["allowedUsers", (r, f) => {
2334
+ r.allowedUserIds = f.allowedUserIds;
2335
+ }],
2336
+ ["notifyOnSessionEnd", (r, f) => {
2337
+ r.notifyOnSessionEnd = f.notifyOnSessionEnd;
2338
+ }],
2339
+ ["notifyOnDelegate", (r, f) => {
2340
+ r.notifyOnDelegate = f.notifyOnDelegate;
2341
+ }],
2342
+ ["longToolThresholdMs", (r, f) => {
2343
+ r.longToolThresholdMs = f.longToolThresholdMs;
2344
+ }],
2345
+ ["maxMessageLength", (r, f) => {
2346
+ r.maxMessageLength = f.maxMessageLength;
2347
+ }],
2348
+ ["allowGroupApprovals", (r, f) => {
2349
+ r.allowGroupApprovals = f.allowGroupApprovals;
2350
+ }],
2351
+ ["rateLimitTokensPerSecond", (r, f) => {
2352
+ r.rateLimitTokensPerSecond = f.rateLimitTokensPerSecond;
2353
+ }],
2354
+ ["rateLimitBurst", (r, f) => {
2355
+ r.rateLimitBurst = f.rateLimitBurst;
2356
+ }],
2357
+ ["parseMode", (r, f) => {
2358
+ r.parseMode = f.parseMode;
2359
+ }]
2360
+ ]);
2361
+ for (const [key, apply] of HOT_APPLIERS) {
2362
+ if (hotSet.has(key)) apply(runtimeCfg, fresh);
2363
+ }
2364
+ if (hotSet.has("maxMessageLength") && fresh.maxMessageLength !== was.maxMessageLength) {
2365
+ notifyChannel = runtimeCfg.notifyChatId !== void 0 ? new TelegramNotificationChannel({
2187
2366
  bot,
2188
- chatId: fresh.notifyChatId,
2367
+ chatId: runtimeCfg.notifyChatId,
2189
2368
  maxMessageLength: fresh.maxMessageLength,
2190
2369
  enqueueNotification: (chatId, text) => outbound.enqueueNotification(chatId, text),
2191
2370
  log
2192
2371
  }) : void 0;
2193
2372
  }
2194
- log.debug("Telegram notification settings updated from config", {
2373
+ if (restartKeys.length > 0) {
2374
+ log.warn(
2375
+ "Telegram config changed restart-required keys \u2014 restart the plugin for these to take effect",
2376
+ { restartKeys, hotKeys }
2377
+ );
2378
+ api.emitCustom("telegram:restart_required", {
2379
+ keys: restartKeys,
2380
+ message: `Restart required for: ${restartKeys.join(", ")}`
2381
+ });
2382
+ }
2383
+ log.debug("Telegram config updated", {
2384
+ hotApplied: hotKeys,
2385
+ restartRequired: restartKeys,
2195
2386
  notifyOnSessionEnd: runtimeCfg.notifyOnSessionEnd,
2196
2387
  notifyOnDelegate: runtimeCfg.notifyOnDelegate,
2197
2388
  longToolThresholdMs: runtimeCfg.longToolThresholdMs,
2389
+ parseMode: runtimeCfg.parseMode,
2198
2390
  notifyChatId: runtimeCfg.notifyChatId ?? "not set"
2199
2391
  });
2200
2392
  });