@ebowwa/glm-daemon 0.3.2 → 0.4.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.
@@ -0,0 +1,395 @@
1
+ "use strict";
2
+ /**
3
+ * Telegram Channel Adapter
4
+ *
5
+ * Implements ChannelConnector from @ebowwa/channel-types.
6
+ * Telegram bot using node-telegram-bot-api.
7
+ */
8
+ var __extends = (this && this.__extends) || (function () {
9
+ var extendStatics = function (d, b) {
10
+ extendStatics = Object.setPrototypeOf ||
11
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
12
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
13
+ return extendStatics(d, b);
14
+ };
15
+ return function (d, b) {
16
+ if (typeof b !== "function" && b !== null)
17
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
18
+ extendStatics(d, b);
19
+ function __() { this.constructor = d; }
20
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
21
+ };
22
+ })();
23
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
24
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
25
+ return new (P || (P = Promise))(function (resolve, reject) {
26
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
27
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
28
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
29
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
30
+ });
31
+ };
32
+ var __generator = (this && this.__generator) || function (thisArg, body) {
33
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
34
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
35
+ function verb(n) { return function (v) { return step([n, v]); }; }
36
+ function step(op) {
37
+ if (f) throw new TypeError("Generator is already executing.");
38
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
39
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
40
+ if (y = 0, t) op = [op[0] & 2, t.value];
41
+ switch (op[0]) {
42
+ case 0: case 1: t = op; break;
43
+ case 4: _.label++; return { value: op[1], done: false };
44
+ case 5: _.label++; y = op[1]; op = [0]; continue;
45
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
46
+ default:
47
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
48
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
49
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
50
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
51
+ if (t[2]) _.ops.pop();
52
+ _.trys.pop(); continue;
53
+ }
54
+ op = body.call(thisArg, _);
55
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
56
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
57
+ }
58
+ };
59
+ Object.defineProperty(exports, "__esModule", { value: true });
60
+ exports.TelegramChannel = void 0;
61
+ exports.createTelegramChannel = createTelegramChannel;
62
+ exports.createTelegramConfigFromEnv = createTelegramConfigFromEnv;
63
+ var node_telegram_bot_api_1 = require("node-telegram-bot-api");
64
+ var base_js_1 = require("./base.js");
65
+ // ============================================================
66
+ // TELEGRAM CHANNEL
67
+ // ============================================================
68
+ var TelegramChannel = /** @class */ (function (_super) {
69
+ __extends(TelegramChannel, _super);
70
+ function TelegramChannel(config) {
71
+ var _this = _super.call(this, config) || this;
72
+ _this.label = "Telegram";
73
+ _this.capabilities = {
74
+ supports: {
75
+ text: true,
76
+ media: true,
77
+ replies: true,
78
+ threads: false,
79
+ reactions: false,
80
+ editing: true,
81
+ streaming: false,
82
+ },
83
+ media: {
84
+ maxFileSize: 50 * 1024 * 1024, // 50MB
85
+ supportedMimeTypes: ["image/*", "video/*", "audio/*", "application/pdf"],
86
+ },
87
+ rateLimits: {
88
+ messagesPerMinute: 30,
89
+ charactersPerMessage: 4096,
90
+ },
91
+ };
92
+ _this.token = config.botToken;
93
+ _this.testChatId = config.testChatId;
94
+ _this.allowedUsers = config.allowedUsers;
95
+ _this.allowedChats = config.allowedChats;
96
+ _this.id = _this.createId();
97
+ _this.bot = new node_telegram_bot_api_1.default(_this.token, { polling: false }); // Don't start polling yet
98
+ return _this;
99
+ }
100
+ // ============================================================
101
+ // ChannelConnector Implementation
102
+ // ============================================================
103
+ /**
104
+ * Send response to Telegram
105
+ */
106
+ TelegramChannel.prototype.send = function (response) {
107
+ return __awaiter(this, void 0, void 0, function () {
108
+ var chatId;
109
+ return __generator(this, function (_a) {
110
+ switch (_a.label) {
111
+ case 0:
112
+ chatId = this.extractChatId(response.replyTo);
113
+ if (!chatId) {
114
+ console.error("[Telegram] Cannot send: no chat ID in response");
115
+ return [2 /*return*/];
116
+ }
117
+ return [4 /*yield*/, this.sendResponse(chatId, response.content.text)];
118
+ case 1:
119
+ _a.sent();
120
+ return [2 /*return*/];
121
+ }
122
+ });
123
+ });
124
+ };
125
+ // ============================================================
126
+ // Platform-Specific Implementation
127
+ // ============================================================
128
+ /**
129
+ * Start Telegram bot
130
+ */
131
+ TelegramChannel.prototype.startPlatform = function () {
132
+ return __awaiter(this, void 0, void 0, function () {
133
+ var _this = this;
134
+ return __generator(this, function (_a) {
135
+ switch (_a.label) {
136
+ case 0:
137
+ console.log("[Telegram] Starting bot...");
138
+ // Start polling
139
+ this.bot = new node_telegram_bot_api_1.default(this.token, { polling: true });
140
+ // Handle /start command
141
+ this.bot.onText(/\/start/, function (msg) { return __awaiter(_this, void 0, void 0, function () {
142
+ var chatId;
143
+ return __generator(this, function (_a) {
144
+ switch (_a.label) {
145
+ case 0:
146
+ chatId = msg.chat.id;
147
+ return [4 /*yield*/, this.bot.sendMessage(chatId, "👋 Hello! I'm the GLM Daemon Telegram Bot.\n\n" +
148
+ "I can help you with:\n" +
149
+ "- Chat and questions (handled locally)\n" +
150
+ "- Coding tasks and features (delegated to daemon)\n\n" +
151
+ "Just send me a message!")];
152
+ case 1:
153
+ _a.sent();
154
+ return [2 /*return*/];
155
+ }
156
+ });
157
+ }); });
158
+ // Handle all text messages
159
+ this.bot.on("message", function (msg) { return __awaiter(_this, void 0, void 0, function () {
160
+ var chatId, userId, userName, message, response, error_1;
161
+ var _a, _b, _c;
162
+ return __generator(this, function (_d) {
163
+ switch (_d.label) {
164
+ case 0:
165
+ if (!msg.text)
166
+ return [2 /*return*/];
167
+ if (msg.text.startsWith("/"))
168
+ return [2 /*return*/];
169
+ chatId = msg.chat.id;
170
+ userId = (_a = msg.from) === null || _a === void 0 ? void 0 : _a.id;
171
+ // Check allowlist
172
+ if (!this.isAllowed(userId, chatId)) {
173
+ console.log("[Telegram] Blocked message from unauthorized user/chat: ".concat(userId, "/").concat(chatId));
174
+ return [2 /*return*/];
175
+ }
176
+ userName = ((_b = msg.from) === null || _b === void 0 ? void 0 : _b.first_name) || ((_c = msg.from) === null || _c === void 0 ? void 0 : _c.username) || "User";
177
+ console.log("[Telegram] ".concat(userName, ": ").concat(msg.text));
178
+ // Show typing indicator
179
+ return [4 /*yield*/, this.bot.sendChatAction(chatId, "typing")];
180
+ case 1:
181
+ // Show typing indicator
182
+ _d.sent();
183
+ _d.label = 2;
184
+ case 2:
185
+ _d.trys.push([2, 5, , 7]);
186
+ message = this.createChannelMessage(msg);
187
+ return [4 /*yield*/, this.routeChannelMessage(message)];
188
+ case 3:
189
+ response = _d.sent();
190
+ // Send response
191
+ return [4 /*yield*/, this.send(response)];
192
+ case 4:
193
+ // Send response
194
+ _d.sent();
195
+ return [3 /*break*/, 7];
196
+ case 5:
197
+ error_1 = _d.sent();
198
+ console.error("[Telegram] Error handling message:", error_1);
199
+ return [4 /*yield*/, this.bot.sendMessage(chatId, "Sorry, I encountered an error: ".concat(error_1 instanceof Error ? error_1.message : String(error_1)))];
200
+ case 6:
201
+ _d.sent();
202
+ return [3 /*break*/, 7];
203
+ case 7: return [2 /*return*/];
204
+ }
205
+ });
206
+ }); });
207
+ // Handle polling errors
208
+ this.bot.on("polling_error", function (error) {
209
+ console.error("[Telegram] Polling error:", error);
210
+ });
211
+ console.log("[Telegram] Bot is running!");
212
+ if (!this.testChatId) return [3 /*break*/, 2];
213
+ return [4 /*yield*/, this.sendTestMessage(Number(this.testChatId))];
214
+ case 1:
215
+ _a.sent();
216
+ _a.label = 2;
217
+ case 2: return [2 /*return*/];
218
+ }
219
+ });
220
+ });
221
+ };
222
+ /**
223
+ * Stop Telegram bot
224
+ */
225
+ TelegramChannel.prototype.stopPlatform = function () {
226
+ return __awaiter(this, void 0, void 0, function () {
227
+ return __generator(this, function (_a) {
228
+ switch (_a.label) {
229
+ case 0:
230
+ console.log("[Telegram] Stopping bot...");
231
+ return [4 /*yield*/, this.bot.stopPolling()];
232
+ case 1:
233
+ _a.sent();
234
+ return [2 /*return*/];
235
+ }
236
+ });
237
+ });
238
+ };
239
+ // ============================================================
240
+ // Telegram-Specific Helpers
241
+ // ============================================================
242
+ /**
243
+ * Create normalized ChannelMessage from Telegram message
244
+ */
245
+ TelegramChannel.prototype.createChannelMessage = function (msg) {
246
+ var _a, _b, _c, _d, _e, _f;
247
+ var sender = {
248
+ id: ((_b = (_a = msg.from) === null || _a === void 0 ? void 0 : _a.id) === null || _b === void 0 ? void 0 : _b.toString()) || msg.chat.id.toString(),
249
+ username: (_c = msg.from) === null || _c === void 0 ? void 0 : _c.username,
250
+ displayName: ((_d = msg.from) === null || _d === void 0 ? void 0 : _d.first_name) || ((_e = msg.from) === null || _e === void 0 ? void 0 : _e.username),
251
+ isBot: ((_f = msg.from) === null || _f === void 0 ? void 0 : _f.is_bot) || false,
252
+ };
253
+ var context = {
254
+ isDM: msg.chat.type === "private",
255
+ groupName: msg.chat.type !== "private" ? msg.chat.title : undefined,
256
+ };
257
+ return {
258
+ messageId: msg.message_id.toString(),
259
+ channelId: this.id,
260
+ timestamp: new Date(msg.date * 1000),
261
+ sender: sender,
262
+ text: msg.text || "",
263
+ context: context,
264
+ replyTo: msg.reply_to_message
265
+ ? {
266
+ messageId: msg.reply_to_message.message_id.toString(),
267
+ channelId: this.id,
268
+ }
269
+ : undefined,
270
+ };
271
+ };
272
+ /**
273
+ * Check if user/chat is allowed
274
+ */
275
+ TelegramChannel.prototype.isAllowed = function (userId, chatId) {
276
+ var _a, _b, _c, _d;
277
+ // No allowlist configured = allow all
278
+ if (!((_a = this.allowedUsers) === null || _a === void 0 ? void 0 : _a.length) && !((_b = this.allowedChats) === null || _b === void 0 ? void 0 : _b.length)) {
279
+ return true;
280
+ }
281
+ // Check user allowlist
282
+ if (((_c = this.allowedUsers) === null || _c === void 0 ? void 0 : _c.length) && userId) {
283
+ if (this.allowedUsers.includes(userId))
284
+ return true;
285
+ }
286
+ // Check chat allowlist
287
+ if (((_d = this.allowedChats) === null || _d === void 0 ? void 0 : _d.length) && chatId) {
288
+ if (this.allowedChats.includes(chatId))
289
+ return true;
290
+ }
291
+ return false;
292
+ };
293
+ /**
294
+ * Extract chat ID from MessageRef
295
+ */
296
+ TelegramChannel.prototype.extractChatId = function (replyTo) {
297
+ // Chat ID is stored in channelId.accountId for Telegram
298
+ var accountId = replyTo.channelId.accountId;
299
+ // Try to parse as number, fallback to null
300
+ var parsed = parseInt(accountId, 10);
301
+ return isNaN(parsed) ? null : parsed;
302
+ };
303
+ /**
304
+ * Send response, handling chunking for long messages
305
+ */
306
+ TelegramChannel.prototype.sendResponse = function (chatId, response) {
307
+ return __awaiter(this, void 0, void 0, function () {
308
+ var maxLength, chunks, _i, chunks_1, chunk;
309
+ return __generator(this, function (_a) {
310
+ switch (_a.label) {
311
+ case 0:
312
+ maxLength = 4096;
313
+ if (!(response.length > maxLength)) return [3 /*break*/, 5];
314
+ chunks = response.match(/[\s\S]{1,4000}/g) || [];
315
+ _i = 0, chunks_1 = chunks;
316
+ _a.label = 1;
317
+ case 1:
318
+ if (!(_i < chunks_1.length)) return [3 /*break*/, 4];
319
+ chunk = chunks_1[_i];
320
+ return [4 /*yield*/, this.bot.sendMessage(chatId, chunk, { parse_mode: "Markdown" })];
321
+ case 2:
322
+ _a.sent();
323
+ _a.label = 3;
324
+ case 3:
325
+ _i++;
326
+ return [3 /*break*/, 1];
327
+ case 4: return [3 /*break*/, 7];
328
+ case 5: return [4 /*yield*/, this.bot.sendMessage(chatId, response, { parse_mode: "Markdown" })];
329
+ case 6:
330
+ _a.sent();
331
+ _a.label = 7;
332
+ case 7: return [2 /*return*/];
333
+ }
334
+ });
335
+ });
336
+ };
337
+ /**
338
+ * Send test message
339
+ */
340
+ TelegramChannel.prototype.sendTestMessage = function (chatId) {
341
+ return __awaiter(this, void 0, void 0, function () {
342
+ return __generator(this, function (_a) {
343
+ switch (_a.label) {
344
+ case 0: return [4 /*yield*/, this.bot.sendMessage(chatId, "✅ GLM Daemon Telegram Bot is NOW ONLINE!\n\n" +
345
+ "🎉 The bot is working correctly!\n\n" +
346
+ "Commands:\n" +
347
+ "/start - Show welcome message\n" +
348
+ 'Any message - Chat or task\n' +
349
+ '"status" - Check daemon status\n\n' +
350
+ "I'll route your messages appropriately!")];
351
+ case 1:
352
+ _a.sent();
353
+ console.log("[Telegram] Test message sent to chat ".concat(chatId));
354
+ return [2 /*return*/];
355
+ }
356
+ });
357
+ });
358
+ };
359
+ return TelegramChannel;
360
+ }(base_js_1.BaseChannel));
361
+ exports.TelegramChannel = TelegramChannel;
362
+ // ============================================================
363
+ // FACTORY FUNCTION
364
+ // ============================================================
365
+ /**
366
+ * Create a Telegram channel from config
367
+ */
368
+ function createTelegramChannel(config) {
369
+ return new TelegramChannel(config);
370
+ }
371
+ /**
372
+ * Create Telegram channel config from environment (Doppler)
373
+ */
374
+ function createTelegramConfigFromEnv() {
375
+ var _a, _b;
376
+ var token = process.env.TELEGRAM_BOT_TOKEN;
377
+ if (!token)
378
+ return null;
379
+ var allowedUsers = (_a = process.env.TELEGRAM_ALLOWED_USERS) === null || _a === void 0 ? void 0 : _a.split(",").map(function (s) { return parseInt(s.trim(), 10); }).filter(function (n) { return !isNaN(n); });
380
+ var allowedChats = (_b = process.env.TELEGRAM_ALLOWED_CHATS) === null || _b === void 0 ? void 0 : _b.split(",").map(function (s) { return parseInt(s.trim(), 10); }).filter(function (n) { return !isNaN(n); });
381
+ return {
382
+ platform: "telegram",
383
+ accountId: process.env.TELEGRAM_ACCOUNT_ID || "default",
384
+ instanceId: process.env.TELEGRAM_INSTANCE_ID,
385
+ botToken: token,
386
+ testChatId: process.env.TELEGRAM_TEST_CHAT_ID,
387
+ allowedUsers: allowedUsers,
388
+ allowedChats: allowedChats,
389
+ daemonWorkdir: process.env.DAEMON_WORKDIR,
390
+ daemonBaseBranch: process.env.DAEMON_BASE_BRANCH,
391
+ enableDaemonAutoPR: process.env.DAEMON_AUTO_PR === "true",
392
+ enableDaemonAutoCommit: process.env.DAEMON_AUTO_COMMIT === "true",
393
+ butlerStorageDir: process.env.BUTLER_STORAGE_DIR,
394
+ };
395
+ }