@wrongstack/telegram 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # @wrongstack/telegram
2
+
3
+ Telegram bridge for WrongStack — connect your agent to Telegram.
4
+ Send messages, receive instructions, get notified when long tasks finish.
5
+
6
+ ## Features
7
+
8
+ - **`telegram_read`** — Agent reads incoming Telegram messages (newest first, filtered by chat, with ack support)
9
+ - **`telegram_send`** — Agent sends messages via Telegram (HTML formatting, confirm permission)
10
+ - **System prompt injection** — Unread messages appear in the agent's system prompt so it sees them naturally
11
+ - **Slash commands** — `/tg status`, `/tg send`, `/tg chatid` in the TUI
12
+ - **Event notifications** — Session end summaries and long tool completions forwarded to Telegram
13
+ - **Allowlist filtering** — Restrict which users/chats can interact with the bot
14
+ - **Zero dependencies** — Uses Node.js native `fetch`, no third-party Telegram libraries
15
+
16
+ ## Quickstart
17
+
18
+ ### 1. Create a bot
19
+
20
+ Message [@BotFather](https://t.me/BotFather) on Telegram:
21
+ ```
22
+ /newbot
23
+ ```
24
+
25
+ Copy the token (looks like `123456789:ABCdef...`).
26
+
27
+ ### 2. Get your chat ID
28
+
29
+ Message your new bot, then visit:
30
+ ```
31
+ https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates
32
+ ```
33
+
34
+ Find your `chat.id` in the response.
35
+
36
+ ### 3. Configure
37
+
38
+ In `~/.wrongstack/config.json` or `.wrongstack/config.json`:
39
+
40
+ ```jsonc
41
+ {
42
+ "plugins": {
43
+ "telegram": {
44
+ "botToken": "123456789:ABCdefGHIjkl...",
45
+ "notifyChatId": "987654321",
46
+ "allowedUsers": [987654321],
47
+ "notifyOnSessionEnd": true,
48
+ "longToolThresholdMs": 30000,
49
+ "pollIntervalSec": 2
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### 4. Install
56
+
57
+ ```bash
58
+ pnpm add @wrongstack/telegram
59
+ ```
60
+
61
+ The plugin loads automatically on next WrongStack start.
62
+
63
+ ## Configuration reference
64
+
65
+ | Field | Type | Required | Default | Description |
66
+ |---|---|---|---|---|
67
+ | `botToken` | `string` | **yes** | — | Bot token from @BotFather |
68
+ | `notifyChatId` | `string \| number` | no | — | Default chat for outgoing messages and notifications |
69
+ | `allowedUsers` | `(string \| number)[]` | no | `[]` | User IDs allowed to interact. Empty = all allowed |
70
+ | `allowedChats` | `(string \| number)[]` | no | `[]` | Chat IDs the bot reads from. Empty = all allowed |
71
+ | `pollIntervalSec` | `number` | no | `2` | How often to poll Telegram for new messages (1–60) |
72
+ | `notifyOnSessionEnd` | `boolean` | no | `false` | Send token usage summary when a session ends |
73
+ | `longToolThresholdMs` | `number` | no | `30000` | Notify when a tool runs longer than this (ms). `0` = off |
74
+ | `maxMessageLength` | `number` | no | `4000` | Max chars per outgoing message (Telegram limit: 4096) |
75
+
76
+ ## Tools
77
+
78
+ ### `telegram_read`
79
+
80
+ Read buffered incoming messages.
81
+
82
+ ```jsonc
83
+ // Read all recent messages
84
+ telegram_read()
85
+
86
+ // Read from a specific chat
87
+ telegram_read(chat_id: "987654321", limit: 5)
88
+
89
+ // Read and acknowledge (clear from buffer)
90
+ telegram_read(ack_last: 42)
91
+ ```
92
+
93
+ Permission: `auto` | Category: `Telegram`
94
+
95
+ ### `telegram_send`
96
+
97
+ Send a message to a Telegram chat.
98
+
99
+ ```jsonc
100
+ // Send using default chat
101
+ telegram_send(message: "Build succeeded ✓")
102
+
103
+ // Send to a specific chat
104
+ telegram_send(chat_id: "123456", message: "Deploy complete. Check staging.")
105
+ ```
106
+
107
+ Permission: `confirm` | Category: `Telegram`
108
+
109
+ Message text supports Telegram HTML: `<b>bold</b>`, `<i>italic</i>`, `<code>mono</code>`, `<a href="...">links</a>`, `<pre>code blocks</pre>`.
110
+
111
+ ## Slash commands (TUI)
112
+
113
+ | Command | Description |
114
+ |---|---|
115
+ | `/tg status` | Bot connection health, polling config, allowlist stats, notification settings |
116
+ | `/tg send [chat_id] <msg>` | Send a message from the terminal |
117
+ | `/tg chatid` | Show the configured default chat ID |
118
+
119
+ ## How it works
120
+
121
+ ```
122
+ ┌─────────────────┐ poll ┌──────────────┐
123
+ │ Telegram API │◄──────────────│ TelegramBot │
124
+ │ (getUpdates) │──────────────►│ (buffer) │
125
+ └────────┬────────┘ updates └──────┬───────┘
126
+ │ │
127
+ user sends ┌────▼───────┐
128
+ "build failed?" │ PluginAPI │
129
+ │ .emitCustom│
130
+ │ .contrib. │
131
+ └────┬───────┘
132
+
133
+ ┌─────────▼─────────┐
134
+ │ Agent sees inbox │
135
+ │ in system prompt │
136
+ │ calls read/send │
137
+ └───────────────────┘
138
+ ```
139
+
140
+ 1. Bot polls Telegram every N seconds via `getUpdates`
141
+ 2. Incoming messages go into a circular buffer (50 max)
142
+ 3. A system prompt contributor injects unread messages so the agent sees them
143
+ 4. Agent reads with `telegram_read`, responds with `telegram_send`
144
+ 5. Custom event `telegram:message_received` fires for TUI panels / other plugins
145
+
146
+ ## Events
147
+
148
+ | Event | Payload | When |
149
+ |---|---|---|
150
+ | `telegram:message_received` | `TelegramIncomingMessage` | Incoming message passes allowlist |
151
+ | `session.ended` | session summary → Telegram | If `notifyOnSessionEnd: true` |
152
+ | `tool.executed` | tool result → Telegram | If duration > `longToolThresholdMs` |
153
+
154
+ ## License
155
+
156
+ MIT
@@ -0,0 +1,43 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ interface TelegramIncomingMessage {
4
+ messageId: number;
5
+ chatId: number;
6
+ chatType: string;
7
+ userId?: number;
8
+ userName?: string;
9
+ text: string;
10
+ timestamp: number;
11
+ }
12
+
13
+ interface TelegramPluginConfig {
14
+ /** Telegram Bot API token (from @BotFather). */
15
+ botToken: string;
16
+ /**
17
+ * Default chat ID for outgoing notifications.
18
+ * The agent's `telegram_send` tool can override per-call.
19
+ */
20
+ notifyChatId?: string | number;
21
+ /**
22
+ * List of user/chat IDs allowed to interact with the bot.
23
+ * Empty = allow all. Recommended to set in production.
24
+ */
25
+ allowedUsers?: Array<string | number>;
26
+ /**
27
+ * List of group/chat IDs the bot is allowed to read from.
28
+ * Empty = allow all. Narrow this to prevent noise.
29
+ */
30
+ allowedChats?: Array<string | number>;
31
+ /** Polling interval in seconds (default: 2). */
32
+ pollIntervalSec?: number;
33
+ /** Notify on Telegram when a session ends. */
34
+ notifyOnSessionEnd?: boolean;
35
+ /** Notify when a tool runs longer than this threshold (ms). Set 0 to disable. */
36
+ longToolThresholdMs?: number;
37
+ /** Maximum message length for Telegram (Telegram caps at 4096). */
38
+ maxMessageLength?: number;
39
+ }
40
+
41
+ declare const plugin: Plugin;
42
+
43
+ export { type TelegramIncomingMessage, type TelegramPluginConfig, plugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,591 @@
1
+ // src/bot.ts
2
+ var TelegramBot = class {
3
+ token;
4
+ baseUrl;
5
+ pollIntervalMs;
6
+ allowedUsers;
7
+ allowedChats;
8
+ log;
9
+ onMessage;
10
+ controller = new AbortController();
11
+ pollTimer = null;
12
+ pollActive = false;
13
+ offset = 0;
14
+ _startedAt = null;
15
+ // Circular buffer for incoming messages
16
+ bufferMax;
17
+ buffer = [];
18
+ constructor(opts) {
19
+ this.token = opts.token;
20
+ this.baseUrl = `https://api.telegram.org/bot${opts.token}`;
21
+ this.pollIntervalMs = opts.pollIntervalSec * 1e3;
22
+ this.allowedUsers = opts.allowedUsers;
23
+ this.allowedChats = opts.allowedChats;
24
+ this.bufferMax = opts.bufferSize;
25
+ this.log = opts.log;
26
+ this.onMessage = opts.onMessage;
27
+ }
28
+ // ------------------------------------------------------------------
29
+ // Lifecycle
30
+ // ------------------------------------------------------------------
31
+ /** Start polling for updates. Idempotent. */
32
+ start() {
33
+ if (this.pollActive) return;
34
+ this.pollActive = true;
35
+ this._startedAt = Date.now();
36
+ this.log.info("Telegram bot polling started");
37
+ this.schedulePoll();
38
+ }
39
+ /** Stop polling and cancel all in-flight requests. */
40
+ stop() {
41
+ this.pollActive = false;
42
+ this.controller.abort();
43
+ if (this.pollTimer) {
44
+ clearTimeout(this.pollTimer);
45
+ this.pollTimer = null;
46
+ }
47
+ this.log.info("Telegram bot stopped");
48
+ }
49
+ get startedAt() {
50
+ return this._startedAt;
51
+ }
52
+ get running() {
53
+ return this.pollActive;
54
+ }
55
+ // ------------------------------------------------------------------
56
+ // Buffer — incoming messages the agent can read
57
+ // ------------------------------------------------------------------
58
+ /** Return buffered messages, newest first. Optionally filter by chat. */
59
+ getMessages(opts) {
60
+ let msgs = [...this.buffer].reverse();
61
+ if (opts?.chatId) {
62
+ const cid = String(opts.chatId);
63
+ msgs = msgs.filter((m) => String(m.chatId) === cid);
64
+ }
65
+ const limit = opts?.limit ?? 20;
66
+ return msgs.slice(0, limit);
67
+ }
68
+ /** Drop messages older than the given message ID from the buffer. */
69
+ acknowledge(lastMessageId) {
70
+ const before = this.buffer.length;
71
+ let i = this.buffer.length;
72
+ while (i-- > 0) {
73
+ if (this.buffer[i].messageId <= lastMessageId) {
74
+ this.buffer.splice(0, i + 1);
75
+ break;
76
+ }
77
+ }
78
+ return before - this.buffer.length;
79
+ }
80
+ get bufferCount() {
81
+ return this.buffer.length;
82
+ }
83
+ // ------------------------------------------------------------------
84
+ // Outgoing — send a message
85
+ // ------------------------------------------------------------------
86
+ async sendMessage(chatId, text) {
87
+ const url = `${this.baseUrl}/sendMessage`;
88
+ const body = JSON.stringify({
89
+ chat_id: String(chatId),
90
+ text,
91
+ parse_mode: "HTML",
92
+ disable_web_page_preview: true
93
+ });
94
+ this.log.debug(`Sending Telegram message to ${chatId} (${text.length} chars)`);
95
+ let lastErr;
96
+ for (let attempt = 1; attempt <= 3; attempt++) {
97
+ try {
98
+ const res = await fetch(url, {
99
+ method: "POST",
100
+ headers: { "Content-Type": "application/json" },
101
+ body,
102
+ signal: AbortSignal.timeout(1e4)
103
+ });
104
+ const data = await res.json();
105
+ if (!data.ok) {
106
+ throw new Error(`Telegram API error ${data.error_code}: ${data.description}`);
107
+ }
108
+ return data;
109
+ } catch (err) {
110
+ lastErr = err;
111
+ if (attempt < 3) {
112
+ this.log.warn(`Telegram sendMessage attempt ${attempt} failed, retrying in 1s...`);
113
+ await sleep(1e3);
114
+ }
115
+ }
116
+ }
117
+ throw lastErr;
118
+ }
119
+ // ------------------------------------------------------------------
120
+ // Health
121
+ // ------------------------------------------------------------------
122
+ async health() {
123
+ try {
124
+ const url = `${this.baseUrl}/getMe`;
125
+ const res = await fetch(url, { signal: AbortSignal.timeout(5e3) });
126
+ const data = await res.json();
127
+ if (!data.ok || !data.result) {
128
+ return { ok: false, error: data.description ?? "Unknown error" };
129
+ }
130
+ return { ok: true, username: data.result.username };
131
+ } catch (err) {
132
+ return { ok: false, error: err.message };
133
+ }
134
+ }
135
+ // ------------------------------------------------------------------
136
+ // Polling
137
+ // ------------------------------------------------------------------
138
+ schedulePoll() {
139
+ if (!this.pollActive) return;
140
+ this.pollTimer = setTimeout(() => {
141
+ void this.poll().finally(() => this.schedulePoll());
142
+ }, this.pollIntervalMs);
143
+ }
144
+ async poll() {
145
+ try {
146
+ const url = `${this.baseUrl}/getUpdates?offset=${this.offset}&timeout=10`;
147
+ const res = await fetch(url, { signal: this.controller.signal });
148
+ const data = await res.json();
149
+ if (!data.ok) {
150
+ this.log.warn(`Telegram getUpdates failed: ${data.description}`);
151
+ return;
152
+ }
153
+ const updates = data.result ?? [];
154
+ for (const upd of updates) {
155
+ this.offset = upd.update_id + 1;
156
+ const raw = upd.message ?? upd.edited_message;
157
+ if (!raw?.text) continue;
158
+ const msg = { ...raw, text: raw.text };
159
+ this.processMessage(msg);
160
+ }
161
+ } catch (err) {
162
+ if (err.name === "AbortError") return;
163
+ this.log.warn(`Telegram poll error: ${err.message}`);
164
+ }
165
+ }
166
+ processMessage(msg) {
167
+ const chatId = String(msg.chat.id);
168
+ const userId = msg.from ? String(msg.from.id) : void 0;
169
+ if (this.allowedUsers.size > 0 && userId && !this.allowedUsers.has(userId)) {
170
+ this.log.debug(`Ignoring message from user ${userId} (not in allowedUsers)`);
171
+ void this.sendMessage(chatId, "\u26D4 You are not authorized to interact with this bot.");
172
+ return;
173
+ }
174
+ if (this.allowedChats.size > 0 && !this.allowedChats.has(chatId)) {
175
+ this.log.debug(`Ignoring message from chat ${chatId} (not in allowedChats)`);
176
+ return;
177
+ }
178
+ const incoming = {
179
+ messageId: msg.message_id,
180
+ chatId: msg.chat.id,
181
+ chatType: msg.chat.type,
182
+ userId: msg.from?.id,
183
+ userName: msg.from?.username ?? msg.from?.first_name,
184
+ text: msg.text,
185
+ timestamp: msg.date * 1e3
186
+ };
187
+ this.buffer.push(incoming);
188
+ while (this.buffer.length > this.bufferMax) this.buffer.shift();
189
+ this.onMessage(incoming);
190
+ }
191
+ };
192
+ function sleep(ms) {
193
+ return new Promise((r) => setTimeout(r, ms));
194
+ }
195
+ function truncateForTelegram(text, maxLen = 4e3) {
196
+ if (text.length <= maxLen) return text;
197
+ const cut = text.lastIndexOf("\n", maxLen - 20);
198
+ const idx = cut > maxLen / 2 ? cut : maxLen - 20;
199
+ return `${text.slice(0, idx)}
200
+
201
+ \u2026[truncated ${text.length - idx} chars]`;
202
+ }
203
+ function escapeHtml(text) {
204
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
205
+ }
206
+
207
+ // src/config.ts
208
+ var PLUGIN_NAME = "telegram";
209
+ var DEFAULT_CONFIG = {
210
+ allowedUsers: [],
211
+ allowedChats: [],
212
+ pollIntervalSec: 2,
213
+ notifyOnSessionEnd: false,
214
+ longToolThresholdMs: 3e4,
215
+ maxMessageLength: 4e3
216
+ };
217
+ var telegramConfigSchema = {
218
+ type: "object",
219
+ properties: {
220
+ botToken: { type: "string", description: "Telegram Bot API token from @BotFather" },
221
+ notifyChatId: {
222
+ oneOf: [{ type: "string" }, { type: "integer" }],
223
+ description: "Default chat ID for outgoing notifications"
224
+ },
225
+ allowedUsers: {
226
+ type: "array",
227
+ items: { oneOf: [{ type: "string" }, { type: "integer" }] },
228
+ description: "User IDs allowed to interact with the bot"
229
+ },
230
+ allowedChats: {
231
+ type: "array",
232
+ items: { oneOf: [{ type: "string" }, { type: "integer" }] },
233
+ description: "Chat IDs the bot is allowed to read from"
234
+ },
235
+ pollIntervalSec: {
236
+ type: "integer",
237
+ minimum: 1,
238
+ maximum: 60,
239
+ description: "Polling interval in seconds"
240
+ },
241
+ notifyOnSessionEnd: { type: "boolean" },
242
+ longToolThresholdMs: { type: "integer", minimum: 0 },
243
+ maxMessageLength: { type: "integer", minimum: 100, maximum: 4096 }
244
+ },
245
+ required: ["botToken"]
246
+ };
247
+ function readTelegramConfig(api) {
248
+ const raw = api.config.plugins;
249
+ const opts = raw?.[PLUGIN_NAME] ?? {};
250
+ return {
251
+ ...DEFAULT_CONFIG,
252
+ ...opts
253
+ };
254
+ }
255
+
256
+ // src/slash-commands/index.ts
257
+ function tgStatusCommand(bot, cfg) {
258
+ return {
259
+ name: "status",
260
+ aliases: ["tgstat", "tgs"],
261
+ description: "Show Telegram bot connection status and config",
262
+ help: `Usage: /tg status
263
+
264
+ Shows whether the bot is connected, its username, polling interval,
265
+ allowlist status, and notification settings.`,
266
+ async run(_args, _ctx) {
267
+ const health = await bot.health();
268
+ const lines = [
269
+ "\u2550\u2550\u2550 Telegram Plugin Status \u2550\u2550\u2550",
270
+ "",
271
+ `Bot: ${health.ok ? `\u2705 @${health.username ?? "connected"}` : `\u274C ${health.error ?? "offline"}`}`,
272
+ `Running: ${bot.running ? "yes" : "no"}`,
273
+ `Started: ${bot.startedAt ? new Date(bot.startedAt).toLocaleTimeString() : "N/A"}`,
274
+ `Poll: every ${cfg.pollIntervalSec ?? 2}s`,
275
+ `Allowed: ${(cfg.allowedUsers?.length ?? 0) > 0 ? `${cfg.allowedUsers.length} users` : "everyone (users)"} / ${(cfg.allowedChats?.length ?? 0) > 0 ? `${cfg.allowedChats.length} chats` : "everyone (chats)"}`,
276
+ `Notify: sessionEnd=${cfg.notifyOnSessionEnd ?? false}, longTool=${cfg.longToolThresholdMs ? `${cfg.longToolThresholdMs}ms` : "off"}`
277
+ ];
278
+ return { message: lines.join("\n") };
279
+ }
280
+ };
281
+ }
282
+ function tgSendCommand(bot, defaultChatId) {
283
+ return {
284
+ name: "send",
285
+ description: "Send a message to a Telegram chat",
286
+ help: `Usage: /tg send [chat_id] <message>
287
+
288
+ Send a message to a Telegram chat.
289
+ - First argument (optional): chat or user ID. Uses notifyChatId from config when omitted.
290
+ - Everything else: the message text.
291
+
292
+ Examples:
293
+ /tg send 123456789 Build completed successfully \u2713
294
+ /tg send Deploy finished \u2014 check staging`,
295
+ async run(args, _ctx) {
296
+ if (!args.trim()) {
297
+ return { message: "Usage: /tg send [chat_id] <message>" };
298
+ }
299
+ let chatId;
300
+ let text;
301
+ const parts = args.trim().split(/\s+/);
302
+ const maybeId = parts[0];
303
+ if (/^\d+$/.test(maybeId) && parts.length > 1) {
304
+ chatId = maybeId;
305
+ text = parts.slice(1).join(" ");
306
+ } else if (defaultChatId) {
307
+ chatId = defaultChatId;
308
+ text = args.trim();
309
+ } else {
310
+ return {
311
+ message: "No chat_id provided and no default notifyChatId configured.\nUsage: /tg send <chat_id> <message>"
312
+ };
313
+ }
314
+ try {
315
+ const res = await bot.sendMessage(chatId, text);
316
+ return {
317
+ message: `\u2705 Message sent to ${chatId} (msg_id=${res.result?.message_id ?? "?"})`
318
+ };
319
+ } catch (err) {
320
+ return { message: `\u274C Failed to send: ${err.message}` };
321
+ }
322
+ }
323
+ };
324
+ }
325
+ function tgChatIdCommand(defaultChatId) {
326
+ const chatIdStr = defaultChatId ? String(defaultChatId) : null;
327
+ return {
328
+ name: "chatid",
329
+ description: "Show the configured default chat ID",
330
+ help: `Usage: /tg chatid
331
+
332
+ Shows the current default notifyChatId used for notifications
333
+ and the \`telegram_send\` tool when no chat_id is specified.`,
334
+ async run(_args, _ctx) {
335
+ if (chatIdStr) {
336
+ return { message: `Configured notifyChatId: ${chatIdStr}` };
337
+ }
338
+ return { message: "No notifyChatId configured. Set it in the plugin config or pass chat_id explicitly to telegram_send." };
339
+ }
340
+ };
341
+ }
342
+ function registerSlashCommands(api, bot, cfg) {
343
+ const cmds = [
344
+ tgStatusCommand(bot, cfg),
345
+ tgSendCommand(bot, cfg.notifyChatId),
346
+ tgChatIdCommand(cfg.notifyChatId)
347
+ ];
348
+ for (const cmd of cmds) api.slashCommands.register(cmd);
349
+ return cmds.map((c) => c.name);
350
+ }
351
+
352
+ // src/tools/telegram-read.ts
353
+ function makeTelegramReadTool(opts) {
354
+ return {
355
+ name: "telegram_read",
356
+ description: "Read incoming Telegram messages from the bot. Returns recent messages the bot received, newest first. Use this to check if anyone sent instructions, questions, or feedback via Telegram. After processing messages, pass the last message_id to ack_last to clear them from the inbox.",
357
+ usageHint: 'telegram_read(chat_id: "123456789", limit: 5)',
358
+ category: "Telegram",
359
+ inputSchema: {
360
+ type: "object",
361
+ properties: {
362
+ chat_id: {
363
+ oneOf: [{ type: "string" }, { type: "integer" }],
364
+ description: "Read messages only from this chat/user."
365
+ },
366
+ limit: {
367
+ type: "integer",
368
+ minimum: 1,
369
+ maximum: 50,
370
+ description: "Max messages to return (default: 10)."
371
+ },
372
+ ack_last: {
373
+ type: "integer",
374
+ description: "After processing messages, pass the highest message_id to clear them from the buffer."
375
+ }
376
+ }
377
+ },
378
+ permission: "auto",
379
+ mutating: false,
380
+ timeoutMs: 5e3,
381
+ async execute(input) {
382
+ const msgs = opts.bot.getMessages({
383
+ chatId: input.chat_id,
384
+ limit: input.limit ?? 10
385
+ });
386
+ let acked = 0;
387
+ if (input.ack_last !== void 0 && input.ack_last > 0) {
388
+ acked = opts.bot.acknowledge(input.ack_last);
389
+ }
390
+ return {
391
+ buffer_total: opts.bot.bufferCount,
392
+ messages: msgs.map((m) => ({
393
+ message_id: m.messageId,
394
+ chat_id: m.chatId,
395
+ chat_type: m.chatType,
396
+ from: m.userName ?? `user_${m.userId ?? "unknown"}`,
397
+ text: m.text,
398
+ ts: new Date(m.timestamp).toISOString()
399
+ })),
400
+ acked,
401
+ hint: acked > 0 ? void 0 : "Use ack_last with the highest message_id to clear processed messages."
402
+ };
403
+ }
404
+ };
405
+ }
406
+
407
+ // src/tools/telegram-send.ts
408
+ function makeTelegramSendTool(opts) {
409
+ return {
410
+ name: "telegram_send",
411
+ description: "Send a message via Telegram to a specified chat. Use this to notify users, report results, or communicate through Telegram. The message supports HTML formatting (bold, italic, code, links).",
412
+ usageHint: 'telegram_send(chat_id: "123456789", message: "Task completed \u2713")',
413
+ category: "Telegram",
414
+ inputSchema: {
415
+ type: "object",
416
+ properties: {
417
+ chat_id: {
418
+ oneOf: [{ type: "string" }, { type: "integer" }],
419
+ description: "Target chat or user ID. Uses the plugin default when omitted."
420
+ },
421
+ message: {
422
+ type: "string",
423
+ description: 'Message text (supports HTML: <b>bold</b>, <i>italic</i>, <code>mono</code>, <a href="...">links</a>).'
424
+ }
425
+ },
426
+ required: ["message"]
427
+ },
428
+ permission: "confirm",
429
+ mutating: true,
430
+ timeoutMs: 15e3,
431
+ async execute(input, _ctx, _opts) {
432
+ const chatId = input.chat_id ?? opts.defaultChatId;
433
+ if (!chatId) {
434
+ throw new Error(
435
+ "No chat_id provided and no default notifyChatId configured. Set notifyChatId in plugin config or pass chat_id."
436
+ );
437
+ }
438
+ const safeMsg = escapeHtml(input.message);
439
+ const truncated = truncateForTelegram(safeMsg, opts.maxMessageLength);
440
+ opts.log.info(`telegram_send \u2192 chat_id=${chatId} (${truncated.length} chars)`);
441
+ const res = await opts.bot.sendMessage(chatId, truncated);
442
+ return {
443
+ ok: res.ok,
444
+ message_id: res.result?.message_id,
445
+ chat: res.result?.chat ? {
446
+ id: res.result.chat.id,
447
+ type: res.result.chat.type,
448
+ title: res.result.chat.title
449
+ } : void 0
450
+ };
451
+ }
452
+ };
453
+ }
454
+
455
+ // src/index.ts
456
+ var teardownState = null;
457
+ var plugin = {
458
+ name: PLUGIN_NAME,
459
+ version: "0.1.0",
460
+ description: "Telegram bridge \u2014 send/receive messages, get agent notifications.",
461
+ apiVersion: "^0.1.10",
462
+ capabilities: {
463
+ tools: true,
464
+ slashCommands: true,
465
+ pipelines: []
466
+ },
467
+ configSchema: telegramConfigSchema,
468
+ defaultConfig: {
469
+ pollIntervalSec: 2,
470
+ notifyOnSessionEnd: false,
471
+ longToolThresholdMs: 3e4,
472
+ maxMessageLength: 4e3
473
+ },
474
+ async setup(api) {
475
+ const cfg = readTelegramConfig(api);
476
+ const log = api.log;
477
+ log.info("Starting Telegram plugin...");
478
+ const bot = new TelegramBot({
479
+ token: cfg.botToken,
480
+ pollIntervalSec: cfg.pollIntervalSec,
481
+ allowedUsers: new Set((cfg.allowedUsers ?? []).map(String)),
482
+ allowedChats: new Set((cfg.allowedChats ?? []).map(String)),
483
+ bufferSize: 50,
484
+ log,
485
+ onMessage(msg) {
486
+ api.emitCustom("telegram:message_received", msg);
487
+ const who = msg.userName ?? msg.userId ?? "unknown";
488
+ log.info(`\u{1F4E8} Telegram: ${who} (chat=${msg.chatId}): ${msg.text.slice(0, 200)}`);
489
+ }
490
+ });
491
+ const sendTool = makeTelegramSendTool({
492
+ bot,
493
+ defaultChatId: cfg.notifyChatId,
494
+ maxMessageLength: cfg.maxMessageLength,
495
+ log
496
+ });
497
+ const readTool = makeTelegramReadTool({ bot });
498
+ api.tools.register(sendTool);
499
+ api.tools.register(readTool);
500
+ const offs = [];
501
+ const unregisterPrompt = api.registerSystemPromptContributor(async () => {
502
+ const msgs = bot.getMessages({ limit: 5 });
503
+ if (msgs.length === 0) return [];
504
+ const blocks = [
505
+ {
506
+ type: "text",
507
+ text: [
508
+ "## Telegram Inbox",
509
+ `You have ${bot.bufferCount} unread Telegram message(s).`,
510
+ "Read them with `telegram_read` and reply with `telegram_send`.",
511
+ "",
512
+ "Recent messages:",
513
+ ...msgs.map((m) => {
514
+ const who = m.userName ?? `user_${m.userId ?? "unknown"}`;
515
+ const ts = new Date(m.timestamp).toLocaleTimeString();
516
+ return `- [${ts}] **${who}** (chat=${m.chatId}): ${m.text.slice(0, 200)}`;
517
+ }),
518
+ ""
519
+ ].join("\n")
520
+ }
521
+ ];
522
+ return blocks;
523
+ });
524
+ offs.push(unregisterPrompt);
525
+ const commandNames = registerSlashCommands(api, bot, cfg);
526
+ if (cfg.notifyOnSessionEnd && cfg.notifyChatId) {
527
+ offs.push(
528
+ api.events.on("session.ended", (event) => {
529
+ const inputTokens = event.usage.input ?? 0;
530
+ const outputTokens = event.usage.output ?? 0;
531
+ const totalTokens = inputTokens + outputTokens;
532
+ const msg = [
533
+ "\u2705 <b>Session ended</b>",
534
+ "",
535
+ `Session: <code>${event.id.slice(0, 8)}</code>`,
536
+ `Input: ${inputTokens} tokens`,
537
+ `Output: ${outputTokens} tokens`,
538
+ `Total: ${totalTokens} tokens`
539
+ ].join("\n");
540
+ void bot.sendMessage(cfg.notifyChatId, msg).catch((err) => {
541
+ log.warn(`Failed to send session end notification: ${err.message}`);
542
+ });
543
+ })
544
+ );
545
+ }
546
+ if (cfg.longToolThresholdMs && cfg.longToolThresholdMs > 0 && cfg.notifyChatId) {
547
+ offs.push(
548
+ api.events.on("tool.executed", (event) => {
549
+ if (event.durationMs < cfg.longToolThresholdMs) return;
550
+ const sec = (event.durationMs / 1e3).toFixed(1);
551
+ const status = event.ok ? "\u2705" : "\u274C";
552
+ const preview = event.output ? truncateForTelegram(escapeHtml(event.output), 500) : "(no output)";
553
+ const msg = [
554
+ `${status} <b>${escapeHtml(event.name)}</b> completed in ${sec}s`,
555
+ "",
556
+ `<pre>${preview}</pre>`
557
+ ].join("\n");
558
+ void bot.sendMessage(cfg.notifyChatId, msg).catch((err) => {
559
+ log.warn(`Failed to send tool notification: ${err.message}`);
560
+ });
561
+ })
562
+ );
563
+ }
564
+ bot.start();
565
+ teardownState = { offs, toolNames: [sendTool.name, readTool.name], commandNames, bot };
566
+ log.info("Telegram plugin ready");
567
+ },
568
+ async teardown(api) {
569
+ const state = teardownState;
570
+ if (!state) return;
571
+ teardownState = null;
572
+ state.bot.stop();
573
+ for (const off of state.offs) off();
574
+ for (const name of state.toolNames) api.tools.unregister(name);
575
+ for (const name of state.commandNames) {
576
+ api.slashCommands.unregister(`${PLUGIN_NAME}:${name}`);
577
+ }
578
+ api.log.info("Telegram plugin torn down");
579
+ },
580
+ async health() {
581
+ const state = teardownState;
582
+ if (!state?.bot) return { ok: false, message: "Plugin not initialized" };
583
+ const h = await state.bot.health();
584
+ return h;
585
+ }
586
+ };
587
+ var index_default = plugin;
588
+
589
+ export { index_default as default };
590
+ //# sourceMappingURL=index.js.map
591
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bot.ts","../src/config.ts","../src/slash-commands/index.ts","../src/tools/telegram-read.ts","../src/tools/telegram-send.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Logger } from '@wrongstack/core';\n\n// ---------------------------------------------------------------------------\n// Telegram Bot API types (subset used by this plugin)\n// ---------------------------------------------------------------------------\n\ninterface TgUser {\n id: number;\n is_bot: boolean;\n first_name: string;\n username?: string;\n}\n\ninterface TgChat {\n id: number;\n type: 'private' | 'group' | 'supergroup' | 'channel';\n title?: string;\n username?: string;\n}\n\ninterface TgMessage {\n message_id: number;\n from?: TgUser;\n chat: TgChat;\n date: number;\n text?: string;\n}\n\ninterface TgUpdate {\n update_id: number;\n message?: TgMessage;\n edited_message?: TgMessage;\n}\n\ninterface TgResponse<T> {\n ok: boolean;\n result?: T;\n description?: string;\n error_code?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Incoming message shape emitted as a custom event\n// ---------------------------------------------------------------------------\n\nexport interface TelegramIncomingMessage {\n messageId: number;\n chatId: number;\n chatType: string;\n userId?: number;\n userName?: string;\n text: string;\n timestamp: number;\n}\n\n// ---------------------------------------------------------------------------\n// Bot options\n// ---------------------------------------------------------------------------\n\nexport interface TelegramBotOptions {\n token: string;\n pollIntervalSec: number;\n allowedUsers: Set<string>;\n allowedChats: Set<string>;\n /** Max messages to buffer for the agent to read. Default: 50. */\n bufferSize: number;\n log: Logger;\n /** Called for each incoming message that passes allowlist checks. */\n onMessage(msg: TelegramIncomingMessage): void;\n}\n\n// ---------------------------------------------------------------------------\n// Bot\n// ---------------------------------------------------------------------------\n\nexport class TelegramBot {\n private readonly token: string;\n private readonly baseUrl: string;\n private readonly pollIntervalMs: number;\n private readonly allowedUsers: Set<string>;\n private readonly allowedChats: Set<string>;\n private readonly log: Logger;\n private readonly onMessage: (msg: TelegramIncomingMessage) => void;\n private readonly controller = new AbortController();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private pollActive = false;\n private offset = 0;\n private _startedAt: number | null = null;\n\n // Circular buffer for incoming messages\n private readonly bufferMax: number;\n private readonly buffer: TelegramIncomingMessage[] = [];\n\n constructor(opts: TelegramBotOptions) {\n this.token = opts.token;\n this.baseUrl = `https://api.telegram.org/bot${opts.token}`;\n this.pollIntervalMs = opts.pollIntervalSec * 1000;\n this.allowedUsers = opts.allowedUsers;\n this.allowedChats = opts.allowedChats;\n this.bufferMax = opts.bufferSize;\n this.log = opts.log;\n this.onMessage = opts.onMessage;\n }\n\n // ------------------------------------------------------------------\n // Lifecycle\n // ------------------------------------------------------------------\n\n /** Start polling for updates. Idempotent. */\n start(): void {\n if (this.pollActive) return;\n this.pollActive = true;\n this._startedAt = Date.now();\n this.log.info('Telegram bot polling started');\n this.schedulePoll();\n }\n\n /** Stop polling and cancel all in-flight requests. */\n stop(): void {\n this.pollActive = false;\n this.controller.abort();\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n this.log.info('Telegram bot stopped');\n }\n\n get startedAt(): number | null {\n return this._startedAt;\n }\n\n get running(): boolean {\n return this.pollActive;\n }\n\n // ------------------------------------------------------------------\n // Buffer — incoming messages the agent can read\n // ------------------------------------------------------------------\n\n /** Return buffered messages, newest first. Optionally filter by chat. */\n getMessages(opts?: { chatId?: string | number; limit?: number }): TelegramIncomingMessage[] {\n let msgs = [...this.buffer].reverse();\n if (opts?.chatId) {\n const cid = String(opts.chatId);\n msgs = msgs.filter((m) => String(m.chatId) === cid);\n }\n const limit = opts?.limit ?? 20;\n return msgs.slice(0, limit);\n }\n\n /** Drop messages older than the given message ID from the buffer. */\n acknowledge(lastMessageId: number): number {\n const before = this.buffer.length;\n let i = this.buffer.length;\n while (i-- > 0) {\n if (this.buffer[i]!.messageId <= lastMessageId) {\n this.buffer.splice(0, i + 1);\n break;\n }\n }\n return before - this.buffer.length;\n }\n\n get bufferCount(): number {\n return this.buffer.length;\n }\n\n // ------------------------------------------------------------------\n // Outgoing — send a message\n // ------------------------------------------------------------------\n\n async sendMessage(chatId: string | number, text: string): Promise<TgResponse<TgMessage>> {\n const url = `${this.baseUrl}/sendMessage`;\n const body = JSON.stringify({\n chat_id: String(chatId),\n text,\n parse_mode: 'HTML',\n disable_web_page_preview: true,\n });\n\n this.log.debug(`Sending Telegram message to ${chatId} (${text.length} chars)`);\n\n let lastErr: unknown;\n for (let attempt = 1; attempt <= 3; attempt++) {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n signal: AbortSignal.timeout(10_000),\n });\n const data = (await res.json()) as TgResponse<TgMessage>;\n if (!data.ok) {\n throw new Error(`Telegram API error ${data.error_code}: ${data.description}`);\n }\n return data;\n } catch (err) {\n lastErr = err;\n if (attempt < 3) {\n this.log.warn(`Telegram sendMessage attempt ${attempt} failed, retrying in 1s...`);\n await sleep(1000);\n }\n }\n }\n throw lastErr;\n }\n\n // ------------------------------------------------------------------\n // Health\n // ------------------------------------------------------------------\n\n async health(): Promise<{ ok: boolean; username?: string; error?: string }> {\n try {\n const url = `${this.baseUrl}/getMe`;\n const res = await fetch(url, { signal: AbortSignal.timeout(5000) });\n const data = (await res.json()) as TgResponse<TgUser>;\n if (!data.ok || !data.result) {\n return { ok: false, error: data.description ?? 'Unknown error' };\n }\n return { ok: true, username: data.result.username };\n } catch (err) {\n return { ok: false, error: (err as Error).message };\n }\n }\n\n // ------------------------------------------------------------------\n // Polling\n // ------------------------------------------------------------------\n\n private schedulePoll(): void {\n if (!this.pollActive) return;\n this.pollTimer = setTimeout(() => {\n void this.poll().finally(() => this.schedulePoll());\n }, this.pollIntervalMs);\n }\n\n private async poll(): Promise<void> {\n try {\n const url = `${this.baseUrl}/getUpdates?offset=${this.offset}&timeout=10`;\n const res = await fetch(url, { signal: this.controller.signal });\n const data = (await res.json()) as TgResponse<TgUpdate[]>;\n\n if (!data.ok) {\n this.log.warn(`Telegram getUpdates failed: ${data.description}`);\n return;\n }\n\n const updates = data.result ?? [];\n for (const upd of updates) {\n this.offset = upd.update_id + 1;\n const raw = upd.message ?? upd.edited_message;\n if (!raw?.text) continue;\n const msg = { ...raw, text: raw.text };\n this.processMessage(msg);\n }\n } catch (err) {\n if ((err as Error).name === 'AbortError') return;\n this.log.warn(`Telegram poll error: ${(err as Error).message}`);\n }\n }\n\n private processMessage(msg: TgMessage & { text: string }): void {\n const chatId = String(msg.chat.id);\n const userId = msg.from ? String(msg.from.id) : undefined;\n\n // Allowlist checks\n if (this.allowedUsers.size > 0 && userId && !this.allowedUsers.has(userId)) {\n this.log.debug(`Ignoring message from user ${userId} (not in allowedUsers)`);\n void this.sendMessage(chatId, '⛔ You are not authorized to interact with this bot.');\n return;\n }\n if (this.allowedChats.size > 0 && !this.allowedChats.has(chatId)) {\n this.log.debug(`Ignoring message from chat ${chatId} (not in allowedChats)`);\n return;\n }\n\n const incoming: TelegramIncomingMessage = {\n messageId: msg.message_id,\n chatId: msg.chat.id,\n chatType: msg.chat.type,\n userId: msg.from?.id,\n userName: msg.from?.username ?? msg.from?.first_name,\n text: msg.text,\n timestamp: msg.date * 1000,\n };\n\n // Push to circular buffer\n this.buffer.push(incoming);\n while (this.buffer.length > this.bufferMax) this.buffer.shift();\n\n this.onMessage(incoming);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/**\n * Truncate text to fit Telegram's 4096-char message limit.\n * Splits on a newline when possible; otherwise hard-cuts with \"…\" suffix.\n */\nexport function truncateForTelegram(text: string, maxLen = 4000): string {\n if (text.length <= maxLen) return text;\n const cut = text.lastIndexOf('\\n', maxLen - 20);\n const idx = cut > maxLen / 2 ? cut : maxLen - 20;\n return `${text.slice(0, idx)}\\n\\n…[truncated ${text.length - idx} chars]`;\n}\n\n/**\n * Escape HTML special chars for Telegram's HTML parse mode.\n */\nexport function escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n","import type { PluginAPI } from '@wrongstack/core';\n\nexport const PLUGIN_NAME = 'telegram';\n\nexport interface TelegramPluginConfig {\n /** Telegram Bot API token (from @BotFather). */\n botToken: string;\n /**\n * Default chat ID for outgoing notifications.\n * The agent's `telegram_send` tool can override per-call.\n */\n notifyChatId?: string | number;\n /**\n * List of user/chat IDs allowed to interact with the bot.\n * Empty = allow all. Recommended to set in production.\n */\n allowedUsers?: Array<string | number>;\n /**\n * List of group/chat IDs the bot is allowed to read from.\n * Empty = allow all. Narrow this to prevent noise.\n */\n allowedChats?: Array<string | number>;\n /** Polling interval in seconds (default: 2). */\n pollIntervalSec?: number;\n /** Notify on Telegram when a session ends. */\n notifyOnSessionEnd?: boolean;\n /** Notify when a tool runs longer than this threshold (ms). Set 0 to disable. */\n longToolThresholdMs?: number;\n /** Maximum message length for Telegram (Telegram caps at 4096). */\n maxMessageLength?: number;\n}\n\nexport const DEFAULT_CONFIG: Required<Omit<TelegramPluginConfig, 'botToken' | 'notifyChatId'>> = {\n allowedUsers: [],\n allowedChats: [],\n pollIntervalSec: 2,\n notifyOnSessionEnd: false,\n longToolThresholdMs: 30_000,\n maxMessageLength: 4000,\n};\n\nexport const telegramConfigSchema = {\n type: 'object',\n properties: {\n botToken: { type: 'string', description: 'Telegram Bot API token from @BotFather' },\n notifyChatId: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Default chat ID for outgoing notifications',\n },\n allowedUsers: {\n type: 'array',\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\n description: 'User IDs allowed to interact with the bot',\n },\n allowedChats: {\n type: 'array',\n items: { oneOf: [{ type: 'string' }, { type: 'integer' }] },\n description: 'Chat IDs the bot is allowed to read from',\n },\n pollIntervalSec: {\n type: 'integer',\n minimum: 1,\n maximum: 60,\n description: 'Polling interval in seconds',\n },\n notifyOnSessionEnd: { type: 'boolean' },\n longToolThresholdMs: { type: 'integer', minimum: 0 },\n maxMessageLength: { type: 'integer', minimum: 100, maximum: 4096 },\n },\n required: ['botToken'],\n};\n\nexport function readTelegramConfig(\n api: Pick<PluginAPI, 'config'>,\n): Required<Omit<TelegramPluginConfig, 'notifyChatId'>> & Pick<TelegramPluginConfig, 'notifyChatId'> {\n const raw = (api.config as unknown as Record<string, unknown>).plugins as\n | Record<string, unknown>\n | undefined;\n const opts = (raw?.[PLUGIN_NAME] ?? {}) as TelegramPluginConfig;\n return {\n ...DEFAULT_CONFIG,\n ...opts,\n };\n}\n","import type { PluginAPI, SlashCommand } from '@wrongstack/core';\nimport type { TelegramBot } from '../bot.js';\nimport type { TelegramPluginConfig } from '../config.js';\n\n// ---------------------------------------------------------------------------\n// /tg status\n// ---------------------------------------------------------------------------\n\nexport function tgStatusCommand(bot: TelegramBot, cfg: TelegramPluginConfig): SlashCommand {\n return {\n name: 'status',\n aliases: ['tgstat', 'tgs'],\n description: 'Show Telegram bot connection status and config',\n help: `Usage: /tg status\n\nShows whether the bot is connected, its username, polling interval,\nallowlist status, and notification settings.`,\n async run(_args, _ctx) {\n const health = await bot.health();\n const lines = [\n '═══ Telegram Plugin Status ═══',\n '',\n `Bot: ${health.ok ? `✅ @${health.username ?? 'connected'}` : `❌ ${health.error ?? 'offline'}`}`,\n `Running: ${bot.running ? 'yes' : 'no'}`,\n `Started: ${bot.startedAt ? new Date(bot.startedAt).toLocaleTimeString() : 'N/A'}`,\n `Poll: every ${cfg.pollIntervalSec ?? 2}s`,\n `Allowed: ${(cfg.allowedUsers?.length ?? 0) > 0 ? `${cfg.allowedUsers!.length} users` : 'everyone (users)'} / ${(cfg.allowedChats?.length ?? 0) > 0 ? `${cfg.allowedChats!.length} chats` : 'everyone (chats)'}`,\n `Notify: sessionEnd=${cfg.notifyOnSessionEnd ?? false}, longTool=${cfg.longToolThresholdMs ? `${cfg.longToolThresholdMs}ms` : 'off'}`,\n ];\n\n return { message: lines.join('\\n') };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// /tg send\n// ---------------------------------------------------------------------------\n\nexport function tgSendCommand(\n bot: TelegramBot,\n defaultChatId: string | number | undefined,\n): SlashCommand {\n return {\n name: 'send',\n description: 'Send a message to a Telegram chat',\n help: `Usage: /tg send [chat_id] <message>\n\nSend a message to a Telegram chat.\n- First argument (optional): chat or user ID. Uses notifyChatId from config when omitted.\n- Everything else: the message text.\n\nExamples:\n /tg send 123456789 Build completed successfully ✓\n /tg send Deploy finished — check staging`,\n async run(args, _ctx) {\n if (!args.trim()) {\n return { message: 'Usage: /tg send [chat_id] <message>' };\n }\n\n let chatId: string | number;\n let text: string;\n\n // First token might be a numeric chat_id\n const parts = args.trim().split(/\\s+/);\n const maybeId = parts[0];\n if (/^\\d+$/.test(maybeId!) && parts.length > 1) {\n chatId = maybeId!;\n text = parts.slice(1).join(' ');\n } else if (defaultChatId) {\n chatId = defaultChatId;\n text = args.trim();\n } else {\n return {\n message:\n 'No chat_id provided and no default notifyChatId configured.\\nUsage: /tg send <chat_id> <message>',\n };\n }\n\n try {\n const res = await bot.sendMessage(chatId, text);\n return {\n message: `✅ Message sent to ${chatId} (msg_id=${res.result?.message_id ?? '?'})`,\n };\n } catch (err) {\n return { message: `❌ Failed to send: ${(err as Error).message}` };\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// /tg chatid\n// ---------------------------------------------------------------------------\n\nexport function tgChatIdCommand(defaultChatId?: string | number): SlashCommand {\n const chatIdStr = defaultChatId ? String(defaultChatId) : null;\n return {\n name: 'chatid',\n description: 'Show the configured default chat ID',\n help: `Usage: /tg chatid\n\nShows the current default notifyChatId used for notifications\nand the \\`telegram_send\\` tool when no chat_id is specified.`,\n async run(_args, _ctx) {\n if (chatIdStr) {\n return { message: `Configured notifyChatId: ${chatIdStr}` };\n }\n return { message: 'No notifyChatId configured. Set it in the plugin config or pass chat_id explicitly to telegram_send.' };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Register all\n// ---------------------------------------------------------------------------\n\nexport function registerSlashCommands(\n api: PluginAPI,\n bot: TelegramBot,\n cfg: TelegramPluginConfig,\n): string[] {\n const cmds = [\n tgStatusCommand(bot, cfg),\n tgSendCommand(bot, cfg.notifyChatId),\n tgChatIdCommand(cfg.notifyChatId),\n ];\n for (const cmd of cmds) api.slashCommands.register(cmd);\n return cmds.map((c) => c.name);\n}\n","import type { Tool } from '@wrongstack/core';\nimport type { TelegramBot } from '../bot.js';\n\ninterface TelegramReadInput {\n /** Filter to messages from a specific chat/user ID. Omit to see all chats. */\n chat_id?: string | number;\n /** Max messages to return (default: 10, max: 50). */\n limit?: number;\n /**\n * If a message_id is provided, acknowledge all messages up to and\n * including this ID (mark them as processed / remove from buffer).\n */\n ack_last?: number;\n}\n\nexport function makeTelegramReadTool(opts: {\n bot: TelegramBot;\n}): Tool<TelegramReadInput> {\n return {\n name: 'telegram_read',\n description:\n 'Read incoming Telegram messages from the bot. Returns recent messages the bot received, newest first. Use this to check if anyone sent instructions, questions, or feedback via Telegram. After processing messages, pass the last message_id to ack_last to clear them from the inbox.',\n usageHint: 'telegram_read(chat_id: \"123456789\", limit: 5)',\n category: 'Telegram',\n inputSchema: {\n type: 'object',\n properties: {\n chat_id: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Read messages only from this chat/user.',\n },\n limit: {\n type: 'integer',\n minimum: 1,\n maximum: 50,\n description: 'Max messages to return (default: 10).',\n },\n ack_last: {\n type: 'integer',\n description:\n 'After processing messages, pass the highest message_id to clear them from the buffer.',\n },\n },\n },\n permission: 'auto',\n mutating: false,\n timeoutMs: 5_000,\n async execute(input) {\n const msgs = opts.bot.getMessages({\n chatId: input.chat_id,\n limit: input.limit ?? 10,\n });\n\n let acked = 0;\n if (input.ack_last !== undefined && input.ack_last > 0) {\n acked = opts.bot.acknowledge(input.ack_last);\n }\n\n return {\n buffer_total: opts.bot.bufferCount,\n messages: msgs.map((m) => ({\n message_id: m.messageId,\n chat_id: m.chatId,\n chat_type: m.chatType,\n from: m.userName ?? `user_${m.userId ?? 'unknown'}`,\n text: m.text,\n ts: new Date(m.timestamp).toISOString(),\n })),\n acked,\n hint: acked > 0\n ? undefined\n : 'Use ack_last with the highest message_id to clear processed messages.',\n };\n },\n };\n}\n","import type { Tool } from '@wrongstack/core';\nimport type { Logger } from '@wrongstack/core';\nimport type { TelegramBot } from '../bot.js';\nimport { escapeHtml, truncateForTelegram } from '../bot.js';\n\ninterface TelegramSendInput {\n /** Chat or user ID to send the message to. Falls back to config.notifyChatId when omitted. */\n chat_id?: string | number;\n /** Message text. Supports Telegram HTML parse mode. */\n message: string;\n}\n\nexport function makeTelegramSendTool(opts: {\n bot: TelegramBot;\n defaultChatId?: string | number;\n maxMessageLength: number;\n log: Logger;\n}): Tool<TelegramSendInput> {\n return {\n name: 'telegram_send',\n description:\n 'Send a message via Telegram to a specified chat. Use this to notify users, report results, or communicate through Telegram. The message supports HTML formatting (bold, italic, code, links).',\n usageHint: 'telegram_send(chat_id: \"123456789\", message: \"Task completed ✓\")',\n category: 'Telegram',\n inputSchema: {\n type: 'object',\n properties: {\n chat_id: {\n oneOf: [{ type: 'string' }, { type: 'integer' }],\n description: 'Target chat or user ID. Uses the plugin default when omitted.',\n },\n message: {\n type: 'string',\n description:\n 'Message text (supports HTML: <b>bold</b>, <i>italic</i>, <code>mono</code>, <a href=\"...\">links</a>).',\n },\n },\n required: ['message'],\n },\n permission: 'confirm',\n mutating: true,\n timeoutMs: 15_000,\n async execute(input, _ctx, _opts) {\n const chatId = input.chat_id ?? opts.defaultChatId;\n if (!chatId) {\n throw new Error(\n 'No chat_id provided and no default notifyChatId configured. Set notifyChatId in plugin config or pass chat_id.',\n );\n }\n\n // Format: wrap the message in a code block if it looks like raw output\n const safeMsg = escapeHtml(input.message);\n const truncated = truncateForTelegram(safeMsg, opts.maxMessageLength);\n\n opts.log.info(`telegram_send → chat_id=${chatId} (${truncated.length} chars)`);\n\n const res = await opts.bot.sendMessage(chatId, truncated);\n\n return {\n ok: res.ok,\n message_id: res.result?.message_id,\n chat: res.result?.chat\n ? {\n id: res.result.chat.id,\n type: res.result.chat.type,\n title: res.result.chat.title,\n }\n : undefined,\n };\n },\n };\n}\n","import type { Plugin } from '@wrongstack/core';\nimport type { Logger } from '@wrongstack/core';\nimport { TelegramBot } from './bot.js';\nimport type { TelegramIncomingMessage } from './bot.js';\nimport { truncateForTelegram, escapeHtml } from './bot.js';\nimport { PLUGIN_NAME, readTelegramConfig, telegramConfigSchema } from './config.js';\nimport { registerSlashCommands } from './slash-commands/index.js';\nimport { makeTelegramReadTool } from './tools/telegram-read.js';\nimport { makeTelegramSendTool } from './tools/telegram-send.js';\n\n// ---------------------------------------------------------------------------\n// Teardown state\n// ---------------------------------------------------------------------------\n\nlet teardownState: {\n offs: Array<() => void>;\n toolNames: string[];\n commandNames: string[];\n bot: TelegramBot;\n} | null = null;\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nconst plugin: Plugin = {\n name: PLUGIN_NAME,\n version: '0.1.0',\n description: 'Telegram bridge — send/receive messages, get agent notifications.',\n apiVersion: '^0.1.10',\n capabilities: {\n tools: true,\n slashCommands: true,\n pipelines: [],\n },\n configSchema: telegramConfigSchema,\n defaultConfig: {\n pollIntervalSec: 2,\n notifyOnSessionEnd: false,\n longToolThresholdMs: 30_000,\n maxMessageLength: 4000,\n },\n\n async setup(api) {\n const cfg = readTelegramConfig(api);\n const log = api.log;\n\n log.info('Starting Telegram plugin...');\n\n // ---- Bot ----\n const bot = new TelegramBot({\n token: cfg.botToken,\n pollIntervalSec: cfg.pollIntervalSec,\n allowedUsers: new Set((cfg.allowedUsers ?? []).map(String)),\n allowedChats: new Set((cfg.allowedChats ?? []).map(String)),\n bufferSize: 50,\n log,\n onMessage(msg: TelegramIncomingMessage) {\n // Emit custom event so other plugins or the host can react.\n // The TUI can subscribe and surface it (future hook).\n api.emitCustom('telegram:message_received', msg);\n\n // Log it for the user in the TUI\n const who = msg.userName ?? msg.userId ?? 'unknown';\n log.info(`📨 Telegram: ${who} (chat=${msg.chatId}): ${msg.text.slice(0, 200)}`);\n },\n });\n\n // ---- Register tools ----\n const sendTool = makeTelegramSendTool({\n bot,\n defaultChatId: cfg.notifyChatId,\n maxMessageLength: cfg.maxMessageLength,\n log,\n });\n const readTool = makeTelegramReadTool({ bot });\n api.tools.register(sendTool);\n api.tools.register(readTool);\n\n // ---- Event subscriptions ----\n const offs: Array<() => void> = [];\n\n // System prompt contributor — inject unread Telegram messages\n const unregisterPrompt = api.registerSystemPromptContributor(async () => {\n const msgs = bot.getMessages({ limit: 5 });\n if (msgs.length === 0) return [];\n\n const blocks: Array<{ type: 'text'; text: string }> = [\n {\n type: 'text',\n text: [\n '## Telegram Inbox',\n `You have ${bot.bufferCount} unread Telegram message(s).`,\n 'Read them with `telegram_read` and reply with `telegram_send`.',\n '',\n 'Recent messages:',\n ...msgs.map((m) => {\n const who = m.userName ?? `user_${m.userId ?? 'unknown'}`;\n const ts = new Date(m.timestamp).toLocaleTimeString();\n return `- [${ts}] **${who}** (chat=${m.chatId}): ${m.text.slice(0, 200)}`;\n }),\n '',\n ].join('\\n'),\n },\n ];\n return blocks;\n });\n offs.push(unregisterPrompt);\n\n // Register slash commands\n const commandNames = registerSlashCommands(api, bot, cfg);\n\n // Notify on session end\n if (cfg.notifyOnSessionEnd && cfg.notifyChatId) {\n offs.push(\n api.events.on('session.ended', (event) => {\n const inputTokens = event.usage.input ?? 0;\n const outputTokens = event.usage.output ?? 0;\n const totalTokens = inputTokens + outputTokens;\n const msg = [\n '✅ <b>Session ended</b>',\n '',\n `Session: <code>${event.id.slice(0, 8)}</code>`,\n `Input: ${inputTokens} tokens`,\n `Output: ${outputTokens} tokens`,\n `Total: ${totalTokens} tokens`,\n ].join('\\n');\n\n void bot.sendMessage(cfg.notifyChatId!, msg).catch((err) => {\n log.warn(`Failed to send session end notification: ${(err as Error).message}`);\n });\n }),\n );\n }\n\n // Notify for long-running tools\n if (cfg.longToolThresholdMs && cfg.longToolThresholdMs > 0 && cfg.notifyChatId) {\n offs.push(\n api.events.on('tool.executed', (event) => {\n if (event.durationMs < cfg.longToolThresholdMs!) return;\n const sec = (event.durationMs / 1000).toFixed(1);\n const status = event.ok ? '✅' : '❌';\n const preview = event.output\n ? truncateForTelegram(escapeHtml(event.output), 500)\n : '(no output)';\n\n const msg = [\n `${status} <b>${escapeHtml(event.name)}</b> completed in ${sec}s`,\n '',\n `<pre>${preview}</pre>`,\n ].join('\\n');\n\n void bot.sendMessage(cfg.notifyChatId!, msg).catch((err) => {\n log.warn(`Failed to send tool notification: ${(err as Error).message}`);\n });\n }),\n );\n }\n\n // ---- Start polling ----\n bot.start();\n\n teardownState = { offs, toolNames: [sendTool.name, readTool.name], commandNames, bot };\n\n log.info('Telegram plugin ready');\n },\n\n async teardown(api) {\n const state = teardownState;\n if (!state) return;\n teardownState = null;\n\n state.bot.stop();\n for (const off of state.offs) off();\n for (const name of state.toolNames) api.tools.unregister(name);\n for (const name of state.commandNames) {\n api.slashCommands.unregister(`${PLUGIN_NAME}:${name}`);\n }\n\n api.log.info('Telegram plugin torn down');\n },\n\n async health() {\n const state = teardownState;\n if (!state?.bot) return { ok: false, message: 'Plugin not initialized' };\n const h = await state.bot.health();\n return h;\n },\n};\n\nexport default plugin;\n\n// Re-export the types consumers may want\nexport type { TelegramIncomingMessage } from './bot.js';\nexport type { TelegramPluginConfig } from './config.js';\n"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@wrongstack/telegram",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "description": "WrongStack plugin — Telegram bridge: send messages, receive prompts, get notified.",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/WrongStack/WrongStack.git",
9
+ "directory": "packages/telegram"
10
+ },
11
+ "homepage": "https://github.com/WrongStack/WrongStack#readme",
12
+ "bugs": "https://github.com/WrongStack/WrongStack/issues",
13
+ "author": "ECOSTACK TECHNOLOGY OU",
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "peerDependencies": {
28
+ "@wrongstack/core": "0.3.1"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.19.19",
32
+ "tsup": "^8.5.1",
33
+ "typescript": "^5.9.3",
34
+ "@wrongstack/core": "0.3.1"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "typecheck": "tsc --noEmit",
42
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
43
+ }
44
+ }