@wrongstack/telegram 0.319.0 → 0.319.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bot.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { TelegramBotOptions } from './bot-types.js';
3
3
  import { TelegramInbox } from './inbox.js';
4
4
  import { Poller } from './poller.js';
5
5
  import { TelegramOutbox } from './outbox.js';
6
- export type { TelegramBotOptions, TelegramBotResponse, TelegramIncomingMessage } from './bot-types.js';
6
+ export type { TelegramBotOptions, TelegramBotResponse, TelegramIncomingMessage, } from './bot-types.js';
7
7
  export type { TelegramApprovalRequestInput, TelegramApprovalResult } from './approval-flow.js';
8
8
  export { escapeHtml, truncateForTelegram } from './text-format.js';
9
9
  export declare class TelegramBot {
package/dist/index.js CHANGED
@@ -278,7 +278,8 @@ var ApprovalFlow = class {
278
278
  if (request?.state !== "pending") return false;
279
279
  request.state = state;
280
280
  clearTimeout(request.timer);
281
- if (request.signal && request.abortHandler) request.signal.removeEventListener("abort", request.abortHandler);
281
+ if (request.signal && request.abortHandler)
282
+ request.signal.removeEventListener("abort", request.abortHandler);
282
283
  const leftover = request.pendingCallbacks.splice(0);
283
284
  for (const cq of leftover) {
284
285
  const notice = state === "expired" ? "Approval request expired" : state === "cancelled" ? "Approval request cancelled" : "Approval request settled";
@@ -298,7 +299,9 @@ var ApprovalFlow = class {
298
299
  const denialReason = this.inboundDenialReason(userId, chatId);
299
300
  if (denialReason) {
300
301
  const identity = denialReason === "user" ? userId ?? "unknown" : chatId ?? "unknown";
301
- this.log.warn(`Ignoring callback_query from non-allowlisted ${denialReason} ${identity} (data="${key}") \u2014 possible hijack attempt.`);
302
+ this.log.warn(
303
+ `Ignoring callback_query from non-allowlisted ${denialReason} ${identity} (data="${key}") \u2014 possible hijack attempt.`
304
+ );
302
305
  await this.answerCallback(cq.id, "\u26D4 Not authorized", true);
303
306
  return;
304
307
  }
@@ -320,37 +323,68 @@ var ApprovalFlow = class {
320
323
  const chatType = cq.message?.chat.type;
321
324
  const wrongIdentity = userId === void 0 || chatId !== request.expectedChatId || !request.expectedUserIds.has(userId) || messageId !== request.promptMessageId || chatType !== "private" && !request.allowGroup;
322
325
  if (wrongIdentity) {
323
- this.log.warn(`Ignoring callback_query that does not match approval request ${request.requestId} in session ${request.sessionId}.`);
326
+ this.log.warn(
327
+ `Ignoring callback_query that does not match approval request ${request.requestId} in session ${request.sessionId}.`
328
+ );
324
329
  await this.answerCallback(cq.id, "\u26D4 Not authorized for this approval", true);
325
330
  return;
326
331
  }
327
332
  const approved = action[2] === "yes";
328
333
  const fromUser = cq.from?.username ?? cq.from?.first_name ?? `user:${userId}`;
329
- const resolved = this.settleApproval(requestId, "resolved", { approved, fromUser, fromUserId: cq.from?.id });
330
- await this.answerCallback(cq.id, resolved ? approved ? "Approved \u2713" : "Denied \u2717" : "Approval request unavailable", !resolved);
334
+ const resolved = this.settleApproval(requestId, "resolved", {
335
+ approved,
336
+ fromUser,
337
+ fromUserId: cq.from?.id
338
+ });
339
+ await this.answerCallback(
340
+ cq.id,
341
+ resolved ? approved ? "Approved \u2713" : "Denied \u2717" : "Approval request unavailable",
342
+ !resolved
343
+ );
331
344
  }
332
345
  async answerCallback(callbackQueryId, text, showAlert) {
333
346
  try {
334
- await this.api().answerCallbackQuery(callbackQueryId, text, showAlert, { signal: AbortSignal.timeout(5e3) });
347
+ await this.api().answerCallbackQuery(callbackQueryId, text, showAlert, {
348
+ signal: AbortSignal.timeout(5e3)
349
+ });
335
350
  } catch (err) {
336
351
  this.log.debug(`answerCallbackQuery failed: ${err.message}`);
337
352
  }
338
353
  }
339
354
  awaitApproval(input) {
340
- if (input.expectedUserIds.length === 0) throw new Error("Telegram approval requires at least one expected user ID.");
341
- if (this.callbackWaiters.has(input.requestId)) throw new Error(`Telegram approval request ${input.requestId} is already pending.`);
355
+ if (input.expectedUserIds.length === 0)
356
+ throw new Error("Telegram approval requires at least one expected user ID.");
357
+ if (this.callbackWaiters.has(input.requestId))
358
+ throw new Error(`Telegram approval request ${input.requestId} is already pending.`);
342
359
  return new Promise((resolve) => {
343
360
  const delayMs = Math.max(0, input.expiresAt - Date.now());
344
361
  const timer = setTimeout(() => {
345
362
  this.settleApproval(input.requestId, "expired", { approved: false, fromUser: "timeout" });
346
363
  }, delayMs);
347
- const request = { requestId: input.requestId, sessionId: input.sessionId, expectedChatId: String(input.expectedChatId), expectedUserIds: new Set(input.expectedUserIds.map(String)), allowGroup: input.allowGroup, pendingCallbacks: [], expiresAt: input.expiresAt, state: "pending", resolve, timer, signal: input.signal };
348
- if (input.signal) request.abortHandler = () => {
349
- this.settleApproval(input.requestId, "cancelled", { approved: false, fromUser: "aborted" });
364
+ const request = {
365
+ requestId: input.requestId,
366
+ sessionId: input.sessionId,
367
+ expectedChatId: String(input.expectedChatId),
368
+ expectedUserIds: new Set(input.expectedUserIds.map(String)),
369
+ allowGroup: input.allowGroup,
370
+ pendingCallbacks: [],
371
+ expiresAt: input.expiresAt,
372
+ state: "pending",
373
+ resolve,
374
+ timer,
375
+ signal: input.signal
350
376
  };
377
+ if (input.signal)
378
+ request.abortHandler = () => {
379
+ this.settleApproval(input.requestId, "cancelled", {
380
+ approved: false,
381
+ fromUser: "aborted"
382
+ });
383
+ };
351
384
  this.callbackWaiters.set(input.requestId, request);
352
385
  if (input.signal?.aborted) request.abortHandler?.();
353
- else if (input.signal && request.abortHandler) input.signal.addEventListener("abort", request.abortHandler, { once: true });
386
+ else if (input.signal && request.abortHandler)
387
+ input.signal.addEventListener("abort", request.abortHandler, { once: true });
354
388
  });
355
389
  }
356
390
  bindApprovalPrompt(requestId, promptMessageId) {
@@ -358,14 +392,20 @@ var ApprovalFlow = class {
358
392
  if (request?.state !== "pending" || request.promptMessageId !== void 0) return false;
359
393
  request.promptMessageId = promptMessageId;
360
394
  const pending = request.pendingCallbacks.splice(0);
361
- for (const callback of pending) void this.dispatchCallback(callback).catch((err) => this.log.debug(`Callback dispatch failed: ${err instanceof Error ? err.message : String(err)}`));
395
+ for (const callback of pending)
396
+ void this.dispatchCallback(callback).catch(
397
+ (err) => this.log.debug(
398
+ `Callback dispatch failed: ${err instanceof Error ? err.message : String(err)}`
399
+ )
400
+ );
362
401
  return true;
363
402
  }
364
403
  cancelApproval(requestId, fromUser = "cancelled") {
365
404
  return this.settleApproval(requestId, "cancelled", { approved: false, fromUser });
366
405
  }
367
406
  cancelAll(fromUser) {
368
- for (const requestId of Array.from(this.callbackWaiters.keys())) this.settleApproval(requestId, "cancelled", { approved: false, fromUser });
407
+ for (const requestId of Array.from(this.callbackWaiters.keys()))
408
+ this.settleApproval(requestId, "cancelled", { approved: false, fromUser });
369
409
  }
370
410
  };
371
411
 
@@ -431,7 +471,9 @@ var TelegramInbox = class {
431
471
  const userId = msg.from ? String(msg.from.id) : void 0;
432
472
  const denialReason = this.denialReason(userId, chatId);
433
473
  if (denialReason === "user") {
434
- this.deps.log.debug(`Ignoring message from user ${userId ?? "unknown"} (not in allowedUsers)`);
474
+ this.deps.log.debug(
475
+ `Ignoring message from user ${userId ?? "unknown"} (not in allowedUsers)`
476
+ );
435
477
  void this.deps.sendNotice(chatId, "\u26D4 You are not authorized to interact with this bot.").catch(
436
478
  (err) => this.deps.log.debug(
437
479
  `Failed to send denial notice: ${err instanceof Error ? err.message : String(err)}`
@@ -526,7 +568,9 @@ var Poller = class _Poller {
526
568
  if (this.lock && !this.lock.tryAcquire()) {
527
569
  if (!this.standbyAnnounced) {
528
570
  this.standbyAnnounced = true;
529
- this.log.info("Telegram: another wstack instance is already polling this bot token \u2014 standing by; will take over when it stops.");
571
+ this.log.info(
572
+ "Telegram: another wstack instance is already polling this bot token \u2014 standing by; will take over when it stops."
573
+ );
530
574
  }
531
575
  this.standbyTimer = setTimeout(() => this.acquireAndPoll(), this.standbyRetryMs);
532
576
  this.standbyTimer.unref?.();
@@ -544,7 +588,9 @@ var Poller = class _Poller {
544
588
  clearTimeout(this.pollTimer);
545
589
  this.pollTimer = null;
546
590
  }
547
- this.log.warn("Telegram: poll lock lost to another instance \u2014 pausing polling and standing by.");
591
+ this.log.warn(
592
+ "Telegram: poll lock lost to another instance \u2014 pausing polling and standing by."
593
+ );
548
594
  this.standbyAnnounced = true;
549
595
  this.standbyTimer = setTimeout(() => this.acquireAndPoll(), this.standbyRetryMs);
550
596
  this.standbyTimer.unref?.();
@@ -559,7 +605,12 @@ var Poller = class _Poller {
559
605
  }
560
606
  async poll() {
561
607
  try {
562
- const updates = await this.api().getUpdates({ offset: this.offset, timeoutSeconds: 10, deadlineMs: 15e3, signal: this.controller.signal });
608
+ const updates = await this.api().getUpdates({
609
+ offset: this.offset,
610
+ timeoutSeconds: 10,
611
+ deadlineMs: 15e3,
612
+ signal: this.controller.signal
613
+ });
563
614
  this._conflictStreak = 0;
564
615
  for (const upd of updates) {
565
616
  if (upd.update_id < this.offset) continue;
@@ -577,7 +628,9 @@ var Poller = class _Poller {
577
628
  this.onMessageUpdate(raw);
578
629
  this.offset = upd.update_id + 1;
579
630
  } catch (err) {
580
- this.log.debug(`Telegram processMessage failed: ${err instanceof Error ? err.message : String(err)}`);
631
+ this.log.debug(
632
+ `Telegram processMessage failed: ${err instanceof Error ? err.message : String(err)}`
633
+ );
581
634
  break;
582
635
  }
583
636
  }
@@ -586,7 +639,10 @@ var Poller = class _Poller {
586
639
  if (err instanceof TelegramNetworkError && err.aborted) return;
587
640
  if (err instanceof TelegramBotApiError && err.errorCode === 409) {
588
641
  this._conflictStreak++;
589
- if (this._conflictStreak === _Poller.CONFLICT_BACKOFF_AFTER) this.log.warn(this.lock ? "Telegram: another consumer outside this machine is polling this bot token (HTTP 409) \u2014 backing off to 60s polls. Check other machines/bots using this token, or a registered webhook (deleteWebhook)." : "Telegram: another instance is polling this bot token (HTTP 409) \u2014 backing off to 60s polls until it stops.");
642
+ if (this._conflictStreak === _Poller.CONFLICT_BACKOFF_AFTER)
643
+ this.log.warn(
644
+ this.lock ? "Telegram: another consumer outside this machine is polling this bot token (HTTP 409) \u2014 backing off to 60s polls. Check other machines/bots using this token, or a registered webhook (deleteWebhook)." : "Telegram: another instance is polling this bot token (HTTP 409) \u2014 backing off to 60s polls until it stops."
645
+ );
590
646
  this.log.debug(`Telegram getUpdates failed: ${err.description}`);
591
647
  return;
592
648
  }
@@ -764,7 +820,11 @@ var TelegramBot = class {
764
820
  onMessage: opts.onMessage,
765
821
  sendNotice: (chatId, text) => this.sendMessage(chatId, text)
766
822
  });
767
- this.outbox = new TelegramOutbox({ log: this.log, api: () => this.api, getParseMode: opts.getParseMode });
823
+ this.outbox = new TelegramOutbox({
824
+ log: this.log,
825
+ api: () => this.api,
826
+ getParseMode: opts.getParseMode
827
+ });
768
828
  this.approvals = new ApprovalFlow({
769
829
  log: this.log,
770
830
  api: () => this.api,
@@ -779,7 +839,11 @@ var TelegramBot = class {
779
839
  lock: opts.lock,
780
840
  standbyRetryMs: opts.standbyRetryMs ?? 15e3,
781
841
  onCallbackQuery: (cq) => {
782
- void this.approvals.dispatchCallback(cq).catch((err) => this.log.debug(`Callback dispatch failed: ${err instanceof Error ? err.message : String(err)}`));
842
+ void this.approvals.dispatchCallback(cq).catch(
843
+ (err) => this.log.debug(
844
+ `Callback dispatch failed: ${err instanceof Error ? err.message : String(err)}`
845
+ )
846
+ );
783
847
  },
784
848
  onMessageUpdate: (msg) => this.processMessage({ ...msg, text: msg.text })
785
849
  });
@@ -879,7 +943,10 @@ var TELEGRAM_CONFIG_FIELDS = {
879
943
  outboundQueueConcurrency: { lifecycle: "restart" },
880
944
  rateLimitTokensPerSecond: { lifecycle: "hot", description: "Per-chat rate limit (tokens/sec)" },
881
945
  rateLimitBurst: { lifecycle: "hot", description: "Per-chat rate limit burst size" },
882
- parseMode: { lifecycle: "hot", description: "Telegram parse mode: HTML, MarkdownV2, or empty for plain text" }
946
+ parseMode: {
947
+ lifecycle: "hot",
948
+ description: "Telegram parse mode: HTML, MarkdownV2, or empty for plain text"
949
+ }
883
950
  };
884
951
  var telegramConfigSchema = {
885
952
  type: "object",
@@ -1083,11 +1150,7 @@ function formatDelegateCompleted(e) {
1083
1150
  const task = e.task.length > 160 ? `${e.task.slice(0, 159)}\u2026` : e.task;
1084
1151
  const rawBody = e.summary?.trim() || `(no summary) \u2014 ${task}`;
1085
1152
  const body = redactSecrets(rawBody);
1086
- const stats = [
1087
- `\u23F1 ${fmtDuration(e.durationMs)}`,
1088
- `${e.iterations} iter`,
1089
- `${e.toolCalls} tools`
1090
- ];
1153
+ const stats = [`\u23F1 ${fmtDuration(e.durationMs)}`, `${e.iterations} iter`, `${e.toolCalls} tools`];
1091
1154
  if (typeof e.costUsd === "number" && e.costUsd > 0) {
1092
1155
  stats.push(`\u{1F4B2}${e.costUsd.toFixed(4)}`);
1093
1156
  }
@@ -1565,10 +1628,15 @@ _Reply by tapping a button. Auto-denies in ${Math.round(timeoutMs / 1e3)}s._`;
1565
1628
  });
1566
1629
  let promptMessageId;
1567
1630
  try {
1568
- const sent = await opts.bot.sendMessageWithKeyboard(chatId, text, [
1569
- { text: "\u2705 Approve", callback_data: yesKey },
1570
- { text: "\u274C Deny", callback_data: noKey }
1571
- ], toolOpts?.signal);
1631
+ const sent = await opts.bot.sendMessageWithKeyboard(
1632
+ chatId,
1633
+ text,
1634
+ [
1635
+ { text: "\u2705 Approve", callback_data: yesKey },
1636
+ { text: "\u274C Deny", callback_data: noKey }
1637
+ ],
1638
+ toolOpts?.signal
1639
+ );
1572
1640
  promptMessageId = sent.result?.message_id;
1573
1641
  if (promptMessageId === void 0) {
1574
1642
  throw new Error("Telegram approval prompt response did not include a message ID.");
@@ -1959,7 +2027,9 @@ var TelegramNotificationChannel = class {
1959
2027
  return { ok: true, channel: this.name, deliveredAt };
1960
2028
  }
1961
2029
  const res = await this.#bot.sendMessage(this.#chatId, truncated);
1962
- this.#log?.debug?.(`telegram notification delivered (${truncated.length} chars, ok=${res.ok})`);
2030
+ this.#log?.debug?.(
2031
+ `telegram notification delivered (${truncated.length} chars, ok=${res.ok})`
2032
+ );
1963
2033
  return {
1964
2034
  ok: res.ok,
1965
2035
  channel: this.name,
@@ -2348,7 +2418,8 @@ var plugin = {
2348
2418
  level: "info",
2349
2419
  source: "session.end"
2350
2420
  }).then((r) => {
2351
- if (!r.ok) log.warn(`session.ended notification delivery failed: ${r.error ?? "unknown"}`);
2421
+ if (!r.ok)
2422
+ log.warn(`session.ended notification delivery failed: ${r.error ?? "unknown"}`);
2352
2423
  }).catch((err) => {
2353
2424
  log.debug(`session.ended notification delivery threw: ${err.message}`);
2354
2425
  });
@@ -2356,7 +2427,8 @@ var plugin = {
2356
2427
  );
2357
2428
  cleanups.push(
2358
2429
  api.events.on("tool.executed", (event) => {
2359
- if (!runtimeCfg.notifyChatId || !notifyChannel || runtimeCfg.longToolThresholdMs <= 0) return;
2430
+ if (!runtimeCfg.notifyChatId || !notifyChannel || runtimeCfg.longToolThresholdMs <= 0)
2431
+ return;
2360
2432
  if (event.durationMs < runtimeCfg.longToolThresholdMs) return;
2361
2433
  const payload = {
2362
2434
  name: event.name,
@@ -2370,7 +2442,8 @@ var plugin = {
2370
2442
  level: event.ok ? "info" : "warning",
2371
2443
  source: "tool.exec"
2372
2444
  }).then((r) => {
2373
- if (!r.ok) log.warn(`tool.executed notification delivery failed: ${r.error ?? "unknown"}`);
2445
+ if (!r.ok)
2446
+ log.warn(`tool.executed notification delivery failed: ${r.error ?? "unknown"}`);
2374
2447
  }).catch((err) => {
2375
2448
  log.debug(`tool.executed notification delivery threw: ${err.message}`);
2376
2449
  });
@@ -2392,9 +2465,14 @@ var plugin = {
2392
2465
  level: event.ok ? "info" : "warning",
2393
2466
  source: "delegate.completed"
2394
2467
  }).then((r) => {
2395
- if (!r.ok) log.warn(`delegate.completed notification delivery failed: ${r.error ?? "unknown"}`);
2468
+ if (!r.ok)
2469
+ log.warn(
2470
+ `delegate.completed notification delivery failed: ${r.error ?? "unknown"}`
2471
+ );
2396
2472
  }).catch((err) => {
2397
- log.debug(`delegate.completed notification delivery threw: ${err.message}`);
2473
+ log.debug(
2474
+ `delegate.completed notification delivery threw: ${err.message}`
2475
+ );
2398
2476
  });
2399
2477
  })
2400
2478
  );
@@ -2407,36 +2485,66 @@ var plugin = {
2407
2485
  const fresh = telegramFromConfig(next);
2408
2486
  const hotSet = new Set(hotKeys);
2409
2487
  const HOT_APPLIERS = /* @__PURE__ */ new Map([
2410
- ["allowedOutboundChats", (r, f) => {
2411
- r.allowedOutboundChats = f.allowedOutboundChats;
2412
- }],
2413
- ["allowedUsers", (r, f) => {
2414
- r.allowedUserIds = f.allowedUserIds;
2415
- }],
2416
- ["notifyOnSessionEnd", (r, f) => {
2417
- r.notifyOnSessionEnd = f.notifyOnSessionEnd;
2418
- }],
2419
- ["notifyOnDelegate", (r, f) => {
2420
- r.notifyOnDelegate = f.notifyOnDelegate;
2421
- }],
2422
- ["longToolThresholdMs", (r, f) => {
2423
- r.longToolThresholdMs = f.longToolThresholdMs;
2424
- }],
2425
- ["maxMessageLength", (r, f) => {
2426
- r.maxMessageLength = f.maxMessageLength;
2427
- }],
2428
- ["allowGroupApprovals", (r, f) => {
2429
- r.allowGroupApprovals = f.allowGroupApprovals;
2430
- }],
2431
- ["rateLimitTokensPerSecond", (r, f) => {
2432
- r.rateLimitTokensPerSecond = f.rateLimitTokensPerSecond;
2433
- }],
2434
- ["rateLimitBurst", (r, f) => {
2435
- r.rateLimitBurst = f.rateLimitBurst;
2436
- }],
2437
- ["parseMode", (r, f) => {
2438
- r.parseMode = f.parseMode;
2439
- }]
2488
+ [
2489
+ "allowedOutboundChats",
2490
+ (r, f) => {
2491
+ r.allowedOutboundChats = f.allowedOutboundChats;
2492
+ }
2493
+ ],
2494
+ [
2495
+ "allowedUsers",
2496
+ (r, f) => {
2497
+ r.allowedUserIds = f.allowedUserIds;
2498
+ }
2499
+ ],
2500
+ [
2501
+ "notifyOnSessionEnd",
2502
+ (r, f) => {
2503
+ r.notifyOnSessionEnd = f.notifyOnSessionEnd;
2504
+ }
2505
+ ],
2506
+ [
2507
+ "notifyOnDelegate",
2508
+ (r, f) => {
2509
+ r.notifyOnDelegate = f.notifyOnDelegate;
2510
+ }
2511
+ ],
2512
+ [
2513
+ "longToolThresholdMs",
2514
+ (r, f) => {
2515
+ r.longToolThresholdMs = f.longToolThresholdMs;
2516
+ }
2517
+ ],
2518
+ [
2519
+ "maxMessageLength",
2520
+ (r, f) => {
2521
+ r.maxMessageLength = f.maxMessageLength;
2522
+ }
2523
+ ],
2524
+ [
2525
+ "allowGroupApprovals",
2526
+ (r, f) => {
2527
+ r.allowGroupApprovals = f.allowGroupApprovals;
2528
+ }
2529
+ ],
2530
+ [
2531
+ "rateLimitTokensPerSecond",
2532
+ (r, f) => {
2533
+ r.rateLimitTokensPerSecond = f.rateLimitTokensPerSecond;
2534
+ }
2535
+ ],
2536
+ [
2537
+ "rateLimitBurst",
2538
+ (r, f) => {
2539
+ r.rateLimitBurst = f.rateLimitBurst;
2540
+ }
2541
+ ],
2542
+ [
2543
+ "parseMode",
2544
+ (r, f) => {
2545
+ r.parseMode = f.parseMode;
2546
+ }
2547
+ ]
2440
2548
  ]);
2441
2549
  for (const [key, apply] of HOT_APPLIERS) {
2442
2550
  if (hotSet.has(key)) apply(runtimeCfg, fresh);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/telegram",
3
- "version": "0.319.0",
3
+ "version": "0.319.1",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack plugin — Telegram bridge: send messages, receive prompts, get notified.",
6
6
  "repository": {
@@ -26,11 +26,11 @@
26
26
  "!dist/**/*.map"
27
27
  ],
28
28
  "peerDependencies": {
29
- "@wrongstack/core": "0.319.0"
29
+ "@wrongstack/core": "0.319.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^26.2.0",
33
- "@wrongstack/core": "0.319.0",
33
+ "@wrongstack/core": "0.319.1",
34
34
  "typescript": "^7.0.2"
35
35
  },
36
36
  "publishConfig": {