@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 +1 -0
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/docs/architecture.md +1 -1
- package/index.ts +2 -0
- package/lib/commands.ts +37 -3
- package/lib/config.ts +13 -1
- package/lib/queue.ts +28 -4
- package/lib/routing.ts +4 -0
- package/lib/updates.ts +77 -65
- package/package.json +1 -1
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.
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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 (
|
|
803
|
-
|
|
804
|
-
plan.
|
|
805
|
-
|
|
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
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
"
|
|
832
|
+
"Telegram bridge paired with this account.",
|
|
832
833
|
);
|
|
833
834
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
}
|