@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/README.md +1 -1
- package/dist/api-client.d.ts +2 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/bot-queue.d.ts +16 -1
- package/dist/bot-queue.d.ts.map +1 -1
- package/dist/bot.d.ts +8 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/config-classifier.d.ts +1 -52
- package/dist/config-classifier.d.ts.map +1 -1
- package/dist/config.d.ts +110 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +282 -90
- package/dist/index.js.map +3 -3
- package/dist/manifest.d.ts +35 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/notification-channel.d.ts +2 -2
- package/dist/notification-channel.d.ts.map +1 -1
- package/dist/outbound-queue.d.ts +1 -1
- package/dist/outbound-queue.d.ts.map +1 -1
- package/dist/poll-lock.d.ts +1 -1
- package/dist/poll-lock.d.ts.map +1 -1
- package/dist/rate-limiter.d.ts +29 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/security/outbound.d.ts.map +1 -1
- package/dist/slash-commands/index.d.ts +2 -1
- package/dist/slash-commands/index.d.ts.map +1 -1
- package/dist/tools/telegram-approve.d.ts +6 -3
- package/dist/tools/telegram-approve.d.ts.map +1 -1
- package/dist/tools/telegram-read.d.ts +1 -1
- package/dist/tools/telegram-read.d.ts.map +1 -1
- package/dist/tools/telegram-send.d.ts +1 -1
- package/dist/tools/telegram-send.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/inbox-cursor-store.d.ts +0 -54
- package/dist/inbox-cursor-store.d.ts.map +0 -1
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
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
const
|
|
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:
|
|
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
|
|
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.
|
|
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.
|
|
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.#
|
|
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
|
|
2092
|
+
const tg = readTelegramConfigFromConfig(cfg);
|
|
1963
2093
|
return {
|
|
1964
|
-
notifyChatId:
|
|
1965
|
-
allowedOutboundChats:
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
});
|