@llblab/pi-telegram 0.10.6 → 0.10.8

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/AGENTS.md CHANGED
@@ -57,6 +57,7 @@
57
57
  - The bridge is session-local, paired to one allowed Telegram user, and owns a local queue aligned with π lifecycle hooks
58
58
  - Queue admission is explicit and validated: immediate commands, control lane, priority lane, and default lane must preserve allowed kind/lane pairings
59
59
  - Dispatch is gated by active turns, pending dispatch, unsettled control work, compaction, `ctx.isIdle()`, and π pending messages; dispatched prompts remain queued until `agent_start` consumes them
60
+ - Telegram `/compact` owns a native `typing` keepalive for the compaction window so phone clients show activity between the started/completed notices; stop it on both completion and failure
60
61
  - `/stop`, `/abort`, `/next`, and `/continue` have distinct contracts: reset queue and abort; abort while preserving queue; force next queued turn; enqueue a priority `continue` prompt
61
62
  - `/start`, `/help`, and `/status` open the unified command-help/status-row/control menu; `/model`, `/thinking`, and `/queue` jump to sections directly; visible bot commands are `/start`, `/compact`, `/next`, `/continue`, `/abort`, `/stop`
62
63
  - Command/menu emoji are fixed UI adornments owned by the `commands` map; do not add a persisted emoji toggle or Settings menu until there is a real setting to own
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.8: Compact Typing Timing Hotfix
4
+
5
+ - `[Compaction]` Telegram `/compact` now starts the native `typing` chat-action keepalive after the "Compaction started" notice is sent, then stops it on completion or failure. Impact: operators see the same Telegram activity indicator during context compression that they already see during normal agent/tool work, without showing `typing` before the explicit start confirmation arrives.
6
+
7
+ ## 0.10.7: Stale Context Hardening Hotfix
8
+
9
+ - `[Session Reloads]` Context-sensitive command, pairing, queue, session-start, and update-dispatch paths now ignore only stale-session/stale-context failures instead of swallowing broad runtime errors. Impact: the bridge survives ctx replacement/fork/reload races while real bugs still surface for diagnostics.
10
+ - `[Runtime Status]` Restored status update error propagation so existing polling/dispatch safety wrappers can record stale status failures as structured runtime events instead of losing diagnostics inside the status domain.
11
+ - `[Release]` Added a tag-triggered GitHub Actions release workflow that verifies the `vX.Y.Z` tag matches `package.json`, extracts the matching `CHANGELOG.md` section, and publishes a GitHub Release automatically.
12
+ - `[Tests]` Added focused regressions proving the newly guarded call sites tolerate stale context errors and still rethrow unrelated failures.
13
+
3
14
  ## 0.10.6: Native Typing Keepalive Hotfix
4
15
 
5
16
  - `[Typing]` Telegram native `typing` chat actions now refresh every 2.5s instead of every 4s. Impact: the bot's Telegram-side typing animation has more headroom to stay visible during model retries, transient model/API errors, and other long-running agent work.
package/README.md CHANGED
@@ -85,7 +85,7 @@ What it feels like:
85
85
  Use these inside the Telegram DM with your bot. The main entrypoint is `/start`: it opens the operator menu and exposes many of the important agent controls that normally live in the CLI, adapted for Telegram.
86
86
 
87
87
  - **`/start`**: Pair the first Telegram user when needed, register bot commands, and open the inline application menu with command help, prompt-template commands, status rows, model controls, thinking controls, settings, and queue controls.
88
- - **`/compact`**: Start session compaction when the session is idle.
88
+ - **`/compact`**: Start session compaction when the session is idle; Telegram shows the native typing indicator while compaction is running.
89
89
  - **`/next`**: Dispatch the next queued turn, aborting π first if needed.
90
90
  - **`/continue`**: Enqueue a priority `continue` prompt.
91
91
  - **`/abort`**: Abort the active run without touching the queue.
@@ -114,7 +114,7 @@ Dispatch is gated by:
114
114
 
