@llblab/pi-telegram 0.52.2 → 0.53.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 (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +7 -11
  5. package/api/commands.ts +3 -0
  6. package/dist/api/commands.d.ts +1 -1
  7. package/dist/lib/activity-verbosity.d.ts +1 -5
  8. package/dist/lib/activity-verbosity.js +15 -42
  9. package/dist/lib/activity.d.ts +9 -0
  10. package/dist/lib/activity.js +39 -10
  11. package/dist/lib/agent-messages.js +7 -3
  12. package/dist/lib/bindings.d.ts +43 -5
  13. package/dist/lib/bindings.js +163 -20
  14. package/dist/lib/bus-api.d.ts +12 -0
  15. package/dist/lib/bus-api.js +182 -79
  16. package/dist/lib/bus-follower.d.ts +111 -31
  17. package/dist/lib/bus-follower.js +1324 -359
  18. package/dist/lib/bus-leader.d.ts +41 -91
  19. package/dist/lib/bus-leader.js +969 -637
  20. package/dist/lib/bus-transport.js +16 -9
  21. package/dist/lib/bus.d.ts +215 -18
  22. package/dist/lib/bus.js +1048 -327
  23. package/dist/lib/channel-posts.d.ts +5 -0
  24. package/dist/lib/channel-posts.js +434 -261
  25. package/dist/lib/command-templates.d.ts +26 -5
  26. package/dist/lib/command-templates.js +92 -176
  27. package/dist/lib/commands.d.ts +168 -82
  28. package/dist/lib/commands.js +1409 -291
  29. package/dist/lib/config.d.ts +1 -8
  30. package/dist/lib/config.js +34 -76
  31. package/dist/lib/delivery.d.ts +2 -2
  32. package/dist/lib/delivery.js +16 -16
  33. package/dist/lib/extension.js +639 -171
  34. package/dist/lib/generative-app-worker.mjs +19 -7
  35. package/dist/lib/generative-apps.d.ts +0 -5
  36. package/dist/lib/generative-apps.js +61 -49
  37. package/dist/lib/inbound.d.ts +5 -0
  38. package/dist/lib/inbound.js +35 -59
  39. package/dist/lib/journal.d.ts +33 -3
  40. package/dist/lib/journal.js +2063 -782
  41. package/dist/lib/lifecycle.d.ts +2 -6
  42. package/dist/lib/lifecycle.js +75 -58
  43. package/dist/lib/locks.d.ts +29 -3
  44. package/dist/lib/locks.js +289 -108
  45. package/dist/lib/logging.js +50 -14
  46. package/dist/lib/media.d.ts +22 -1
  47. package/dist/lib/media.js +46 -28
  48. package/dist/lib/menu-model.d.ts +19 -5
  49. package/dist/lib/menu-model.js +53 -11
  50. package/dist/lib/menu-queue.d.ts +5 -4
  51. package/dist/lib/menu-queue.js +45 -63
  52. package/dist/lib/menu-settings.d.ts +5 -4
  53. package/dist/lib/menu-settings.js +114 -78
  54. package/dist/lib/menu-status.d.ts +1 -1
  55. package/dist/lib/menu-status.js +8 -25
  56. package/dist/lib/menu-thinking.d.ts +6 -1
  57. package/dist/lib/menu-thinking.js +14 -21
  58. package/dist/lib/menu.d.ts +8 -61
  59. package/dist/lib/menu.js +92 -164
  60. package/dist/lib/model.d.ts +1 -1
  61. package/dist/lib/model.js +2 -16
  62. package/dist/lib/outbound-attachments.d.ts +6 -27
  63. package/dist/lib/outbound-attachments.js +45 -38
  64. package/dist/lib/outbound-buttons.js +22 -16
  65. package/dist/lib/outbound-markup.d.ts +0 -2
  66. package/dist/lib/outbound-markup.js +51 -64
  67. package/dist/lib/outbound-voice.js +6 -3
  68. package/dist/lib/outbound.d.ts +6 -6
  69. package/dist/lib/outbound.js +61 -51
  70. package/dist/lib/ownership.js +6 -2
  71. package/dist/lib/paths.d.ts +5 -0
  72. package/dist/lib/paths.js +37 -21
  73. package/dist/lib/pi.d.ts +2 -0
  74. package/dist/lib/pi.js +8 -1
  75. package/dist/lib/polling.d.ts +21 -37
  76. package/dist/lib/polling.js +59 -50
  77. package/dist/lib/preview.js +39 -19
  78. package/dist/lib/process-identity.d.ts +24 -0
  79. package/dist/lib/process-identity.js +133 -0
  80. package/dist/lib/prompts.js +2 -7
  81. package/dist/lib/queue.d.ts +14 -16
  82. package/dist/lib/queue.js +249 -126
  83. package/dist/lib/recovery.d.ts +2 -83
  84. package/dist/lib/recovery.js +20 -274
  85. package/dist/lib/replies.d.ts +16 -24
  86. package/dist/lib/replies.js +99 -116
  87. package/dist/lib/routing.d.ts +120 -7
  88. package/dist/lib/routing.js +4667 -1152
  89. package/dist/lib/runtime.d.ts +1 -11
  90. package/dist/lib/sections.d.ts +4 -29
  91. package/dist/lib/sections.js +7 -20
  92. package/dist/lib/skills.js +4 -3
  93. package/dist/lib/status.js +86 -57
  94. package/dist/lib/sync.d.ts +1 -3
  95. package/dist/lib/sync.js +67 -43
  96. package/dist/lib/target.d.ts +4 -0
  97. package/dist/lib/target.js +18 -0
  98. package/dist/lib/telegram-api.d.ts +18 -13
  99. package/dist/lib/telegram-api.js +148 -53
  100. package/dist/lib/text-groups.js +4 -39
  101. package/dist/lib/thread-cleanup-manager.d.ts +6 -27
  102. package/dist/lib/thread-cleanup-manager.js +369 -186
  103. package/dist/lib/thread-display.d.ts +1 -2
  104. package/dist/lib/thread-display.js +58 -38
  105. package/dist/lib/thread-naming.d.ts +28 -8
  106. package/dist/lib/thread-naming.js +175 -66
  107. package/dist/lib/thread-reconciler.d.ts +42 -16
  108. package/dist/lib/thread-reconciler.js +90 -50
  109. package/dist/lib/threads.d.ts +82 -20
  110. package/dist/lib/threads.js +2247 -855
  111. package/dist/lib/time-injection.js +1 -2
  112. package/dist/lib/turns.js +1 -19
  113. package/dist/lib/updates.d.ts +169 -80
  114. package/dist/lib/updates.js +2418 -641
  115. package/dist/lib/voice.d.ts +7 -22
  116. package/dist/lib/voice.js +7 -22
  117. package/dist/lib/wire.js +1 -1
  118. package/dist/lib/workspace-admission.d.ts +3 -1
  119. package/dist/lib/workspace-admission.js +425 -211
  120. package/dist/lib/workspace-identity.js +8 -13
  121. package/dist/lib/workspace-retirement.d.ts +1 -1
  122. package/dist/lib/workspace-retirement.js +328 -140
  123. package/dist/lib/workspace-slots.js +8 -4
  124. package/dist/package.json +1 -1
  125. package/docs/architecture.md +31 -29
  126. package/docs/inbound.md +1 -1
  127. package/docs/multi-instance-bus.md +15 -3
  128. package/docs/outbound.md +1 -1
  129. package/docs/public-api.md +29 -1
  130. package/docs/ui-style.md +25 -8
  131. package/lib/activity-verbosity.ts +59 -82
  132. package/lib/activity.ts +146 -102
  133. package/lib/agent-messages.ts +12 -5
  134. package/lib/bindings.ts +389 -82
  135. package/lib/bus-api.ts +263 -102
  136. package/lib/bus-follower.ts +2440 -601
  137. package/lib/bus-leader.ts +2209 -1290
  138. package/lib/bus-transport.ts +46 -12
  139. package/lib/bus.ts +1673 -444
  140. package/lib/channel-posts.ts +848 -327
  141. package/lib/command-templates.ts +152 -265
  142. package/lib/commands.ts +2398 -555
  143. package/lib/config.ts +177 -170
  144. package/lib/delivery.ts +41 -29
  145. package/lib/extension.ts +1206 -433
  146. package/lib/generative-app-worker.mjs +19 -7
  147. package/lib/generative-apps.ts +324 -211
  148. package/lib/inbound.ts +95 -97
  149. package/lib/journal.ts +5311 -1763
  150. package/lib/keyboard.ts +1 -3
  151. package/lib/lifecycle.ts +128 -78
  152. package/lib/locks.ts +677 -203
  153. package/lib/logging.ts +147 -58
  154. package/lib/media.ts +82 -43
  155. package/lib/menu-model.ts +85 -18
  156. package/lib/menu-queue.ts +88 -129
  157. package/lib/menu-settings.ts +260 -155
  158. package/lib/menu-status.ts +20 -60
  159. package/lib/menu-thinking.ts +41 -56
  160. package/lib/menu.ts +112 -285
  161. package/lib/model.ts +8 -22
  162. package/lib/outbound-attachments.ts +156 -125
  163. package/lib/outbound-buttons.ts +76 -49
  164. package/lib/outbound-markup.ts +96 -94
  165. package/lib/outbound-voice.ts +33 -27
  166. package/lib/outbound.ts +125 -125
  167. package/lib/ownership.ts +19 -10
  168. package/lib/paths.ts +148 -49
  169. package/lib/pi.ts +35 -15
  170. package/lib/polling.ts +144 -147
  171. package/lib/preview.ts +93 -40
  172. package/lib/process-identity.ts +164 -0
  173. package/lib/prompts.ts +4 -11
  174. package/lib/queue.ts +511 -300
  175. package/lib/recovery.ts +50 -454
  176. package/lib/rendering.ts +1 -3
  177. package/lib/replies.ts +248 -209
  178. package/lib/routing.ts +8509 -2186
  179. package/lib/runtime.ts +1 -12
  180. package/lib/sections.ts +24 -89
  181. package/lib/setup.ts +1 -3
  182. package/lib/skills.ts +9 -4
  183. package/lib/status.ts +215 -110
  184. package/lib/sync.ts +448 -361
  185. package/lib/target.ts +22 -3
  186. package/lib/telegram-api.ts +302 -127
  187. package/lib/text-groups.ts +5 -39
  188. package/lib/thread-cleanup-manager.ts +833 -331
  189. package/lib/thread-display.ts +213 -97
  190. package/lib/thread-naming.ts +232 -87
  191. package/lib/thread-reconciler.ts +155 -72
  192. package/lib/threads.ts +5318 -1845
  193. package/lib/time-injection.ts +1 -4
  194. package/lib/turns.ts +15 -28
  195. package/lib/updates.ts +5184 -1612
  196. package/lib/voice.ts +7 -35
  197. package/lib/wire.ts +1 -1
  198. package/lib/workspace-admission.ts +996 -416
  199. package/lib/workspace-identity.ts +14 -17
  200. package/lib/workspace-retirement.ts +878 -376
  201. package/lib/workspace-slots.ts +22 -13
  202. package/package.json +1 -1
  203. package/scripts/check-downgrade.mjs +0 -472
  204. package/scripts/measure-workspace.mjs +0 -108
package/lib/menu-model.ts CHANGED
@@ -16,6 +16,11 @@ import {
16
16
  type TelegramModelSwitchContinuationSource,
17
17
  type ThinkingLevel,
18
18
  } from "./model.ts";
19
+ import type { TelegramTarget } from "./target.ts";
20
+ import {
21
+ TelegramApiAuthorityError,
22
+ type TelegramApiCallOptions,
23
+ } from "./telegram-api.ts";
19
24
 
20
25
  const TELEGRAM_MODEL_MENU_CACHE_TTL_MS = 5000;
21
26
  const TELEGRAM_MODEL_MENU_STATE_TTL_MS = 10 * 60 * 1000;
@@ -141,10 +146,72 @@ export interface TelegramMenuMessageRuntimeDeps {
141
146
  text: string,
142
147
  mode: "markdown" | "html" | "plain",
143
148
  replyMarkup: TelegramReplyMarkup,
144
- options?: { target?: { chatId: number; threadId?: number } },
149
+ options?: { target?: TelegramTarget } & Pick<
150
+ TelegramApiCallOptions,
151
+ "assertAuthority"
152
+ >,
145
153
  ) => Promise<number | undefined>;
146
154
  }
