@llblab/pi-telegram 0.17.5 → 0.18.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.
Files changed (61) hide show
  1. package/AGENTS.md +67 -32
  2. package/BACKLOG.md +59 -19
  3. package/CHANGELOG.md +36 -15
  4. package/README.md +63 -35
  5. package/docs/README.md +3 -1
  6. package/docs/architecture.md +55 -23
  7. package/docs/callback-namespaces.md +1 -1
  8. package/docs/inbound.md +1 -1
  9. package/docs/locks.md +0 -2
  10. package/docs/multi-instance-bus.md +483 -0
  11. package/docs/outbound.md +4 -3
  12. package/docs/public-api.md +12 -10
  13. package/docs/sections.md +2 -2
  14. package/docs/ui-style.md +76 -0
  15. package/index.ts +789 -32
  16. package/lib/bindings.ts +68 -12
  17. package/lib/bus-api.ts +314 -0
  18. package/lib/bus-follower.ts +853 -0
  19. package/lib/bus-leader.ts +915 -0
  20. package/lib/bus.ts +866 -0
  21. package/lib/command-templates.ts +9 -11
  22. package/lib/commands.ts +133 -47
  23. package/lib/config.ts +53 -5
  24. package/lib/lifecycle.ts +23 -7
  25. package/lib/locks.ts +230 -66
  26. package/lib/media.ts +30 -2
  27. package/lib/menu-model.ts +48 -17
  28. package/lib/menu-queue.ts +51 -20
  29. package/lib/menu-settings.ts +9 -5
  30. package/lib/menu-status.ts +3 -0
  31. package/lib/menu-thinking.ts +3 -0
  32. package/lib/menu.ts +67 -26
  33. package/lib/outbound-attachments.ts +102 -17
  34. package/lib/outbound-buttons.ts +6 -2
  35. package/lib/outbound-voice.ts +31 -11
  36. package/lib/outbound.ts +6 -4
  37. package/lib/ownership.ts +119 -0
  38. package/lib/pi.ts +26 -3
  39. package/lib/polling.ts +477 -7
  40. package/lib/preview.ts +141 -88
  41. package/lib/prompt-templates.ts +3 -3
  42. package/lib/prompts.ts +80 -30
  43. package/lib/queue.ts +193 -91
  44. package/lib/rendering.ts +0 -25
  45. package/lib/replies.ts +187 -55
  46. package/lib/routing.ts +1673 -9
  47. package/lib/runtime-log.ts +123 -0
  48. package/lib/runtime.ts +84 -12
  49. package/lib/sections.ts +28 -21
  50. package/lib/setup.ts +1 -1
  51. package/lib/status.ts +532 -9
  52. package/lib/sync.ts +618 -0
  53. package/lib/target.ts +49 -0
  54. package/lib/telegram-api.ts +405 -40
  55. package/lib/text-groups.ts +5 -1
  56. package/lib/thread-reconciler.ts +915 -0
  57. package/lib/threads.ts +2205 -0
  58. package/lib/turns.ts +48 -3
  59. package/lib/updates.ts +355 -32
  60. package/package.json +24 -2
  61. package/docs/telegram-bot-api-rich-messages.md +0 -890
package/lib/menu-model.ts CHANGED
@@ -24,6 +24,7 @@ export type TelegramModelScope = "all" | "scoped";
24
24
 