115
115
  This prevents queue races around rapid follow-ups, `/compact`, and mixed local plus Telegram activity. Post-agent-end dispatch retries are scheduled through a session-bound deferred dispatcher that activates on session start, cancels timers on session shutdown, and skips callbacks from older generations before they touch `ExtensionContext`. Telegram `/start` and hidden compatibility shortcuts `/status`, `/model`, `/thinking`, `/queue`, and `/settings` execute immediately; the dispatch controller still serializes any deferred control items so a queued control action must settle before the next queued action can dispatch.
116
116
 
117
- `/start` opens the main application menu: visible command help, compact command-only prompt-template rows when π exposes Telegram-compatible prompt-template names, status rows (`Status`, `Usage`, `Cost`, `Context`), and top-level buttons for model, thinking, and queue sections. The `Status` row reports `compacting` while a Telegram `/compact` run is active. The Queue button includes the current queued-item count. Hidden compatibility shortcuts `/help`, `/status`, `/model`, `/thinking`, and `/queue` jump directly to their corresponding menu screens, while `/settings` opens the hidden settings menu for bridge toggles such as proactive push. Settings options open detail submenus; checkbox-like settings use Back plus green/black/yellow On and Off controls instead of mutating directly from the list. Command emoji come from the `commands` domain map so visible command descriptions and matching menu buttons share one fixed adornment source. Prompt-template commands use a fixed `🧩` marker, map π template names to Telegram-safe aliases such as `fix-tests` → `/fix_tests`, stay visible only inside the `/start` menu, and expand before queueing because `ExtensionAPI.sendUserMessage()` intentionally bypasses π prompt-template expansion for extension-originated messages. Every submenu starts with a top Back row so navigation stays anchored near the original user message above the inline keyboard; model-menu pagination controls sit near the top, tapping the pagination indicator opens a compact page picker headed by `<b>Choose a page:</b>`, and tapping a model opens a detail submenu with Back, ☑️ Activate/🟢 Active selection, and yellow/black-marked Scoped/All membership tabs. `menu-model` owns model-menu state, scoped model pages, model detail rendering, scoped-list persistence planning, and model-menu rendering while `model` owns core model identity/switching semantics. `menu-thinking` owns thinking-menu text, reply markup, callback handling, and message rendering. `menu-status` owns status-menu payloads, status callback handling, and status-message rendering. `menu-queue` owns queue-menu UI only: queue items are rendered under a compact `<b>Queue:</b>` heading, top-to-bottom in dispatch order, numbered, and marked with `⚡` for priority prompts or `📎` for prompts with attachments. An empty queue renders bold message text with the bottom-filled `⌛` hourglass plus the top Main menu button, while non-empty queue states keep the running `⏳` hourglass. Selecting an item opens a submenu that displays the queue item number above the full queued prompt text with Back, side-by-side Priority/Normal tabs, and Cancel. If a callback targets an item that has already left the queue, the menu refreshes the list instead of applying a stale mutation.
117
+ `/start` opens the main application menu: visible command help, compact command-only prompt-template rows when π exposes Telegram-compatible prompt-template names, status rows (`Status`, `Usage`, `Cost`, `Context`), and top-level buttons for model, thinking, and queue sections. The `Status` row reports `compacting` while a Telegram `/compact` run is active, and the bridge sends Telegram's native `typing` chat action as a keepalive for the same compaction window. The Queue button includes the current queued-item count. Hidden compatibility shortcuts `/help`, `/status`, `/model`, `/thinking`, and `/queue` jump directly to their corresponding menu screens, while `/settings` opens the hidden settings menu for bridge toggles such as proactive push. Settings options open detail submenus; checkbox-like settings use Back plus green/black/yellow On and Off controls instead of mutating directly from the list. Command emoji come from the `commands` domain map so visible command descriptions and matching menu buttons share one fixed adornment source. Prompt-template commands use a fixed `🧩` marker, map π template names to Telegram-safe aliases such as `fix-tests` → `/fix_tests`, stay visible only inside the `/start` menu, and expand before queueing because `ExtensionAPI.sendUserMessage()` intentionally bypasses π prompt-template expansion for extension-originated messages. Every submenu starts with a top Back row so navigation stays anchored near the original user message above the inline keyboard; model-menu pagination controls sit near the top, tapping the pagination indicator opens a compact page picker headed by `<b>Choose a page:</b>`, and tapping a model opens a detail submenu with Back, ☑️ Activate/🟢 Active selection, and yellow/black-marked Scoped/All membership tabs. `menu-model` owns model-menu state, scoped model pages, model detail rendering, scoped-list persistence planning, and model-menu rendering while `model` owns core model identity/switching semantics. `menu-thinking` owns thinking-menu text, reply markup, callback handling, and message rendering. `menu-status` owns status-menu payloads, status callback handling, and status-message rendering. `menu-queue` owns queue-menu UI only: queue items are rendered under a compact `<b>Queue:</b>` heading, top-to-bottom in dispatch order, numbered, and marked with `⚡` for priority prompts or `📎` for prompts with attachments. An empty queue renders bold message text with the bottom-filled `⌛` hourglass plus the top Main menu button, while non-empty queue states keep the running `⏳` hourglass. Selecting an item opens a submenu that displays the queue item number above the full queued prompt text with Back, side-by-side Priority/Normal tabs, and Cancel. If a callback targets an item that has already left the queue, the menu refreshes the list instead of applying a stale mutation.
118
118
 
