@llblab/pi-telegram 0.49.0 → 0.50.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/lib/menu-queue.ts CHANGED
@@ -31,6 +31,8 @@ type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
31
31
  interface TelegramQueueMenuItem {
32
32
  chatId: number;
33
33
  replyToMessageId: number;
34
+ queueOrder: number;
35
+ isGuest: boolean;
34
36
  queuePosition: number;
35
37
  isPriority: boolean;
36
38
  priorityEmoji?: string;
@@ -60,6 +62,8 @@ function toTelegramQueueMenuItems<Context>(
60
62
  return {
61
63
  chatId: item.chatId,
62
64
  replyToMessageId: item.replyToMessageId,
65
+ queueOrder: item.queueOrder,
66
+ isGuest: item.kind === "prompt" && item.guestQueryId !== undefined,
63
67
  queuePosition: index + 1,
64
68
  isPriority: item.queueLane === "priority",
65
69
  priorityEmoji: item.kind === "prompt" ? item.priorityEmoji : undefined,
@@ -106,7 +110,9 @@ function buildTelegramQueueMenuReplyMarkup(
106
110
  return [
107
111
  {
108
112
  text: label,
109
- callback_data: `queue:pick:${item.chatId}:${item.replyToMessageId}`,
113
+ callback_data: item.isGuest
114
+ ? `queue:guest-pick:${item.queueOrder}`
115
+ : `queue:pick:${item.chatId}:${item.replyToMessageId}`,
110
116
  },
111
117
  ];
112
118
  });
@@ -178,6 +184,22 @@ function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
178
184
  return `${heading}\n${preview}`;
179
185
  }
180
186
 
187
+ function buildTelegramGuestQueueItemSubmenuReplyMarkup(
188
+ queueOrder: number,
189
+ ): TelegramQueueMenuReplyMarkup {
190
+ return {
191
+ inline_keyboard: [
192
+ [{ text: "⬆️ Back", callback_data: "queue:list" }],
193
+ [
194
+ {
195
+ text: "🔴 Skip",
196
+ callback_data: `queue:guest-skip:${queueOrder}`,
197
+ },
198
+ ],
199
+ ],
200
+ };
201
+ }
202
+
181
203
  function buildTelegramQueueItemSubmenuReplyMarkup(
182
204
  chatId: number,
183
205
  replyToMessageId: number,
@@ -217,6 +239,8 @@ interface TelegramQueueMenuCallbackDeps<Context = unknown> {
217
239
  chatId: number,
218
240
  replyToMessageId: number,
219
241
  ) => TelegramQueueMenuItem | undefined;
242
+ findGuestItem: (queueOrder: number) => TelegramQueueMenuItem | undefined;
243
+ skipGuest: (queueOrder: number, ctx: Context) => Promise<boolean>;
220
244
  togglePriority: (chatId: number, replyToMessageId: number) => boolean;
221
245
  setPriority: (
222
246
  chatId: number,
@@ -275,6 +299,38 @@ async function handleTelegramQueueMenuCallback<Context>(
275
299
  );
276
300
  return true;
277
301
  }
302
+ const guestPickMatch = data.match(/^queue:guest-pick:(\d+)$/);
303
+ if (guestPickMatch) {
304
+ await handleTelegramGuestQueueMenuPick(
305
+ callbackQueryId,
306
+ replyChatId,
307
+ replyMessageId,
308
+ Number(guestPickMatch[1]),
309
+ deps,
310
+ );
311
+ return true;
312
+ }
313
+ const guestSkipMatch = data.match(/^queue:guest-skip:(\d+)$/);
314
+ if (guestSkipMatch) {
315
+ const skipped = await deps.skipGuest(Number(guestSkipMatch[1]), ctx);
316
+ if (!skipped) {
317
+ await refreshStaleTelegramQueueMenuItem(
318
+ callbackQueryId,
319
+ replyChatId,
320
+ replyMessageId,
321
+ deps,
322
+ );
323
+ return true;
324
+ }
325
+ await updateTelegramQueueMenuList(
326
+ callbackQueryId,
327
+ replyChatId,
328
+ replyMessageId,
329
+ deps,
330
+ "Guest prompt skipped.",
331
+ );
332
+ return true;
333
+ }
278
334
  const pickMatch = data.match(/^queue:pick:(\d+):(\d+)$/);
279
335
  if (pickMatch) {
280
336
  await handleTelegramQueueMenuPick(
@@ -379,6 +435,31 @@ async function refreshStaleTelegramQueueMenuItem<Context>(
379
435
  );
380
436
  }
381
437
 
438
+ async function handleTelegramGuestQueueMenuPick<Context>(
439
+ callbackQueryId: string,
440
+ replyChatId: number,
441
+ replyMessageId: number,
442
+ queueOrder: number,
443
+ deps: TelegramQueueMenuCallbackDeps<Context>,
444
+ ): Promise<void> {
445
+ const item = deps.findGuestItem(queueOrder);
446
+ if (!item) {
447
+ return refreshStaleTelegramQueueMenuItem(
448
+ callbackQueryId,
449
+ replyChatId,
450
+ replyMessageId,
451
+ deps,
452
+ );
453
+ }
454
+ await deps.updateQueueMessage(
455
+ replyChatId,
456
+ replyMessageId,
457
+ getTelegramQueueMenuItemText(item),
458
+ buildTelegramGuestQueueItemSubmenuReplyMarkup(queueOrder),
459
+ );
460
+ await deps.answerCallbackQuery(callbackQueryId);
461
+ }
462
+
382
463
  async function handleTelegramQueueMenuPick<Context>(
383
464
  callbackQueryId: string,
384
465
  replyChatId: number,
@@ -606,6 +687,7 @@ export function createTelegramQueueMenuRuntime<
606
687
  ctx: Context,
607
688
  ) => Promise<void>;
608
689
  updateStatus: (ctx: Context) => void;
690
+ dismissGuestPlaceholder?: (inlineMessageId: string) => Promise<void>;
609
691
  }): TelegramQueueMenuRuntime<Context> {
610
692
  const sendQueueMenuMessage = createQueueMenuSendMessageAdapter(
611
693
  deps.sendInteractiveMessage,
@@ -628,6 +710,7 @@ export function createTelegramQueueMenuRuntime<
628
710
  updateStatusMessage: deps.updateStatusMessage,
629
711
  answerCallbackQuery: deps.answerCallbackQuery,
630
712
  updateStatus: deps.updateStatus,
713
+ dismissGuestPlaceholder: deps.dismissGuestPlaceholder,
631
714
  }),
632
715
  };