25
25
  export interface TelegramModelMenuState<TModel extends MenuModel = MenuModel> {
26
26
  chatId: number;
27
+ threadId?: number;
27
28
  messageId: number;
28
29
  page: number;
29
30
  scope: TelegramModelScope;
@@ -91,6 +92,7 @@ export interface TelegramModelMenuRuntimeOptions<
91
92
  TModel extends MenuModel = MenuModel,
92
93
  > {
93
94
  chatId: number;
95
+ threadId?: number;
94
96
  activeModel: TModel | undefined;
95
97
  cachedInputs: CachedTelegramModelMenuInputs<TModel> | undefined;
96
98
  cacheTtlMs: number;
@@ -136,6 +138,7 @@ export interface TelegramMenuMessageRuntimeDeps {
136
138
  text: string,
137
139
  mode: "markdown" | "html" | "plain",
138
140
  replyMarkup: TelegramReplyMarkup,
141
+ options?: { target?: { chatId: number; threadId?: number } },
139
142
  ) => Promise<number | undefined>;
140
143
  }
141
144
 
@@ -178,6 +181,7 @@ export interface BuildTelegramModelMenuStateParams<
178
181
  TModel extends MenuModel = MenuModel,
179
182
  > {
180
183
  chatId: number;
184
+ threadId?: number;
181
185
  activeModel: TModel | undefined;
182
186
  availableModels: TModel[];
183
187
  configuredScopedModelPatterns: string[];
@@ -240,6 +244,7 @@ export interface TelegramModelMenuRuntime<
240
244
  storeState: (state: TelegramModelMenuState<TModel>) => void;
241
245
  getState: (
242
246
  messageId: number | undefined,
247
+ chatId?: number,
243
248
  ) => TelegramModelMenuState<TModel> | undefined;
244
249
  clear: () => void;
245
250
  clearCachedInputs: () => void;
@@ -336,6 +341,9 @@ function sendTelegramMenuMessage(
336
341
  appliedPayload.text,
337
342
  appliedPayload.mode,
338
343
  appliedPayload.replyMarkup,
344
+ state.threadId !== undefined
345
+ ? { target: { chatId: state.chatId, threadId: state.threadId } }
346
+ : undefined,
339
347
  );
340
348
  }
341
349
 
@@ -364,10 +372,14 @@ export function getModelMenuItems<TModel extends MenuModel = MenuModel>(
364
372
  : state.allModels;
365
373
  }
366
374
 
375
+ function getTelegramMenuStateKey(chatId: number, messageId: number): string {
376
+ return `${chatId}:${messageId}`;
377
+ }
378
+
367
379
  export function pruneStoredTelegramModelMenus<
368
380
  TModel extends MenuModel = MenuModel,
369
381
  >(
370
- menus: Map<number, StoredTelegramModelMenuState<TModel>>,
382
+ menus: Map<string, StoredTelegramModelMenuState<TModel>>,
371
383
  options: TelegramModelMenuStoreOptions,
372
384
  ): void {
373
385
  const now = options.now ?? Date.now();
@@ -376,40 +388,51 @@ export function pruneStoredTelegramModelMenus<
376
388
  menus.delete(messageId);
377
389
  }
378
390
  while (menus.size > options.maxStoredMenus) {
379
- const oldestMessageId = menus.keys().next().value as number | undefined;
380
- if (oldestMessageId === undefined) return;
381
- menus.delete(oldestMessageId);
391
+ const oldestKey = menus.keys().next().value as string | undefined;
392
+ if (oldestKey === undefined) return;
393
+ menus.delete(oldestKey);
382
394
  }
383
395
  }
384
396
 
385
397
  export function storeTelegramModelMenuState<
386
398
  TModel extends MenuModel = MenuModel,
387
399
  >(
388
- menus: Map<number, StoredTelegramModelMenuState<TModel>>,
400
+ menus: Map<string, StoredTelegramModelMenuState<TModel>>,
389
401
  state: TelegramModelMenuState<TModel>,
390
402
  options: TelegramModelMenuStoreOptions,
391
403
  ): void {
392
404
  const now = options.now ?? Date.now();
393
405
  pruneStoredTelegramModelMenus(menus, { ...options, now });
394
- menus.set(state.messageId, { state, updatedAt: now });
406
+ menus.set(getTelegramMenuStateKey(state.chatId, state.messageId), {
407
+ state,
408
+ updatedAt: now,
409
+ });
395
410
  pruneStoredTelegramModelMenus(menus, { ...options, now });
396
411
  }
397
412
 
398
413
  export function getStoredTelegramModelMenuState<
399
414
  TModel extends MenuModel = MenuModel,