119
119
  ### Abort Behavior
120
120
 
package/index.ts CHANGED
@@ -354,6 +354,8 @@ export default function (pi: Pi.ExtensionAPI) {
354
354
  dispatchNextQueuedTelegramTurn,
355
355
  requestDeferredDispatchNextQueuedTelegramTurn:
356
356
  deferredQueueDispatchRuntime.request,
357
+ startTypingLoop: promptDispatchRuntime.startTypingLoop,
358
+ stopTypingLoop: typing.stop,
357
359
  answerCallbackQuery,
358
360
  editInteractiveMessage,
359
361
  sendInteractiveMessage,
package/lib/commands.ts CHANGED
@@ -320,6 +320,8 @@ export interface TelegramCompactCommandDeps extends TelegramRuntimeEventRecorder
320
320
  requestDeferredDispatchNextQueuedTelegramTurn?: (
321
321
  dispatch: () => void,
322
322
  ) => void;
323
+ startTypingLoop?: () => void;
324
+ stopTypingLoop?: () => void;
323
325
  compact: (callbacks: {
324
326
  onComplete: () => void;
325
327
  onError: (error: unknown) => void;
@@ -553,6 +555,8 @@ export interface TelegramCommandRuntimeDeps<
553
555
  requestDeferredDispatchNextQueuedTelegramTurn?: (
554
556
  dispatch: (ctx: TContext) => void,
555
557
  ) => void;
558
+ startTypingLoop?: (ctx: TContext, chatId?: number) => void;
559
+ stopTypingLoop?: () => void;
556
560
  enqueueContinueTurn: (message: TMessage, ctx: TContext) => Promise<void>;
557
561
  compact: (
558
562
  ctx: TContext,
@@ -579,7 +583,7 @@ export interface TelegramCommandRuntimeDeps<
579
583
  }
580
584
 
581
585
  export const TELEGRAM_APP_MENU_INTRO_HTML = [
582
- "<b>π Telegram bridge</b>",
586
+ "<b>π Telegram</b>",
583
587
  "",
584
588
  `${formatTelegramCommandEmojiPrefix("start")}/start — Open menu / Pair bridge`,
585
589
  `${formatTelegramCommandEmojiPrefix("compact")}/compact — Compact current session`,
@@ -798,15 +802,20 @@ export async function handleTelegramCompactCommand(
798
802
  }
799
803
  deps.setCompactionInProgress(true);
800
804
  deps.updateStatus();
805
+ let compactionStillInProgress = true;
801
806
  try {
802
807
  deps.compact({
803
808
  onComplete: () => {
809
+ compactionStillInProgress = false;
810
+ deps.stopTypingLoop?.();
804
811
  deps.setCompactionInProgress(false);
805
812
  deps.updateStatus();
806
813
  dispatchNextQueuedTelegramTurnAfterCompact(deps);
807
814
  void deps.sendTextReply("Compaction completed.");
808
815
  },
809
816
  onError: (error) => {
817
+ compactionStillInProgress = false;
818
+ deps.stopTypingLoop?.();
810
819
  deps.setCompactionInProgress(false);
811
820
  deps.updateStatus();
812
821
  dispatchNextQueuedTelegramTurnAfterCompact(deps);
@@ -816,6 +825,8 @@ export async function handleTelegramCompactCommand(
816
825
  },
817
826
  });
818
827
  } catch (error) {
828
+ compactionStillInProgress = false;
829
+ deps.stopTypingLoop?.();
819
830
  deps.setCompactionInProgress(false);
820
831
  deps.updateStatus();
821
832
  deps.recordRuntimeEvent?.("compact", error);
@@ -824,20 +835,37 @@ export async function handleTelegramCompactCommand(
824
835
  return;
825
836
  }
826
837
  await deps.sendTextReply("Compaction started.");
838
+ if (compactionStillInProgress) deps.startTypingLoop?.();
839
+ }
840
+
841
+ function isTelegramStaleContextError(error: unknown): boolean {
842
+ return (
843
+ error instanceof Error &&
844
+ (error.message.includes("stale after session") ||
845
+ error.message.includes("stale ctx"))
846
+ );
827
847
  }
828
848
 
829
849
  export async function handleTelegramStatusCommand<TContext>(deps: {
830
850
  ctx: TContext;
831
851
  showStatus: (ctx: TContext) => Promise<void>;
832
852
  }): Promise<void> {
833
- await deps.showStatus(deps.ctx);
853
+ try {
854
+ await deps.showStatus(deps.ctx);
855
+ } catch (error) {
856
+ if (!isTelegramStaleContextError(error)) throw error;
857
+ }
834
858
  }
835
859
 
836
860
  export async function handleTelegramModelCommand<TContext>(deps: {
837
861
  ctx: TContext;
838
862
  openModelMenu: (ctx: TContext) => Promise<void>;
839
863
  }): Promise<void> {
840
- await deps.openModelMenu(deps.ctx);
864
+ try {
865
+ await deps.openModelMenu(deps.ctx);
866
+ } catch (error) {
867
+ if (!isTelegramStaleContextError(error)) throw error;
868
+ }
841
869
  }
842
870
 
843
871
  export async function executeTelegramCommandAction<TMessage, TContext>(
@@ -946,6 +974,8 @@ export function createTelegramCommandHandlerTargetRuntime<
946
974
  setCompactionInProgress: deps.setCompactionInProgress,
947
975
  updateStatus: deps.updateStatus,
948
976
  dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
977
+ startTypingLoop: deps.startTypingLoop,
978
+ stopTypingLoop: deps.stopTypingLoop,
949
979
  enqueueContinueTurn: deps.enqueueContinueTurn,
950
980
  compact: deps.compact,
951
981
  enqueueControlItem: commandTargetRuntime.enqueueControlItem,
@@ -1099,6 +1129,10 @@ async function handleTelegramCommandRuntime<
1099
1129
  )
1100
1130
  : undefined,
1101
1131
  compact: (callbacks) => deps.compact(commandCtx, callbacks),
1132
+ startTypingLoop: deps.startTypingLoop
1133
+ ? () => deps.startTypingLoop?.(commandCtx, nextMessage.chat.id)
1134
+ : undefined,
1135
+ stopTypingLoop: deps.stopTypingLoop,
1102
1136
  sendTextReply: sendReplyFor(nextMessage),
1103
1137
  recordRuntimeEvent: deps.recordRuntimeEvent,
1104
1138
  });
package/lib/config.ts CHANGED
@@ -185,6 +185,14 @@ export function getTelegramAuthorizationState(
185
185
  return { kind: "deny" };
186
186
  }
187
187
 
188
+ function isTelegramStaleContextError(error: unknown): boolean {
189
+ return (
190
+ error instanceof Error &&
191
+ (error.message.includes("stale after session") ||
192
+ error.message.includes("stale ctx"))
193
+ );
194
+ }
195
+
188
196
  export async function pairTelegramUserIfNeeded<TContext>(
189
197
  userId: number,
190
198
  deps: TelegramUserPairingDeps<TContext>,
@@ -196,7 +204,11 @@ export async function pairTelegramUserIfNeeded<TContext>(
196
204
  if (authorization.kind !== "pair") return false;
197
205
  deps.setAllowedUserId(authorization.userId);
198
206
  await deps.persistConfig();
199
- deps.updateStatus(deps.ctx);
207
+ try {
208
+ deps.updateStatus(deps.ctx);
209
+ } catch (error) {
210
+ if (!isTelegramStaleContextError(error)) throw error;
211
+ }
200
212
  return true;
201
213
  }
202
214
 
package/lib/queue.ts CHANGED
@@ -1289,6 +1289,14 @@ export interface TelegramPromptEnqueueController<TMessage, TContext = unknown> {
1289
1289
  enqueue: (messages: TMessage[], ctx: TContext) => Promise<void>;
1290
1290
  }
1291
1291
 
1292
+ function isTelegramStaleContextError(error: unknown): boolean {
1293
+ return (
1294
+ error instanceof Error &&
1295
+ (error.message.includes("stale after session") ||
1296
+ error.message.includes("stale ctx"))
1297
+ );
1298
+ }
1299
+
1292
1300
  export function buildTelegramSessionStartState<TModel = unknown>(
1293
1301
  currentModel: TModel | undefined,
1294
1302
  ): TelegramSessionStartState<TModel> {
@@ -1326,7 +1334,11 @@ export async function startTelegramSessionRuntime<TContext, TModel = unknown>(
1326
1334
  await deps.loadConfig();
1327
1335
  deps.applyState(buildTelegramSessionStartState(deps.currentModel));
1328
1336
  await deps.prepareTempDir();
1329
- deps.bindDeferredDispatchContext?.(deps.ctx);
1337
+ try {
1338
+ deps.bindDeferredDispatchContext?.(deps.ctx);
1339
+ } catch (error) {
1340
+ if (!isTelegramStaleContextError(error)) throw error;
1341
+ }
1330
1342
  deps.updateStatus();
1331
1343
  }
1332
1344
 
@@ -1478,7 +1490,11 @@ export function reorderTelegramQueueItemsRuntime<TContext>(
1478
1490
  deps.setQueuedItems(
1479
1491
  [...deps.getQueuedItems()].sort(compareTelegramQueueItems),
1480
1492
  );
1481
- deps.updateStatus(deps.ctx);
1493
+ try {
1494
+ deps.updateStatus(deps.ctx);
1495
+ } catch (error) {
1496
+ if (!isTelegramStaleContextError(error)) throw error;
1497
+ }
1482
1498
  }
1483
1499
 
1484
1500
  export function clearTelegramQueueItemsRuntime<TContext>(
@@ -1487,7 +1503,11 @@ export function clearTelegramQueueItemsRuntime<TContext>(
1487
1503
  const removedCount = deps.getQueuedItems().length;
1488
1504
  if (removedCount === 0) return 0;
1489
1505
  deps.setQueuedItems([]);
1490
- deps.updateStatus(deps.ctx);
1506
+ try {
1507
+ deps.updateStatus(deps.ctx);
1508
+ } catch (error) {
1509
+ if (!isTelegramStaleContextError(error)) throw error;
1510
+ }
1491
1511
  return removedCount;
1492
1512
  }
1493
1513
 
@@ -1501,7 +1521,11 @@ export function removeTelegramQueueItemsByMessageIdsRuntime<TContext>(
1501
1521
  );
1502
1522
  if (removedCount === 0) return 0;
1503
1523
  deps.setQueuedItems(items);
1504
- deps.updateStatus(deps.ctx);
1524
+ try {
1525
+ deps.updateStatus(deps.ctx);
1526
+ } catch (error) {
1527
+ if (!isTelegramStaleContextError(error)) throw error;
1528
+ }
1505
1529
  return removedCount;
1506
1530
  }
1507
1531
 
package/lib/routing.ts CHANGED
@@ -82,6 +82,8 @@ export interface TelegramInboundRouteRuntimeDeps<
82
82
  requestDeferredDispatchNextQueuedTelegramTurn?: (
83
83
  dispatch: (ctx: TContext) => void,
84
84
  ) => void;
85
+ startTypingLoop?: (ctx: TContext, chatId?: number) => void;
86
+ stopTypingLoop?: () => void;
85
87
  answerCallbackQuery: (
86
88
  callbackQueryId: string,
87
89
  text?: string,
@@ -344,6 +346,8 @@ export function createTelegramInboundRouteRuntime<
344
346
  dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
345
347
  requestDeferredDispatchNextQueuedTelegramTurn:
346
348
  deps.requestDeferredDispatchNextQueuedTelegramTurn,
349
+ startTypingLoop: deps.startTypingLoop,
350
+ stopTypingLoop: deps.stopTypingLoop,
347
351
  enqueueContinueTurn,
348
352
  compact: deps.compact,
349
353
  allocateItemOrder: deps.bridgeRuntime.queue.allocateItemOrder,
package/lib/updates.ts CHANGED
@@ -746,6 +746,14 @@ export async function handleAuthorizedTelegramReactionUpdate<TContext>(
746
746
  );
747
747
  }
748
748
 
749
+ function isTelegramStaleContextError(error: unknown): boolean {
750
+ return (
751
+ error instanceof Error &&
752
+ (error.message.includes("stale after session") ||
753
+ error.message.includes("stale ctx"))
754
+ );
755
+ }
756
+
749
757
  export async function executeTelegramUpdatePlan<
750
758
  TContext = unknown,
751
759
  TReactionUpdate extends TelegramMessageReactionUpdated =
@@ -761,81 +769,85 @@ export async function executeTelegramUpdatePlan<
761
769
  TMessage
762
770
  >,
763
771
  ): Promise<void> {
764
- if (plan.kind === "ignore") return;
765
- if (plan.kind === "deleted") {
766
- deps.removePendingMediaGroupMessages(plan.messageIds);
767
- deps.removeQueuedTelegramTurnsByMessageIds(plan.messageIds, deps.ctx);
768
- return;
769
- }
770
- if (plan.kind === "reaction") {
771
- await deps.handleAuthorizedTelegramReactionUpdate(
772
- plan.reactionUpdate,
773
- deps.ctx,
774
- );
775
- return;
776
- }
777
- if (plan.kind === "callback") {
778
- if (plan.shouldPair) {
779
- await deps.pairTelegramUserIfNeeded(plan.query.from.id, deps.ctx);
780
- }
781
- if (plan.shouldDeny) {
782
- const callbackQueryId = getTelegramCallbackQueryId(plan.query);
783
- if (callbackQueryId) {
784
- await deps.answerCallbackQuery(
785
- callbackQueryId,
786
- "This bot is not authorized for your account.",
787
- );
788
- }
772
+ try {
773
+ if (plan.kind === "ignore") return;
774
+ if (plan.kind === "deleted") {
775
+ deps.removePendingMediaGroupMessages(plan.messageIds);
776
+ deps.removeQueuedTelegramTurnsByMessageIds(plan.messageIds, deps.ctx);
789
777
  return;
790
778
  }
791
- await deps.handleAuthorizedTelegramCallbackQuery(plan.query, deps.ctx);
792
- return;
793
- }
794
- if (plan.kind === "guest") {
795
- if (plan.shouldDeny) {
796
- await deps.answerGuestQuery(
797
- plan.guestMessage.guest_query_id,
798
- "Access denied.",
779
+ if (plan.kind === "reaction") {
780
+ await deps.handleAuthorizedTelegramReactionUpdate(
781
+ plan.reactionUpdate,
782
+ deps.ctx,
799
783
  );
800
784
  return;
801
785
  }
802
- if (deps.handleAuthorizedTelegramGuestMessage) {
803
- await deps.handleAuthorizedTelegramGuestMessage(
804
- plan.guestMessage,
805
- deps.ctx,
806
- );
786
+ if (plan.kind === "callback") {
787
+ if (plan.shouldPair) {
788
+ await deps.pairTelegramUserIfNeeded(plan.query.from.id, deps.ctx);
789
+ }
790
+ if (plan.shouldDeny) {
791
+ const callbackQueryId = getTelegramCallbackQueryId(plan.query);
792
+ if (callbackQueryId) {
793
+ await deps.answerCallbackQuery(
794
+ callbackQueryId,
795
+ "This bot is not authorized for your account.",
796
+ );
797
+ }
798
+ return;
799
+ }
800
+ await deps.handleAuthorizedTelegramCallbackQuery(plan.query, deps.ctx);
801
+ return;
807
802
  }
808
- return;
809
- }
810
- const pairedNow = plan.shouldPair
811
- ? await deps.pairTelegramUserIfNeeded(plan.message.from.id, deps.ctx)
812
- : false;
813
- const replyTarget = getTelegramMessageReplyTarget(plan.message);
814
- if (
815
- plan.kind === "message" &&
816
- pairedNow &&
817
- plan.shouldNotifyPaired &&
818
- replyTarget
819
- ) {
820
- await deps.sendTextReply(
821
- replyTarget.chatId,
822
- replyTarget.messageId,
823
- "Telegram bridge paired with this account.",
824
- );
825
- }
826
- if (plan.shouldDeny) {
827
- if (replyTarget) {
803
+ if (plan.kind === "guest") {
804
+ if (plan.shouldDeny) {
805
+ await deps.answerGuestQuery(
806
+ plan.guestMessage.guest_query_id,
807
+ "Access denied.",
808
+ );
809
+ return;
810
+ }
811
+ if (deps.handleAuthorizedTelegramGuestMessage) {
812
+ await deps.handleAuthorizedTelegramGuestMessage(
813
+ plan.guestMessage,
814
+ deps.ctx,
815
+ );
816
+ }
817
+ return;
818
+ }
819
+ const pairedNow = plan.shouldPair
820
+ ? await deps.pairTelegramUserIfNeeded(plan.message.from.id, deps.ctx)
821
+ : false;
822
+ const replyTarget = getTelegramMessageReplyTarget(plan.message);
823
+ if (
824
+ plan.kind === "message" &&
825
+ pairedNow &&
826
+ plan.shouldNotifyPaired &&
827
+ replyTarget
828
+ ) {
828
829
  await deps.sendTextReply(
829
830
  replyTarget.chatId,
830
831
  replyTarget.messageId,
831
- "This bot is not authorized for your account.",
832
+ "Telegram bridge paired with this account.",
832
833
  );
833
834
  }
834
- return;
835
- }
836
- if (plan.kind === "edited-message") {
837
- await deps.handleAuthorizedTelegramEditedMessage(plan.message, deps.ctx);
838
- return;
835
+ if (plan.shouldDeny) {
836
+ if (replyTarget) {
837
+ await deps.sendTextReply(
838
+ replyTarget.chatId,
839
+ replyTarget.messageId,
840
+ "This bot is not authorized for your account.",
841
+ );
842
+ }
843
+ return;
844
+ }
845
+ if (plan.kind === "edited-message") {
846
+ await deps.handleAuthorizedTelegramEditedMessage(plan.message, deps.ctx);
847
+ return;
848
+ }
849
+ await deps.handleAuthorizedTelegramMessage(plan.message, deps.ctx);
850
+ } catch (error) {
851
+ if (!isTelegramStaleContextError(error)) throw error;
839
852
  }
840
- await deps.handleAuthorizedTelegramMessage(plan.message, deps.ctx);
841
853
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.10.6",
3
+ "version": "0.10.8",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"