147
155
 
156
+ /** Per-open delivery lifetime; never retain this guard in stored callback state. */
157
+ export function createTelegramMenuDelivery<TModel extends MenuModel>(
158
+ target: TelegramTarget,
159
+ options: Pick<TelegramApiCallOptions, "assertAuthority"> | undefined,
160
+ deps: Pick<TelegramMenuMessageRuntimeDeps, "sendInteractiveMessage"> & {
161
+ storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
162
+ },
163
+ ) {
164
+ const assertAuthority = options?.assertAuthority,
165
+ capturedTarget = { ...target };
166
+ const sendInteractiveMessage = deps.sendInteractiveMessage,
167
+ storeModelMenuState = deps.storeModelMenuState;
168
+ let requestIssued = false;
169
+ const assertCurrent = () => {
170
+ try {
171
+ assertAuthority?.();
172
+ } catch {
173
+ throw new TelegramApiAuthorityError(requestIssued);
174
+ }
175
+ };
176
+ assertCurrent();
177
+ return {
178
+ assertCurrent,
179
+ assertAuthority,
180
+ sendInteractiveMessage: async (
181
+ ...args: Parameters<
182
+ TelegramMenuMessageRuntimeDeps["sendInteractiveMessage"]
183
+ >
184
+ ) => {
185
+ assertCurrent();
186
+ const [chatId, text, mode, replyMarkup, sendOptions] = args;
187
+ const messageId = await sendInteractiveMessage(
188
+ assertAuthority ? capturedTarget.chatId : chatId,
189
+ text,
190
+ mode,
191
+ replyMarkup,
192
+ assertAuthority
193
+ ? { ...sendOptions, target: { ...capturedTarget }, assertAuthority }
194
+ : sendOptions,
195
+ );
196
+ requestIssued = true;
197
+ assertCurrent();
198
+ return messageId;
199
+ },
200
+ storeModelMenuState: (state: TelegramModelMenuState<TModel>) => {
201
+ assertCurrent();
202
+ if (
203
+ assertAuthority &&
204
+ (state.chatId !== capturedTarget.chatId ||
205
+ state.threadId !== capturedTarget.threadId)
206
+ ) {
207
+ throw new TelegramApiAuthorityError(requestIssued);
208
+ }
209
+ storeModelMenuState(state);
210
+ assertCurrent();
211
+ },
212
+ };
213
+ }
214
+
148
215
  export type TelegramModelMenuCallbackDeps<