400
415
  >(
401
- menus: Map<number, StoredTelegramModelMenuState<TModel>>,
416
+ menus: Map<string, StoredTelegramModelMenuState<TModel>>,
402
417
  messageId: number | undefined,
403
418
  options: TelegramModelMenuStoreOptions,
419
+ chatId?: number,
404
420
  ): TelegramModelMenuState<TModel> | undefined {
405
421
  if (messageId === undefined) return undefined;
406
422
  const now = options.now ?? Date.now();
407
423
  pruneStoredTelegramModelMenus(menus, { ...options, now });
408
- const entry = menus.get(messageId);
424
+ const key =
425
+ typeof chatId === "number"
426
+ ? getTelegramMenuStateKey(chatId, messageId)
427
+ : [...menus.keys()].find((candidate) =>
428
+ candidate.endsWith(`:${messageId}`),
429
+ );
430
+ if (!key) return undefined;
431
+ const entry = menus.get(key);
409
432
  if (!entry) return undefined;
410
- menus.delete(messageId);
433
+ menus.delete(key);
411
434
  entry.updatedAt = now;
412
- menus.set(messageId, entry);
435
+ menus.set(key, entry);
413
436
  return entry.state;
414
437
  }
415
438
 
@@ -418,7 +441,7 @@ export function createTelegramModelMenuRuntime<
418
441
  >(
419
442
  options: Partial<TelegramModelMenuStoreOptions> = {},
420
443
  ): TelegramModelMenuRuntime<TModel> {
421
- const menus = new Map<number, StoredTelegramModelMenuState<TModel>>();
444
+ const menus = new Map<string, StoredTelegramModelMenuState<TModel>>();
422
445
  let cachedInputs: CachedTelegramModelMenuInputs<TModel> | undefined;
423
446
  const getStoreOptions = (): TelegramModelMenuStoreOptions => ({
424
447
  maxAgeMs: options.maxAgeMs ?? TELEGRAM_MODEL_MENU_STATE_TTL_MS,
@@ -429,8 +452,13 @@ export function createTelegramModelMenuRuntime<
429
452
  storeState: (state) => {
430
453
  storeTelegramModelMenuState(menus, state, getStoreOptions());
431
454
  },
432
- getState: (messageId) =>
433
- getStoredTelegramModelMenuState(menus, messageId, getStoreOptions()),
455
+ getState: (messageId, chatId) =>
456
+ getStoredTelegramModelMenuState(
457
+ menus,
458
+ messageId,
459
+ getStoreOptions(),
460
+ chatId,
461
+ ),
434
462
  clear: () => {
435
463
  menus.clear();
436
464
  cachedInputs = undefined;
@@ -456,11 +484,12 @@ export function createTelegramModelMenuStateBuilder<
456
484
  TelegramModelMenuStateBuilderContext<TModel>,
457
485
  >(
458
486
  deps: TelegramModelMenuStateBuilderDeps<TModel, TContext>,
459
- ): (chatId: number, ctx: TContext) => Promise<TelegramModelMenuState<TModel>> {
460
- return async (chatId, ctx) => {
487
+ ): (chatId: number, ctx: TContext, threadId?: number) => Promise<TelegramModelMenuState<TModel>> {
488
+ return async (chatId, ctx, threadId) => {
461
489
  const settingsManager = deps.createSettingsManager(ctx.cwd);
462
490
  return deps.runtime.buildState({
463
491
  chatId,
492
+ threadId,
464
493
  activeModel: deps.getActiveModel(ctx),
465
494
  ctx,
466
495
  reloadSettings: () => settingsManager.reload(),
@@ -521,6 +550,7 @@ export function buildTelegramModelMenuState<
521
550
  }
522
551
  return {
523
552
  chatId: params.chatId,
553
+ threadId: params.threadId,
524
554
  messageId: 0,
525
555
  page: 0,
526
556
  scope: scopedModels.length > 0 ? "scoped" : "all",
@@ -562,6 +592,7 @@ export async function buildTelegramModelMenuStateRuntime<
562
592
  cachedInputs,
563
593
  state: buildTelegramModelMenuState({
564
594
  chatId: options.chatId,
595
+ threadId: options.threadId,
565
596
  activeModel: options.activeModel,
566
597
  availableModels: cachedInputs.availableModels,
567
598
  configuredScopedModelPatterns: cachedInputs.configuredScopedModelPatterns,
@@ -844,7 +875,7 @@ export function buildTelegramModelCallbackPlan<
844
875
  if (!params.canRestartBusyRun) {
845
876
  return {
846
877
  kind: "answer",
847
- text: "π is busy. Send /abort, /next, or /stop.",
878
+ text: "Pi is busy. Send /abort, /next, or /stop.",
848
879
  };
849
880
  }
850
881
  return {
@@ -944,7 +975,7 @@ export async function handleTelegramModelMenuCallbackAction<
944
975
  if (!restarted) {
945
976
  await deps.answerCallbackQuery(
946
977
  callbackQueryId,
947
- "π is busy. Send /abort, /next, or /stop.",
978
+ "Pi is busy. Send /abort, /next, or /stop.",
948
979
  );
949
980
  return true;
950
981
  }
package/lib/menu-queue.ts CHANGED
@@ -38,6 +38,7 @@ interface TelegramQueueMenuItem {
38
38
  statusSummary: string;
39
39
  promptText: string;
40
40
  }
41
+
41
42
  function getTelegramQueueItemPromptText<Context>(
42
43
  item: Queue.TelegramQueueItem<Context>,
43
44
  ): string {
@@ -50,10 +51,11 @@ function getTelegramQueueItemPromptText<Context>(
50
51
  .trim() || item.statusSummary
51
52
  );
52
53
  }
54
+
53
55
  function toTelegramQueueMenuItems<Context>(
54
56
  items: readonly Queue.TelegramQueueItem<Context>[],
55
57
  ): TelegramQueueMenuItem[] {
56
- return items.map(function toTelegramQueueMenuItem(item, index) {
58
+ return items.map((item, index) => {
57
59
  return {
58
60
  chatId: item.chatId,
59
61
  replyToMessageId: item.replyToMessageId,
@@ -67,6 +69,7 @@ function toTelegramQueueMenuItems<Context>(
67
69
  };
68
70
  });
69
71
  }
72
+
70
73
  function buildTelegramQueueMenuReplyMarkup(
71
74
  items: readonly TelegramQueueMenuItem[],
72
75
  emptyRefreshIndex = 0,
@@ -80,7 +83,7 @@ function buildTelegramQueueMenuReplyMarkup(
80
83
  : "queue:refresh";
81
84
  const refreshRow = [{ text: "🌀 Refresh", callback_data: refreshData }];
82
85
  if (items.length === 0) return { inline_keyboard: [backRow, refreshRow] };
83
- const rows = items.map(function buildTelegramQueueMenuRow(item, index) {
86
+ const rows = items.map((item, index) => {
84
87
  const prefix = item.isPriority
85
88
  ? `${item.priorityEmoji ?? "⚡"} `
86
89
  : item.hasAttachments
@@ -96,33 +99,38 @@ function buildTelegramQueueMenuReplyMarkup(
96
99
  });
97
100
  return { inline_keyboard: [backRow, refreshRow, ...rows] };
98
101
  }
102
+
99
103
  function findTelegramQueueItem<Context>(
100
104
  items: readonly Queue.TelegramQueueItem<Context>[],
101
105
  chatId: number,
102
106
  replyToMessageId: number,
103
107
  ): Queue.TelegramQueueItem<Context> | undefined {
104
- return items.find(function matchesTelegramQueueItem(item) {
108
+ return items.find((item) => {
105
109
  return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
106
110
  });
107
111
  }
112
+
108
113
  function findTelegramQueueMenuItem(
109
114
  items: readonly TelegramQueueMenuItem[],
110
115
  chatId: number,
111
116
  replyToMessageId: number,
112
117
  ): TelegramQueueMenuItem | undefined {
113
- return items.find(function matchesTelegramQueueMenuItem(item) {
118
+ return items.find((item) => {
114
119
  return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
115
120
  });
116
121
  }
122
+
117
123
  function escapeTelegramQueueMenuHtmlChar(char: string): string {
118
124
  if (char === "&") return "&amp;";
119
125
  if (char === "<") return "&lt;";
120
126
  if (char === ">") return "&gt;";
121
127
  return char;
122
128
  }
129
+
123
130
  function escapeTelegramQueueMenuHtml(text: string): string {
124
131
  return Array.from(text).map(escapeTelegramQueueMenuHtmlChar).join("");
125
132
  }
133
+
126
134
  function escapeTelegramQueueMenuHtmlPreview(text: string): string {
127
135
  const suffix = escapeTelegramQueueMenuHtml(
128
136
  QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX,
@@ -142,12 +150,14 @@ function escapeTelegramQueueMenuHtmlPreview(text: string): string {
142
150
  }
143
151
  return truncated ? escaped + suffix : escaped;
144
152
  }
153
+
145
154
  function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
146
155
  const badge = item.isPriority ? ` ${item.priorityEmoji ?? "⚡"}` : "";
147
156
  const heading = `<b>${item.queuePosition}.</b>${badge}`;
148
157
  const preview = `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
149
158
  return `${heading}\n${preview}`;
150
159
  }
160
+
151
161
  function buildTelegramQueueItemSubmenuReplyMarkup(
152
162
  chatId: number,
153
163
  replyToMessageId: number,
@@ -175,6 +185,7 @@ function buildTelegramQueueItemSubmenuReplyMarkup(
175
185
  ],
176
186
  };
177
187
  }
188
+
178
189
  function buildTelegramQueueDeleteConfirmationReplyMarkup(
179
190
  chatId: number,
180
191
  replyToMessageId: number,
@@ -223,6 +234,7 @@ interface TelegramQueueMenuCallbackDeps<Context = unknown> {
223
234
  ) => Promise<void>;
224
235
  updateStatus: (ctx: Context) => void;
225
236
  }
237
+
226
238
  async function handleTelegramQueueMenuCallback<Context>(
227
239
  callbackQueryId: string,
228
240
  data: string,
@@ -337,6 +349,7 @@ async function handleTelegramQueueMenuCallback<Context>(
337
349
  }
338
350
  return false;
339
351
  }
352
+
340
353
  function getTelegramQueueMenuListText(
341
354
  items: readonly TelegramQueueMenuItem[],
342
355
  emptyRefreshIndex?: number,
@@ -347,6 +360,7 @@ function getTelegramQueueMenuListText(
347
360
  emptyRefreshIndex % EMPTY_QUEUE_REFRESH_TITLES.length
348
361
  ];
349
362
  }
363
+
350
364
  async function updateTelegramQueueMenuList<Context>(
351
365
  callbackQueryId: string,
352
366
  replyChatId: number,
@@ -364,6 +378,7 @@ async function updateTelegramQueueMenuList<Context>(
364
378
  );
365
379
  await deps.answerCallbackQuery(callbackQueryId, notice);
366
380
  }
381
+
367
382
  async function refreshStaleTelegramQueueMenuItem<Context>(
368
383
  callbackQueryId: string,
369
384
  replyChatId: number,
@@ -378,6 +393,7 @@ async function refreshStaleTelegramQueueMenuItem<Context>(
378
393
  "Item no longer in queue.",
379
394
  );
380
395
  }
396
+
381
397
  async function handleTelegramQueueMenuPick<Context>(
382
398
  callbackQueryId: string,
383
399
  replyChatId: number,
@@ -403,6 +419,7 @@ async function handleTelegramQueueMenuPick<Context>(
403
419
  );
404
420
  await deps.answerCallbackQuery(callbackQueryId);
405
421
  }
422
+
406
423
  async function handleTelegramQueueMenuPriority<Context>(
407
424
  callbackQueryId: string,
408
425
  replyChatId: number,
@@ -432,6 +449,7 @@ async function handleTelegramQueueMenuPriority<Context>(
432
449
  deps,
433
450
  );
434
451
  }
452
+
435
453
  async function handleTelegramQueueMenuPrioritySet<Context>(
436
454
  callbackQueryId: string,
437
455
  replyChatId: number,
@@ -462,6 +480,7 @@ async function handleTelegramQueueMenuPrioritySet<Context>(
462
480
  deps,
463
481
  );
464
482
  }
483
+
465
484
  async function updateTelegramQueueMenuPriority<Context>(
466
485
  callbackQueryId: string,
467
486
  replyChatId: number,
@@ -494,6 +513,7 @@ async function updateTelegramQueueMenuPriority<Context>(
494
513
  updated.isPriority ? "Prioritized." : "Normal priority.",
495
514
  );
496
515
  }
516
+
497
517
  async function handleTelegramQueueMenuDeleteRequest<Context>(
498
518
  callbackQueryId: string,
499
519
  replyChatId: number,
@@ -519,6 +539,7 @@ async function handleTelegramQueueMenuDeleteRequest<Context>(
519
539
  );
520
540
  await deps.answerCallbackQuery(callbackQueryId);
521
541
  }
542
+
522
543
  async function handleTelegramQueueMenuKeep<Context>(
523
544
  callbackQueryId: string,
524
545
  replyChatId: number,
@@ -544,6 +565,7 @@ async function handleTelegramQueueMenuKeep<Context>(
544
565
  );
545
566
  await deps.answerCallbackQuery(callbackQueryId, "Kept in queue.");
546
567
  }
568
+
547
569
  async function handleTelegramQueueMenuConfirmDelete<Context>(
548
570
  callbackQueryId: string,
549
571
  replyChatId: number,
@@ -609,6 +631,7 @@ export function createTelegramQueueMenuRuntime<
609
631
  ) => Promise<TelegramModelMenuState<TModel>>;
610
632
  getStoredModelMenuState: (
611
633
  messageId: number | undefined,
634
+ chatId?: number,
612
635
  ) => TelegramModelMenuState<TModel> | undefined;
613
636
  storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
614
637
  updateStatusMessage: (
@@ -641,6 +664,7 @@ export function createTelegramQueueMenuRuntime<
641
664
  }),
642
665
  };
643
666
  }
667
+
644
668
  function createOpenQueueMenu<
645
669
  Context,
646
670
  TModel extends MenuModel = MenuModel,
@@ -658,11 +682,11 @@ function createOpenQueueMenu<
658
682
  replyMarkup: TelegramQueueMenuReplyMarkup,
659
683
  ) => Promise<number | undefined>;
660
684
  }) {
661
- return async function openQueueMenu(
685
+ return async (
662
686
  chatId: number,
663
687
  replyToMessageId: number,
664
688
  ctx: Context,
665
- ): Promise<void> {
689
+ ): Promise<void> => {
666
690
  const state = await deps.getModelMenuState(chatId, ctx);
667
691
  const menuItems = toTelegramQueueMenuItems(deps.getQueuedItems());
668
692
  const text = getTelegramQueueMenuListText(menuItems);
@@ -678,6 +702,7 @@ function createOpenQueueMenu<
678
702
  deps.storeModelMenuState(state);
679
703
  };
680
704
  }
705
+
681
706
  function createQueueMenuCallbackHandler<
682
707
  Context,
683
708
  TModel extends MenuModel = MenuModel,
@@ -692,6 +717,7 @@ function createQueueMenuCallbackHandler<
692
717
  ) => Promise<number | undefined>;
693
718
  getStoredModelMenuState: (
694
719
  messageId: number | undefined,
720
+ chatId?: number,
695
721
  ) => TelegramModelMenuState<TModel> | undefined;
696
722
  updateStatusMessage: (
697
723
  state: TelegramModelMenuState<TModel>,
@@ -703,17 +729,17 @@ function createQueueMenuCallbackHandler<
703
729
  ) => Promise<void>;
704
730
  updateStatus: (ctx: Context) => void;
705
731
  }) {
706
- return async function queueMenuCallbackHandler(
732
+ return async (
707
733
  query: TelegramQueueMenuCallbackQuery,
708
734
  ctx: Context,
709
- ): Promise<boolean> {
735
+ ): Promise<boolean> => {
710
736
  const data = query.data;
711
737
  const chatId = query.message?.chat?.id;
712
738
  const messageId = query.message?.message_id;
713
739
  if (!data || typeof chatId !== "number" || typeof messageId !== "number")
714
740
  return false;
715
741
  if (data === "menu:queue" || data === "status:queue") {
716
- const state = deps.getStoredModelMenuState(messageId);
742
+ const state = deps.getStoredModelMenuState(messageId, chatId);
717
743
  if (!state) {
718
744
  await deps.answerCallbackQuery(
719
745
  query.id,
@@ -735,13 +761,13 @@ function createQueueMenuCallbackHandler<
735
761
  return true;
736
762
  }
737
763
  if (!data.startsWith("queue:")) return false;
738
- const getQueueSnapshot = function getQueueSnapshot() {
764
+ const getQueueSnapshot = () => {
739
765
  return deps.telegramQueueStore.getQueuedItems();
740
766
  };
741
- const toMenuItems = function toMenuItems() {
767
+ const toMenuItems = () => {
742
768
  return toTelegramQueueMenuItems(getQueueSnapshot());
743
769
  };
744
- const findItem = function findItem(cId: number, rId: number) {
770
+ const findItem = (cId: number, rId: number) => {
745
771
  return findTelegramQueueMenuItem(toMenuItems(), cId, rId);
746
772
  };
747
773
  return handleTelegramQueueMenuCallback(
@@ -753,19 +779,19 @@ function createQueueMenuCallbackHandler<
753
779
  {
754
780
  getQueuedItems: toMenuItems,
755
781
  findItem,
756
- togglePriority: function togglePriority(cId, rId) {
782
+ togglePriority: (cId, rId) => {
757
783
  return toggleQueuedTelegramPromptPriority(cId, rId, ctx, {
758
784
  getQueueSnapshot,
759
785
  queueMutationRuntime: deps.queueMutationRuntime,
760
786
  });
761
787
  },
762
- setPriority: function setPriority(cId, rId, enabled) {
788
+ setPriority: (cId, rId, enabled) => {
763
789
  return setQueuedTelegramPromptPriority(cId, rId, enabled, ctx, {
764
790
  getQueueSnapshot,
765
791
  queueMutationRuntime: deps.queueMutationRuntime,
766
792
  });
767
793
  },
768
- cancelItem: function cancelItem(cId, rId, c) {
794
+ cancelItem: (cId, rId, c) => {
769
795
  return cancelQueuedTelegramItem(cId, rId, c, {
770
796
  getQueueSnapshot,
771
797
  queueMutationRuntime: deps.queueMutationRuntime,
@@ -778,6 +804,7 @@ function createQueueMenuCallbackHandler<
778
804
  );
779
805
  };
780
806
  }
807
+
781
808
  function toggleQueuedTelegramPromptPriority<Context>(
782
809
  chatId: number,
783
810
  replyToMessageId: number,
@@ -800,6 +827,7 @@ function toggleQueuedTelegramPromptPriority<Context>(
800
827
  }
801
828
  return true;
802
829
  }
830
+
803
831
  function setQueuedTelegramPromptPriority<Context>(
804
832
  chatId: number,
805
833
  replyToMessageId: number,
@@ -823,6 +851,7 @@ function setQueuedTelegramPromptPriority<Context>(
823
851
  }
824
852
  return true;
825
853
  }
854
+
826
855
  function cancelQueuedTelegramItem<Context>(
827
856
  chatId: number,
828
857
  replyToMessageId: number,
@@ -843,6 +872,7 @@ function cancelQueuedTelegramItem<Context>(
843
872
  0
844
873
  );
845
874
  }
875
+
846
876
  function createQueueMenuSendMessageAdapter(
847
877
  sendInteractiveMessage: (
848
878
  chatId: number,
@@ -851,15 +881,16 @@ function createQueueMenuSendMessageAdapter(
851
881
  replyMarkup: TelegramQueueMenuReplyMarkup,
852
882
  ) => Promise<number | undefined>,
853
883
  ) {
854
- return function queueMenuSendMessage(
884
+ return (
855
885
  chatId: number,
856
886
  _replyToMessageId: number,
857
887
  text: string,
858
888
  replyMarkup: TelegramQueueMenuReplyMarkup,
859
- ): Promise<number | undefined> {
889
+ ): Promise<number | undefined> => {
860
890
  return sendInteractiveMessage(chatId, text, "html", replyMarkup);
861
891
  };
862
892
  }
893
+
863
894
  function createQueueMenuEditMessageAdapter(
864
895
  editInteractiveMessage: (
865
896
  chatId: number,
@@ -869,19 +900,19 @@ function createQueueMenuEditMessageAdapter(
869
900
  replyMarkup: TelegramQueueMenuReplyMarkup,
870
901
  ) => Promise<void>,
871
902
  ) {
872
- return function queueMenuEditMessage(
903
+ return (
873
904
  chatId: number,
874
905
  messageId: number,
875
906
  text: string,
876
907
  replyMarkup: TelegramQueueMenuReplyMarkup,
877
- ): Promise<number | undefined> {
908
+ ): Promise<number | undefined> => {
878
909
  return editInteractiveMessage(
879
910
  chatId,
880
911
  messageId,
881
912
  text,
882
913
  "html",
883
914
  replyMarkup,
884
- ).then(function () {
915
+ ).then(() => {
885
916
  return undefined as number | undefined;
886
917
  });
887
918
  };
@@ -65,7 +65,7 @@ export interface TelegramSettingsMenuRuntime<TContext> {
65
65
  query: {
66
66
  id: string;
67
67
  data?: string;
68
- message?: { message_id?: number };
68
+ message?: { message_id?: number; chat?: { id?: number } };
69
69
  },
70
70
  ctx: TContext,
71
71
  ) => Promise<boolean>;
@@ -92,6 +92,7 @@ export interface TelegramSettingsMenuRuntimeDeps<
92
92
  ) => Promise<TelegramModelMenuState<TModel>>;
93
93
  getStoredModelMenuState: (
94
94
  messageId: number | undefined,
95
+ chatId?: number,
95
96
  ) => TelegramModelMenuState<TModel> | undefined;
96
97
  storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
97
98
  editInteractiveMessage: (
@@ -126,7 +127,7 @@ function getVoiceReplyModeLabel(mode: TelegramVoiceReplyModeSetting): string {
126
127
  }
127
128
 
128
129
  function getTelegramSettingsStateValueLabel(value: string): string {
129
- return value.length > 0 ? value[0]!.toUpperCase() + value.slice(1) : value;
130
+ return value.toLowerCase();
130
131
  }
131
132
 
132
133
  function getVoiceReplyModeSetting(
@@ -146,7 +147,7 @@ export function buildProactivePushSettingsText(
146
147
  return [
147
148
  `${PROACTIVE_PUSH_SETTINGS_TITLE} <code>${proactivePushEnabled ? "on" : "off"}</code>`,
148
149
  "",
149
- "Send successful local π task results to Telegram when the bridge is connected.",
150
+ "Send successful local Pi task results to Telegram when the bridge is connected.",
150
151
  ].join("\n");
151
152
  }
152
153
 
@@ -218,7 +219,7 @@ export function buildTelegramSettingsMenuReplyMarkup(
218
219
  ],
219
220
  [
220
221
  {
221
- text: `📌 Proactive push: ${proactivePushEnabled ? "On" : "Off"}`,
222
+ text: `📌 Proactive push: ${proactivePushEnabled ? "on" : "off"}`,
222
223
  callback_data: "settings:open:proactive",
223
224
  },
224
225
  ],
@@ -490,7 +491,10 @@ export function createTelegramSettingsMenuRuntime<
490
491
  ),
491
492
  handleCallbackQuery: async (query) => {
492
493
  if (!query.data?.startsWith("settings:")) return false;
493
- const state = deps.getStoredModelMenuState(query.message?.message_id);
494
+ const state = deps.getStoredModelMenuState(
495
+ query.message?.message_id,
496
+ query.message?.chat?.id,
497
+ );
494
498
  if (!state) {
495
499
  const voiceMode = query.data.slice("settings:set:voice-reply:".length);
496
500
  if (
@@ -94,6 +94,9 @@ function sendTelegramMenuMessage(
94
94
  appliedPayload.text,
95
95
  appliedPayload.mode,
96
96
  appliedPayload.replyMarkup,
97
+ state.threadId !== undefined
98
+ ? { target: { chatId: state.chatId, threadId: state.threadId } }
99
+ : undefined,
97
100
  );
98
101
  }
99
102
 
@@ -79,6 +79,9 @@ function sendTelegramMenuMessage(
79
79
  appliedPayload.text,
80
80
  appliedPayload.mode,
81
81
  appliedPayload.replyMarkup,
82
+ state.threadId !== undefined
83
+ ? { target: { chatId: state.chatId, threadId: state.threadId } }
84
+ : undefined,
82
85
  );
83
86
  }
84
87