633
716
  }
@@ -695,6 +778,7 @@ function createQueueMenuCallbackHandler<
695
778
  text?: string,
696
779
  ) => Promise<void>;
697
780
  updateStatus: (ctx: Context) => void;
781
+ dismissGuestPlaceholder?: (inlineMessageId: string) => Promise<void>;
698
782
  }) {
699
783
  return async (
700
784
  query: TelegramQueueMenuCallbackQuery,
@@ -737,6 +821,11 @@ function createQueueMenuCallbackHandler<
737
821
  const findItem = (cId: number, rId: number) => {
738
822
  return findTelegramQueueMenuItem(toMenuItems(), cId, rId);
739
823
  };
824
+ const findGuestItem = (queueOrder: number) => {
825
+ return toMenuItems().find((item) => {
826
+ return item.isGuest && item.queueOrder === queueOrder;
827
+ });
828
+ };
740
829
  return handleTelegramQueueMenuCallback(
741
830
  query.id,
742
831
  data,
@@ -746,6 +835,14 @@ function createQueueMenuCallbackHandler<
746
835
  {
747
836
  getQueuedItems: toMenuItems,
748
837
  findItem,
838
+ findGuestItem,
839
+ skipGuest: (queueOrder, callbackContext) => {
840
+ return skipQueuedTelegramGuestPrompt(queueOrder, callbackContext, {
841
+ getQueueSnapshot,
842
+ queueMutationRuntime: deps.queueMutationRuntime,
843
+ dismissGuestPlaceholder: deps.dismissGuestPlaceholder,
844
+ });
845
+ },
749
846
  togglePriority: (cId, rId) => {
750
847
  return toggleQueuedTelegramPromptPriority(cId, rId, ctx, {
751
848
  getQueueSnapshot,
@@ -772,6 +869,34 @@ function createQueueMenuCallbackHandler<
772
869
  };
773
870
  }
774
871
 
872
+ async function skipQueuedTelegramGuestPrompt<Context>(
873
+ queueOrder: number,
874
+ ctx: Context,
875
+ deps: {
876
+ getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
877
+ queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
878
+ dismissGuestPlaceholder?: (inlineMessageId: string) => Promise<void>;
879
+ },
880
+ ): Promise<boolean> {
881
+ const item = deps.getQueueSnapshot().find((candidate) => {
882
+ return candidate.kind === "prompt" &&
883
+ candidate.guestQueryId !== undefined &&
884
+ candidate.queueOrder === queueOrder;
885
+ });
886
+ if (!item || !deps.queueMutationRuntime.removeGuestPromptByQueueOrder) {
887
+ return false;
888
+ }
889
+ const removed = deps.queueMutationRuntime.removeGuestPromptByQueueOrder(
890
+ queueOrder,
891
+ ctx,
892
+ );
893
+ if (!removed) return false;
894
+ if (item.guestInlineMessageId && deps.dismissGuestPlaceholder) {
895
+ await deps.dismissGuestPlaceholder(item.guestInlineMessageId);
896
+ }
897
+ return true;
898
+ }
899
+
775
900
  function getQueueMenuReactionDisposition<Context>(
776
901
  item: Queue.TelegramQueueItem<Context>,
777
902
  priority: boolean,
package/lib/queue.ts CHANGED
@@ -922,6 +922,28 @@ export function removeTelegramQueueItemsByMessageIds<TContext = unknown>(
922
922
  };
923
923
  }
924
924
 
925
+ export function removeTelegramQueuedGuestPromptByOrder<TContext = unknown>(
926
+ items: TelegramQueueItem<TContext>[],
927
+ queueOrder: number,
928
+ ): {
929
+ items: TelegramQueueItem<TContext>[];
930
+ removedItems: PendingTelegramTurn[];
931
+ removedCount: number;
932
+ } {
933
+ const index = items.findIndex((item) => {
934
+ return isPendingTelegramTurn(item) &&
935
+ item.guestQueryId !== undefined &&
936
+ item.queueOrder === queueOrder;
937
+ });
938
+ if (index < 0) return { items, removedItems: [], removedCount: 0 };
939
+ const removedItem = items[index] as PendingTelegramTurn;
940
+ return {
941
+ items: [...items.slice(0, index), ...items.slice(index + 1)],
942
+ removedItems: [removedItem],
943
+ removedCount: 1,
944
+ };
945
+ }
946
+
925
947
  export function applyTelegramQueuePromptReactionDisposition<
926
948
  TContext = unknown,
927
949
  >(
@@ -2331,6 +2353,10 @@ export interface TelegramQueueMutationController<TContext> {
2331
2353
  ctx: TContext,
2332
2354
  scope?: TelegramQueueMessageScope,
2333
2355
  ) => number;
2356
+ removeGuestPromptByQueueOrder?: (
2357
+ queueOrder: number,
2358
+ ctx: TContext,
2359
+ ) => boolean;
2334
2360
  applyReactionByMessageId: (
2335
2361
  messageId: number,
2336
2362
  disposition: TelegramQueueReactionDisposition,
@@ -2604,6 +2630,11 @@ export function createTelegramQueueMutationController<TContext>(
2604
2630
  buildRuntimeDeps(ctx),
2605
2631
  scope,
2606
2632
  ),
2633
+ removeGuestPromptByQueueOrder: (queueOrder, ctx) =>
2634
+ removeTelegramQueuedGuestPromptByOrderRuntime(
2635
+ queueOrder,
2636
+ buildRuntimeDeps(ctx),
2637
+ ),
2607
2638
  applyReactionByMessageId: (messageId, disposition, ctx, scope) =>
2608
2639
  applyTelegramQueuePromptReactionDispositionRuntime(
2609
2640
  messageId,
@@ -2684,6 +2715,21 @@ export function removeTelegramQueueItemsByMessageIdsRuntime<TContext>(
2684
2715
  return removedCount;
2685
2716
  }
2686
2717
 
2718
+ export function removeTelegramQueuedGuestPromptByOrderRuntime<TContext>(
2719
+ queueOrder: number,
2720
+ deps: TelegramQueueMutationRuntimeDeps<TContext>,
2721
+ ): boolean {
2722
+ const { items, removedItems, removedCount } =
2723
+ removeTelegramQueuedGuestPromptByOrder(deps.getQueuedItems(), queueOrder);
2724
+ if (removedCount === 0) return false;
2725
+ // A menu Skip means permanent discard, so settle durable admission before
2726
+ // removing the live item. A failed settlement keeps the prompt retryable.
2727
+ deps.onItemsDiscarded?.(removedItems, deps.ctx);
2728
+ deps.setQueuedItems(items);
2729
+ updateTelegramQueueStatusRuntime(deps);
2730
+ return true;
2731
+ }
2732
+
2687
2733
  export function applyTelegramQueuePromptReactionDispositionRuntime<TContext>(
2688
2734
  messageId: number,
2689
2735
  disposition: TelegramQueueReactionDisposition,
package/lib/replies.ts CHANGED
@@ -1018,6 +1018,10 @@ export const TELEGRAM_GUEST_PLACEHOLDER_FRAMES = [
1018
1018
  "<b>🌏 Working on it...</b>",
1019
1019
  ] as const;
1020
1020
 
1021
+ // Telegram does not expose deletion for inline messages. This non-printing,
1022
+ // non-empty edit visually clears a guest placeholder after its query is skipped.
1023
+ export const TELEGRAM_DISMISSED_GUEST_PLACEHOLDER_TEXT = "\u2063";
1024
+
1021
1025
  export function buildTelegramGuestPlaceholderFrame(step: number): string {
1022
1026
  const frames: readonly string[] = TELEGRAM_GUEST_PLACEHOLDER_FRAMES;
1023
1027
  const index = ((step % frames.length) + frames.length) % frames.length;
@@ -1052,6 +1056,8 @@ export interface TelegramGuestPlaceholderRuntime {
1052
1056
  stop: (inlineMessageId: string) => Promise<void>;
1053
1057
  /** Cancels every loop without waiting for in-flight edits (session shutdown). */
1054
1058
  stopAll: () => void;
1059
+ /** Stops rotation and visually clears the inline placeholder. */
1060
+ dismiss: (inlineMessageId: string) => Promise<void>;
1055
1061
  }
1056
1062
 
1057
1063
  interface TelegramGuestPlaceholderSession {
@@ -1202,5 +1208,18 @@ export function createTelegramGuestPlaceholderRuntime(
1202
1208
  }
1203
1209
  sessions.clear();
1204
1210
  },
1211
+ async dismiss(inlineMessageId) {
1212
+ await stop(inlineMessageId);
1213
+ try {
1214
+ await deps.editGuestInlineMessage(inlineMessageId, {
1215
+ text: TELEGRAM_DISMISSED_GUEST_PLACEHOLDER_TEXT,
1216
+ parseMode: "HTML",
1217
+ });
1218
+ } catch (error) {
1219
+ deps.recordRuntimeEvent?.("guest", error, {
1220
+ phase: "guest-placeholder-dismiss",
1221
+ });
1222
+ }
1223
+ },
1205
1224
  };
1206
1225
  }
package/lib/sync.ts CHANGED
@@ -684,7 +684,14 @@ export async function ensureTelegramLeaderThreadBinding(
684
684
  assertLeaderEpoch("before-workspace-persist");
685
685
  await deps.topicTargetStore.persist();
686
686
  assertLeaderEpoch("after-workspace-persist");
687
- return { ...result, ...(committed.displayTitle ? { displayTitle: committed.displayTitle } : {}) };
687
+ const { target: _target, slot: _slot, threadName: _threadName, displayTitle: _displayTitle, ...rest } = result;
688
+ return {
689
+ ...rest,
690
+ target: { ...committed.target },
691
+ slot: committed.slot ?? result.slot,
692
+ ...(committed.threadName ? { threadName: committed.threadName } : {}),
693
+ ...(committed.displayTitle ? { displayTitle: committed.displayTitle } : {}),
694
+ };
688
695
  };
689
696
  try {
690
697
  const unavailableTargetKeys = new Set([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.49.0",
3
+ "version": "0.50.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"