149
216
  TModel extends MenuModel = MenuModel,
150
217
  > = {
@@ -322,7 +389,8 @@ function applyTelegramMenuRenderPayload(
322
389
  return payload;
323
390
  }
324
391
 
325
- async function editTelegramMenuMessage(
392
+ /** Apply the payload mode to state, then edit the existing menu message in place. */
393
+ export async function editTelegramMenuMessage(
326
394
  state: TelegramModelMenuState,
327
395
  payload: TelegramMenuRenderPayload,
328
396
  deps: TelegramMenuMessageRuntimeDeps,
@@ -337,7 +405,8 @@ async function editTelegramMenuMessage(
337
405
  );
338
406
  }
339
407
 
340
- function sendTelegramMenuMessage(
408
+ /** Apply the payload mode to state, then send a fresh menu message to its exact chat/Thread. */
409
+ export function sendTelegramMenuMessage(
341
410
  state: TelegramModelMenuState,
342
411
  payload: TelegramMenuRenderPayload,
343
412
  deps: TelegramMenuMessageRuntimeDeps,
@@ -661,9 +730,9 @@ function applyTelegramModelDetailSelection(
661
730
  return "changed";
662
731
  }
663
732
 
664
- function getTelegramSelectedDetailModel<
665
- TModel extends MenuModel = MenuModel,
666
- >(state: TelegramModelMenuState<TModel>): TelegramMenuSelectionResult<TModel> {
733
+ function getTelegramSelectedDetailModel<TModel extends MenuModel = MenuModel>(
734
+ state: TelegramModelMenuState<TModel>,
735
+ ): TelegramMenuSelectionResult<TModel> {
667
736
  const indexedSelection = getTelegramModelSelection(
668
737
  state,
669
738
  state.selectedModelIndex?.toString(),
@@ -810,7 +879,7 @@ export function buildTelegramModelCallbackPlan<
810
879
  action.value,
811
880
  );
812
881
  if (detailResult === "invalid") {
813
- return { kind: "answer", text: "Selected model is no longer available." };
882
+ return { kind: "answer", text: "Selected model is no longer available" };
814
883
  }
815
884
  return { kind: "update-menu" };
816
885
  }
@@ -822,12 +891,12 @@ export function buildTelegramModelCallbackPlan<
822
891
  if (params.state.canMutateScope === false) {
823
892
  return {
824
893
  kind: "answer",
825
- text: "Model scope is controlled by CLI --models.",
894
+ text: "Model scope is controlled by CLI --models",
826
895
  };
827
896
  }
828
897
  const selectionResult = getTelegramSelectedDetailModel(params.state);
829
898
  if (selectionResult.kind !== "selected") {
830
- return { kind: "answer", text: "Selected model is no longer available." };
899
+ return { kind: "answer", text: "Selected model is no longer available" };
831
900
  }
832
901
  const model = selectionResult.selection.model;
833
902
  const enabled =
@@ -854,10 +923,10 @@ export function buildTelegramModelCallbackPlan<
854
923
  ? getTelegramSelectedDetailModel(params.state)
855
924
  : getTelegramModelSelection(params.state, action.value);
856
925
  if (selectionResult.kind === "invalid") {
857
- return { kind: "answer", text: "Invalid model selection." };
926
+ return { kind: "answer", text: "Invalid model selection" };
858
927
  }
859
928
  if (selectionResult.kind === "missing") {
860
- return { kind: "answer", text: "Selected model is no longer available." };
929
+ return { kind: "answer", text: "Selected model is no longer available" };
861
930
  }
862
931
  const selection = selectionResult.selection;
863
932
  if (modelsMatch(selection.model, params.activeModel)) {
@@ -877,7 +946,7 @@ export function buildTelegramModelCallbackPlan<
877
946
  if (!params.canRestartBusyRun) {
878
947
  return {
879
948
  kind: "answer",
880
- text: "Pi is busy. Send /abort, /next, or /stop.",
949
+ text: "Pi is busy. Send /abort, /next, or /stop",
881
950
  };
882
951
  }
883
952
  return {
@@ -940,7 +1009,7 @@ export async function handleTelegramModelMenuCallbackAction<
940
1009
  if (!deps.persistScopedModelPatterns) {
941
1010
  await deps.answerCallbackQuery(
942
1011
  callbackQueryId,
943
- "Scoped model persistence is unavailable.",
1012
+ "Scoped model persistence is unavailable",
944
1013
  );
945
1014
  return true;
946
1015
  }
@@ -959,7 +1028,7 @@ export async function handleTelegramModelMenuCallbackAction<
959
1028
  }
960
1029
  const changed = await deps.setModel(plan.selection.model);
961
1030
  if (changed === false) {
962
- await deps.answerCallbackQuery(callbackQueryId, "Model is not available.");
1031
+ await deps.answerCallbackQuery(callbackQueryId, "Model is not available");
963
1032
  return true;
964
1033
  }
965
1034
  deps.setCurrentModel(plan.selection.model);
@@ -991,7 +1060,7 @@ export async function handleTelegramModelMenuCallbackAction<
991
1060
  if (!restarted) {
992
1061
  await deps.answerCallbackQuery(
993
1062
  callbackQueryId,
994
- "Pi is busy. Send /abort, /next, or /stop.",
1063
+ "Pi is busy. Send /abort, /next, or /stop",
995
1064
  );
996
1065
  return true;
997
1066
  }
@@ -1100,9 +1169,7 @@ function buildModelDetailMenuReplyMarkup(
1100
1169
  };
1101
1170
  }
1102
1171
 
1103
- function buildModelDetailMenuText(
1104
- state: TelegramModelMenuState,
1105
- ): string {
1172
+ function buildModelDetailMenuText(state: TelegramModelMenuState): string {
1106
1173
  const selection = getTelegramSelectedDetailModel(state);
1107
1174
  if (selection.kind !== "selected") return MODEL_DETAIL_MENU_TITLE;
1108
1175
  const model = selection.selection.model;
package/lib/menu-queue.ts CHANGED
@@ -5,11 +5,14 @@
5
5
  */
6
6
 
7
7
  import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
8
- import type { TelegramModelMenuState } from "./menu.ts";
8
+ import {
9
+ createTelegramMenuDelivery,
10
+ type TelegramMenuMessageRuntimeDeps,
11
+ type TelegramModelMenuState,
12
+ } from "./menu-model.ts";
9
13
  import type { MenuModel } from "./model.ts";
10
14
  import * as Queue from "./queue.ts";
11
-
12
- // --- Queue Menu ---
15
+ import type { TelegramApiCallOptions } from "./telegram-api.ts";
13
16
 
14
17
  const QUEUE_ITEM_PROMPT_HTML_LIMIT = 3600;
15
18
  const QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX = "\n… [truncated]";
@@ -119,21 +122,13 @@ function buildTelegramQueueMenuReplyMarkup(
119
122
  return { inline_keyboard: [backRow, refreshRow, ...rows] };
120
123
  }
121
124
 
122
- function findTelegramQueueItem<Context>(
123
- items: readonly Queue.TelegramQueueItem<Context>[],
124
- chatId: number,
125
- replyToMessageId: number,
126
- ): Queue.TelegramQueueItem<Context> | undefined {
127
- return items.find((item) => {
128
- return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
129
- });
130
- }
131
-
132
- function findTelegramQueueMenuItem(
133
- items: readonly TelegramQueueMenuItem[],
125
+ function findTelegramQueueItem<
126
+ TItem extends { chatId: number; replyToMessageId: number },
127
+ >(
128
+ items: readonly TItem[],
134
129
  chatId: number,
135
130
  replyToMessageId: number,
136
- ): TelegramQueueMenuItem | undefined {
131
+ ): TItem | undefined {
137
132
  return items.find((item) => {
138
133
  return item.chatId === chatId && item.replyToMessageId === replyToMessageId;
139
134
  });
@@ -327,7 +322,7 @@ async function handleTelegramQueueMenuCallback<Context>(
327
322
  replyChatId,
328
323
  replyMessageId,
329
324
  deps,
330
- "Guest prompt skipped.",
325
+ "Guest prompt skipped",
331
326
  );
332
327
  return true;
333
328
  }
@@ -372,9 +367,7 @@ async function handleTelegramQueueMenuCallback<Context>(
372
367
  );
373
368
  return true;
374
369
  }
375
- const skipSetMatch = data.match(
376
- /^queue:skip-set:(\d+):(\d+):(skip|keep)$/,
377
- );
370
+ const skipSetMatch = data.match(/^queue:skip-set:(\d+):(\d+):(skip|keep)$/);
378
371
  if (skipSetMatch) {
379
372
  await handleTelegramQueueMenuSkipSet(
380
373
  callbackQueryId,
@@ -431,7 +424,7 @@ async function refreshStaleTelegramQueueMenuItem<Context>(
431
424
  replyChatId,
432
425
  replyMessageId,
433
426
  deps,
434
- "Item no longer in queue.",
427
+ "Item no longer in queue",
435
428
  );
436
429
  }
437
430
 
@@ -586,7 +579,7 @@ async function updateTelegramQueueMenuPriority<Context>(
586
579
  );
587
580
  await deps.answerCallbackQuery(
588
581
  callbackQueryId,
589
- updated.isPriority ? "Prioritized." : "Normal priority.",
582
+ updated.isPriority ? "Prioritized" : "Normal priority",
590
583
  );
591
584
  }
592
585
 
@@ -644,6 +637,8 @@ interface TelegramQueueMenuRuntime<Context> {
644
637
  chatId: number,
645
638
  replyToMessageId: number,
646
639
  ctx: Context,
640
+ threadId?: number,
641
+ options?: Pick<TelegramApiCallOptions, "assertAuthority">,
647
642
  ) => Promise<void>;
648
643
  handleCallbackQuery: (
649
644
  query: TelegramQueueMenuCallbackQuery,
@@ -656,12 +651,7 @@ export function createTelegramQueueMenuRuntime<
656
651
  >(deps: {
657
652
  telegramQueueStore: Queue.TelegramQueueStateStore<Context>;
658
653
  queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
659
- sendInteractiveMessage: (
660
- chatId: number,
661
- text: string,
662
- mode: "html",
663
- replyMarkup: TelegramQueueMenuReplyMarkup,
664
- ) => Promise<number | undefined>;
654
+ sendInteractiveMessage: TelegramMenuMessageRuntimeDeps["sendInteractiveMessage"];
665
655
  editInteractiveMessage: (
666
656
  chatId: number,
667
657
  messageId: number,
@@ -676,6 +666,7 @@ export function createTelegramQueueMenuRuntime<
676
666
  getModelMenuState: (
677
667
  chatId: number,
678
668
  ctx: Context,
669
+ threadId?: number,
679
670
  ) => Promise<TelegramModelMenuState<TModel>>;
680
671
  getStoredModelMenuState: (
681
672
  messageId: number | undefined,
@@ -696,12 +687,22 @@ export function createTelegramQueueMenuRuntime<
696
687
  deps.editInteractiveMessage,
697
688
  );
698
689
  return {
699
- openQueueMenu: createOpenQueueMenu<Context, TModel>({
700
- getQueuedItems: deps.telegramQueueStore.getQueuedItems,
701
- getModelMenuState: deps.getModelMenuState,
702
- storeModelMenuState: deps.storeModelMenuState,
703
- sendInteractiveMessage: sendQueueMenuMessage,
704
- }),
690
+ openQueueMenu: (chatId, replyToMessageId, ctx, threadId, options) => {
691
+ const delivery = createTelegramMenuDelivery(
692
+ { chatId, threadId },
693
+ options,
694
+ deps,
695
+ );
696
+ return createOpenQueueMenu<Context, TModel>({
697
+ getQueuedItems: deps.telegramQueueStore.getQueuedItems,
698
+ getModelMenuState: (chat, context) =>
699
+ deps.getModelMenuState(chat, context, threadId),
700
+ storeModelMenuState: delivery.storeModelMenuState,
701
+ sendInteractiveMessage: delivery.assertAuthority
702
+ ? createQueueMenuSendMessageAdapter(delivery.sendInteractiveMessage)
703
+ : sendQueueMenuMessage,
704
+ })(chatId, replyToMessageId, ctx);
705
+ },
705
706
  handleCallbackQuery: createQueueMenuCallbackHandler<Context, TModel>({
706
707
  telegramQueueStore: deps.telegramQueueStore,
707
708
  queueMutationRuntime: deps.queueMutationRuntime,
@@ -792,10 +793,7 @@ function createQueueMenuCallbackHandler<
792
793
  if (data === "menu:queue" || data === "status:queue") {
793
794
  const state = deps.getStoredModelMenuState(messageId, chatId);
794
795
  if (!state) {
795
- await deps.answerCallbackQuery(
796
- query.id,
797
- "Interactive message expired.",
798
- );
796
+ await deps.answerCallbackQuery(query.id, "Interactive message expired");
799
797
  return true;
800
798
  }
801
799
  const menuItems = toTelegramQueueMenuItems(
@@ -819,13 +817,17 @@ function createQueueMenuCallbackHandler<
819
817
  return toTelegramQueueMenuItems(getQueueSnapshot());
820
818
  };
821
819
  const findItem = (cId: number, rId: number) => {
822
- return findTelegramQueueMenuItem(toMenuItems(), cId, rId);
820
+ return findTelegramQueueItem(toMenuItems(), cId, rId);
823
821
  };
824
822
  const findGuestItem = (queueOrder: number) => {
825
823
  return toMenuItems().find((item) => {
826
824
  return item.isGuest && item.queueOrder === queueOrder;
827
825
  });
828
826
  };
827
+ const flagDeps = {
828
+ getQueueSnapshot,
829
+ queueMutationRuntime: deps.queueMutationRuntime,
830
+ };
829
831
  return handleTelegramQueueMenuCallback(
830
832
  query.id,
831
833
  data,
@@ -843,24 +845,34 @@ function createQueueMenuCallbackHandler<
843
845
  dismissGuestPlaceholder: deps.dismissGuestPlaceholder,
844
846
  });
845
847
  },
846
- togglePriority: (cId, rId) => {
847
- return toggleQueuedTelegramPromptPriority(cId, rId, ctx, {
848
- getQueueSnapshot,
849
- queueMutationRuntime: deps.queueMutationRuntime,
850
- });
851
- },
852
- setPriority: (cId, rId, enabled) => {
853
- return setQueuedTelegramPromptPriority(cId, rId, enabled, ctx, {
854
- getQueueSnapshot,
855
- queueMutationRuntime: deps.queueMutationRuntime,
856
- });
857
- },
858
- setSkipped: (cId, rId, skipped) => {
859
- return setQueuedTelegramPromptSkipped(cId, rId, skipped, ctx, {
860
- getQueueSnapshot,
861
- queueMutationRuntime: deps.queueMutationRuntime,
862
- });
863
- },
848
+ togglePriority: (cId, rId) =>
849
+ applyQueuedTelegramPromptMenuFlags(
850
+ cId,
851
+ rId,
852
+ ctx,
853
+ flagDeps,
854
+ (item) => ({
855
+ priority: item.queueLane !== "priority",
856
+ skipped: isQueuedTelegramPromptSkipped(item),
857
+ }),
858
+ ),
859
+ setPriority: (cId, rId, enabled) =>
860
+ applyQueuedTelegramPromptMenuFlags(
861
+ cId,
862
+ rId,
863
+ ctx,
864
+ flagDeps,
865
+ (item) => ({
866
+ priority: enabled,
867
+ skipped: isQueuedTelegramPromptSkipped(item),
868
+ }),
869
+ ),
870
+ setSkipped: (cId, rId, skipped) =>
871
+ applyQueuedTelegramPromptMenuFlags(cId, rId, ctx, flagDeps, (item) =>
872
+ item.kind === "prompt"
873
+ ? { priority: item.queueLane === "priority", skipped }
874
+ : undefined,
875
+ ),
864
876
  updateQueueMessage: deps.editInteractiveMessage,
865
877
  answerCallbackQuery: deps.answerCallbackQuery,
866
878
  updateStatus: deps.updateStatus,
@@ -879,9 +891,11 @@ async function skipQueuedTelegramGuestPrompt<Context>(
879
891
  },
880
892
  ): Promise<boolean> {
881
893
  const item = deps.getQueueSnapshot().find((candidate) => {
882
- return candidate.kind === "prompt" &&
894
+ return (
895
+ candidate.kind === "prompt" &&
883
896
  candidate.guestQueryId !== undefined &&
884
- candidate.queueOrder === queueOrder;
897
+ candidate.queueOrder === queueOrder
898
+ );
885
899
  });
886
900
  if (!item || !deps.queueMutationRuntime.removeGuestPromptByQueueOrder) {
887
901
  return false;
@@ -906,111 +920,56 @@ function getQueueMenuReactionDisposition<Context>(
906
920
  return {
907
921
  kind: "priority-suppressed",
908
922
  priorityEmoji:
909
- item.kind === "prompt" ? item.priorityEmoji ?? "⚡" : "⚡",
923
+ item.kind === "prompt" ? (item.priorityEmoji ?? "⚡") : "⚡",
910
924
  suppressionEmoji:
911
- item.kind === "prompt"
912
- ? item.reactionSuppressionEmoji ?? "👎"
913
- : "👎",
925
+ item.kind === "prompt" ? (item.reactionSuppressionEmoji ?? "👎") : "👎",
914
926
  };
915
927
  }
916
928
  if (priority) {
917
929
  return {
918
930
  kind: "priority",
919
- emoji: item.kind === "prompt" ? item.priorityEmoji ?? "⚡" : "⚡",
931
+ emoji: item.kind === "prompt" ? (item.priorityEmoji ?? "⚡") : "⚡",
920
932
  };
921
933
  }
922
934
  if (skipped) {
923
935
  return {
924
936
  kind: "suppressed",
925
937
  emoji:
926
- item.kind === "prompt"
927
- ? item.reactionSuppressionEmoji ?? "👎"
928
- : "👎",
938
+ item.kind === "prompt" ? (item.reactionSuppressionEmoji ?? "👎") : "👎",
929
939
  };
930
940
  }
931
941
  return { kind: "default" };
932
942
  }
933
943
 
934
- function toggleQueuedTelegramPromptPriority<Context>(
935
- chatId: number,
936
- replyToMessageId: number,
937
- ctx: Context,
938
- deps: {
939
- getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
940
- queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
941
- },
942
- ): boolean {
943
- const item = findTelegramQueueItem(
944
- deps.getQueueSnapshot(),
945
- chatId,
946
- replyToMessageId,
947
- );
948
- if (!item) return false;
949
- deps.queueMutationRuntime.applyReactionByMessageId(
950
- replyToMessageId,
951
- getQueueMenuReactionDisposition(
952
- item,
953
- item.queueLane !== "priority",
954
- item.kind === "prompt" &&
955
- item.reactionSuppressionEmoji !== undefined,
956
- ),
957
- ctx,
958
- );
959
- return true;
960
- }
961
-
962
- function setQueuedTelegramPromptPriority<Context>(
963
- chatId: number,
964
- replyToMessageId: number,
965
- enabled: boolean,
966
- ctx: Context,
967
- deps: {
968
- getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
969
- queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
970
- },
944
+ function isQueuedTelegramPromptSkipped<Context>(
945
+ item: Queue.TelegramQueueItem<Context>,
971
946
  ): boolean {
972
- const item = findTelegramQueueItem(
973
- deps.getQueueSnapshot(),
974
- chatId,
975
- replyToMessageId,
976
- );
977
- if (!item) return false;
978
- deps.queueMutationRuntime.applyReactionByMessageId(
979
- replyToMessageId,
980
- getQueueMenuReactionDisposition(
981
- item,
982
- enabled,
983
- item.kind === "prompt" &&
984
- item.reactionSuppressionEmoji !== undefined,
985
- ),
986
- ctx,
987
- );
988
- return true;
947
+ return item.kind === "prompt" && item.reactionSuppressionEmoji !== undefined;
989
948
  }
990
949
 
991
- function setQueuedTelegramPromptSkipped<Context>(
950
+ /** Applies a menu Priority/Skip choice to one queued item through the reaction disposition path. */
951
+ function applyQueuedTelegramPromptMenuFlags<Context>(
992
952
  chatId: number,
993
953
  replyToMessageId: number,
994
- skipped: boolean,
995
954
  ctx: Context,
996
955
  deps: {
997
956
  getQueueSnapshot: () => Queue.TelegramQueueItem<Context>[];
998
957
  queueMutationRuntime: Queue.TelegramQueueMutationController<Context>;
999
958
  },
959
+ chooseFlags: (
960
+ item: Queue.TelegramQueueItem<Context>,
961
+ ) => { priority: boolean; skipped: boolean } | undefined,
1000
962
  ): boolean {
1001
963
  const item = findTelegramQueueItem(
1002
964
  deps.getQueueSnapshot(),
1003
965
  chatId,
1004
966
  replyToMessageId,
1005
967
  );
1006
- if (!item || item.kind !== "prompt") return false;
968
+ const flags = item ? chooseFlags(item) : undefined;
969
+ if (!item || !flags) return false;
1007
970
  deps.queueMutationRuntime.applyReactionByMessageId(
1008
971
  replyToMessageId,
1009
- getQueueMenuReactionDisposition(
1010
- item,
1011
- item.queueLane === "priority",
1012
- skipped,
1013
- ),
972
+ getQueueMenuReactionDisposition(item, flags.priority, flags.skipped),
1014
973
  ctx,
1015
974
  );
1016
975
  return true;