@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.ts CHANGED
@@ -5,15 +5,13 @@
5
5
  */
6
6
 
7
7
  import {
8
- createTelegramModelMenuStateBuilder,
8
+ createTelegramMenuDelivery,
9
9
  handleTelegramModelMenuCallbackAction,
10
10
  openTelegramModelMenu,
11
11
  sendTelegramModelMenuMessage,
12
12
  updateTelegramModelMenuMessage,
13
13
  type TelegramMenuMessageRuntimeDeps,
14
14
  type TelegramModelMenuState,
15
- type TelegramModelMenuStateBuilderContext,
16
- type TelegramModelMenuStateBuilderDeps,
17
15
  type TelegramReplyMarkup,
18
16
  } from "./menu-model.ts";
19
17
  import {
@@ -33,14 +31,18 @@ import {
33
31
  type TelegramModelSwitchContinuationSource,
34
32
  type ThinkingLevel,
35
33
  } from "./model.ts";
36
- import type { TelegramInputRichMessage } from "./telegram-api.ts";
37
34
  import {
38
35
  handleTelegramSectionCallback,
39
36
  handleTelegramSectionOpen,
40
37
  handleTelegramSectionSettingsOpen,
41
38
  parseTelegramSectionCallback,
39
+ type TelegramSectionRuntimeDeps,
42
40
  type TelegramSectionRegistry,
43
41
  } from "./sections.ts";
42
+ import type {
43
+ TelegramApiCallOptions,
44
+ TelegramInputRichMessage,
45
+ } from "./telegram-api.ts";
44
46
 
45
47
  export {
46
48
  applyTelegramModelPageSelection,
@@ -267,6 +269,7 @@ export interface TelegramMenuActionRuntimeDeps<
267
269
  options?: {
268
270
  target?: { chatId: number; threadId?: number };
269
271
  parseMode?: "HTML";
272
+ assertAuthority?: TelegramApiCallOptions["assertAuthority"];
270
273
  },
271
274
  ) => Promise<unknown>;
272
275
  sectionRegistry?: TelegramSectionRegistry;
@@ -294,81 +297,24 @@ export interface TelegramMenuActionRuntime<
294
297
  replyToMessageId: number,
295
298
  ctx: TContext,
296
299
  threadId?: number,
300
+ options?: Pick<TelegramApiCallOptions, "assertAuthority">,
297
301
  ) => Promise<void>;
298
302
  openModelMenu: (
299
303
  chatId: number,
300
304
  replyToMessageId: number,
301
305
  ctx: TContext,
302
306
  threadId?: number,
307
+ options?: Pick<TelegramApiCallOptions, "assertAuthority">,
303
308
  ) => Promise<void>;
304
309
  openThinkingMenu: (
305
310
  chatId: number,
306
311
  replyToMessageId: number,
307
312
  ctx: TContext,
313
+ threadId?: number,
314
+ options?: Pick<TelegramApiCallOptions, "assertAuthority">,
308
315
  ) => Promise<void>;
309
316
  }
310
317
 
311
- export type TelegramMenuCallbackAction =
312
- | { kind: "ignore" }
313
- | { kind: "status"; action: "model" | "thinking" | "queue" | "settings" }
314
- | { kind: "thinking:set"; level: string }
315
- | {
316
- kind: "model";
317
- action:
318
- | "noop"
319
- | "scope"
320
- | "page"
321
- | "pages"
322
- | "open"
323
- | "pick"
324
- | "pick-selected"
325
- | "scope-enable"
326
- | "scope-disable"
327
- | "scope-toggle";
328
- value?: string;
329
- };
330
-
331
- export function parseTelegramMenuCallbackAction(
332
- data: string | undefined,
333
- ): TelegramMenuCallbackAction {
334
- if (data === "menu:model" || data === "status:model") {
335
- return { kind: "status", action: "model" };
336
- }
337
- if (data === "menu:thinking" || data === "status:thinking") {
338
- return { kind: "status", action: "thinking" };
339
- }
340
- if (data === "menu:queue" || data === "status:queue") {
341
- return { kind: "status", action: "queue" };
342
- }
343
- if (data === "menu:settings" || data === "status:settings") {
344
- return { kind: "status", action: "settings" };
345
- }
346
- if (data?.startsWith("thinking:set:")) {
347
- return {
348
- kind: "thinking:set",
349
- level: data.slice("thinking:set:".length),
350
- };
351
- }
352
- if (data?.startsWith("model:")) {
353
- const [, action, value] = data.split(":");
354
- if (
355
- action === "noop" ||
356
- action === "scope" ||
357
- action === "page" ||
358
- action === "pages" ||
359
- action === "open" ||
360
- action === "pick" ||
361
- action === "pick-selected" ||
362
- action === "scope-enable" ||
363
- action === "scope-disable" ||
364
- action === "scope-toggle"
365
- ) {
366
- return { kind: "model", action, value };
367
- }
368
- }
369
- return { kind: "ignore" };
370
- }
371
-
372
318
  export async function handleTelegramMenuCallbackEntry(
373
319
  callbackQueryId: string,
374
320
  data: string | undefined,
@@ -382,7 +328,7 @@ export async function handleTelegramMenuCallbackEntry(
382
328
  if (!state) {
383
329
  await deps.answerCallbackQuery(
384
330
  callbackQueryId,
385
- "Interactive message expired.",
331
+ "Interactive message expired",
386
332
  );
387
333
  return;
388
334
  }
@@ -422,85 +368,15 @@ export async function handleStoredTelegramMenuCallback<
422
368
  });
423
369
  }
424
370
 
425
- export interface TelegramMenuCallbackRuntimeAdapterDeps<
371
+ export type TelegramMenuCallbackRuntimeAdapterDeps<
426
372
  TContext,
427
373
  TModel extends MenuModel = MenuModel,
428
- > {
429
- getStoredModelMenuState: (
430
- messageId: number | undefined,
431
- chatId?: number,
432
- ) => TelegramModelMenuState<TModel> | undefined;
433
- getActiveModel: (ctx: TContext) => TModel | undefined;
434
- getThinkingLevel: () => ThinkingLevel;
435
- setThinkingLevel: (level: ThinkingLevel) => void;
436
- updateStatus: (ctx: TContext, error?: string) => void;
437
- updateModelMenuMessage: (
438
- state: TelegramModelMenuState<TModel>,
439
- ctx: TContext,
440
- ) => Promise<void>;
441
- updateThinkingMenuMessage: (
442
- state: TelegramModelMenuState<TModel>,
443
- ctx: TContext,
444
- ) => Promise<void>;
445
- updateStatusMessage: (
446
- state: TelegramModelMenuState<TModel>,
447
- ctx: TContext,
448
- ) => Promise<void>;
449
- updateSettingsMenuMessage?: (
450
- state: TelegramModelMenuState<TModel>,
451
- ctx: TContext,
452
- ) => Promise<void>;
453
- answerCallbackQuery: (
454
- callbackQueryId: string,
455
- text?: string,
456
- ) => Promise<void>;
457
- isIdle: (ctx: TContext) => boolean;
458
- hasAbortHandler: () => boolean;
374
+ > = Omit<
375
+ TelegramMenuCallbackRuntimeDeps<TContext, TModel>,
376
+ "hasActiveToolExecutions"
377
+ > & {
459
378
  getActiveToolExecutions: () => number;
460
- persistScopedModelPatterns?: (
461
- patterns: string[],
462
- ctx: TContext,
463
- ) => Promise<void>;
464
- setModel: (model: TModel) => Promise<boolean>;
465
- setCurrentModel: (model: TModel, ctx: TContext) => void;
466
- stagePendingModelSwitch: (
467
- selection: ScopedTelegramModel<TModel>,
468
- ctx: TContext,
469
- continuationTurn: TelegramModelSwitchContinuationSource,
470
- ) => void;
471
- restartInterruptedTelegramTurn: (
472
- selection: ScopedTelegramModel<TModel>,
473
- ctx: TContext,
474
- continuationTurn: TelegramModelSwitchContinuationSource,
475
- ) => Promise<boolean> | boolean;
476
- sectionRegistry?: TelegramSectionRegistry;
477
- editInteractiveMessage?: (
478
- chatId: number,
479
- messageId: number,
480
- text: string,
481
- mode: "markdown" | "html" | "plain",
482
- replyMarkup: TelegramReplyMarkup,
483
- ) => Promise<void>;
484
- sendInteractiveMessage?: (
485
- chatId: number,
486
- text: string,
487
- mode: "markdown" | "html" | "plain",
488
- replyMarkup: TelegramReplyMarkup,
489
- options?: { target?: { chatId: number; threadId?: number } },
490
- ) => Promise<number | undefined>;
491
- sendSectionRichMessage?: (
492
- chatId: number,
493
- message: TelegramInputRichMessage,
494
- options?: { target?: { chatId: number; threadId?: number } },
495
- ) => Promise<number | undefined>;
496
- enqueueSectionPrompt?: (
497
- prompt: string,
498
- ctx: TContext,
499
- target?: { chatId: number; threadId?: number },
500
- source?: unknown,
501
- ) => Promise<void>;
502
- deleteMessage?: (chatId: number, messageId: number) => Promise<void>;
503
- }
379
+ };
504
380
 
505
381
  export function createTelegramMenuCallbackHandler<
506
382
  TQuery extends MenuCallbackQuery,
@@ -519,31 +395,10 @@ export function createTelegramMenuCallbackHandlerForContext<
519
395
  >(
520
396
  deps: TelegramMenuCallbackRuntimeAdapterDeps<TContext, TModel>,
521
397
  ): (query: TQuery, ctx: TContext) => Promise<void> {
398
+ const { getActiveToolExecutions, ...ports } = deps;
522
399
  return createTelegramMenuCallbackHandler<TQuery, TContext, TModel>({
523
- getStoredModelMenuState: deps.getStoredModelMenuState,
524
- getActiveModel: deps.getActiveModel,
525
- getThinkingLevel: deps.getThinkingLevel,
526
- setThinkingLevel: deps.setThinkingLevel,
527
- updateStatus: deps.updateStatus,
528
- updateModelMenuMessage: deps.updateModelMenuMessage,
529
- updateThinkingMenuMessage: deps.updateThinkingMenuMessage,
530
- updateStatusMessage: deps.updateStatusMessage,
531
- updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
532
- answerCallbackQuery: deps.answerCallbackQuery,
533
- isIdle: deps.isIdle,
534
- hasAbortHandler: deps.hasAbortHandler,
535
- hasActiveToolExecutions: () => deps.getActiveToolExecutions() > 0,
536
- persistScopedModelPatterns: deps.persistScopedModelPatterns,
537
- setModel: deps.setModel,
538
- setCurrentModel: deps.setCurrentModel,
539
- stagePendingModelSwitch: deps.stagePendingModelSwitch,
540
- restartInterruptedTelegramTurn: deps.restartInterruptedTelegramTurn,
541
- sectionRegistry: deps.sectionRegistry,
542
- editInteractiveMessage: deps.editInteractiveMessage,
543
- sendInteractiveMessage: deps.sendInteractiveMessage,
544
- sendSectionRichMessage: deps.sendSectionRichMessage,
545
- enqueueSectionPrompt: deps.enqueueSectionPrompt,
546
- deleteMessage: deps.deleteMessage,
400
+ ...ports,
401
+ hasActiveToolExecutions: () => getActiveToolExecutions() > 0,
547
402
  });
548
403
  }
549
404
 
@@ -562,7 +417,7 @@ export async function handleTelegramMenuCallbackRuntime<
562
417
  query.message?.chat?.id,
563
418
  );
564
419
  if (!state) {
565
- await deps.answerCallbackQuery(query.id, "Interactive message expired.");
420
+ await deps.answerCallbackQuery(query.id, "Interactive message expired");
566
421
  return;
567
422
  }
568
423
  await deps.updateStatusMessage(state, ctx);
@@ -584,12 +439,30 @@ export async function handleTelegramMenuCallbackRuntime<
584
439
  : undefined;
585
440
  if (typeof chatId === "number" && typeof messageId === "number") {
586
441
  const { token, action, payload } = parsed;
442
+ const sectionDeps: TelegramSectionRuntimeDeps = {
443
+ answerCallbackQuery: deps.answerCallbackQuery,
444
+ target,
445
+ editInteractiveMessage:
446
+ deps.editInteractiveMessage ?? (async () => {}),
447
+ sendInteractiveMessage:
448
+ deps.sendInteractiveMessage ?? (async () => undefined),
449
+ sendRichMessage:
450
+ deps.sendSectionRichMessage ??
451
+ (async () => {
452
+ throw new Error("Rich Message delivery is unavailable");
453
+ }),
454
+ enqueuePrompt: deps.enqueueSectionPrompt
455
+ ? (prompt: string) =>
456
+ deps.enqueueSectionPrompt!(prompt, ctx, target, query)
457
+ : async () => {},
458
+ deleteMessage: deps.deleteMessage ?? (async () => {}),
459
+ };
587
460
  if (action === "open") {
588
461
  const state = deps.getStoredModelMenuState(messageId, chatId);
589
462
  if (!state) {
590
463
  await deps.answerCallbackQuery(
591
464
  query.id,
592
- "Interactive message expired.",
465
+ "Interactive message expired",
593
466
  );
594
467
  return;
595
468
  }
@@ -599,51 +472,19 @@ export async function handleTelegramMenuCallbackRuntime<
599
472
  chatId,
600
473
  messageId,
601
474
  query.id,
602
- {
603
- answerCallbackQuery: deps.answerCallbackQuery,
604
- target,
605
- editInteractiveMessage:
606
- deps.editInteractiveMessage ?? (async () => {}),
607
- sendInteractiveMessage:
608
- deps.sendInteractiveMessage ?? (async () => undefined),
609
- sendRichMessage: deps.sendSectionRichMessage ?? (async () => {
610
- throw new Error("Rich Message delivery is unavailable");
611
- }),
612
- enqueuePrompt: deps.enqueueSectionPrompt
613
- ? (prompt: string) =>
614
- deps.enqueueSectionPrompt!(prompt, ctx, target, query)
615
- : async () => {},
616
- deleteMessage: deps.deleteMessage ?? (async () => {}),
617
- },
475
+ sectionDeps,
618
476
  );
619
477
  if (handled) return;
620
478
  } else if (action === "settings") {
621
- if (typeof chatId === "number" && typeof messageId === "number") {
622
- const handled = await handleTelegramSectionSettingsOpen(
623
- deps.sectionRegistry,
624
- token,
625
- chatId,
626
- messageId,
627
- query.id,
628
- {
629
- answerCallbackQuery: deps.answerCallbackQuery,
630
- target,
631
- editInteractiveMessage:
632
- deps.editInteractiveMessage ?? (async () => {}),
633
- sendInteractiveMessage:
634
- deps.sendInteractiveMessage ?? (async () => undefined),
635
- sendRichMessage: deps.sendSectionRichMessage ?? (async () => {
636
- throw new Error("Rich Message delivery is unavailable");
637
- }),
638
- enqueuePrompt: deps.enqueueSectionPrompt
639
- ? (prompt: string) =>
640
- deps.enqueueSectionPrompt!(prompt, ctx, target, query)
641
- : async () => {},
642
- deleteMessage: deps.deleteMessage ?? (async () => {}),
643
- },
644
- );
645
- if (handled) return;
646
- }
479
+ const handled = await handleTelegramSectionSettingsOpen(
480
+ deps.sectionRegistry,
481
+ token,
482
+ chatId,
483
+ messageId,
484
+ query.id,
485
+ sectionDeps,
486
+ );
487
+ if (handled) return;
647
488
  } else {
648
489
  const handled = await handleTelegramSectionCallback(
649
490
  deps.sectionRegistry,
@@ -653,22 +494,7 @@ export async function handleTelegramMenuCallbackRuntime<
653
494
  chatId,
654
495
  messageId,
655
496
  query.id,
656
- {
657
- answerCallbackQuery: deps.answerCallbackQuery,
658
- target,
659
- editInteractiveMessage:
660
- deps.editInteractiveMessage ?? (async () => {}),
661
- sendInteractiveMessage:
662
- deps.sendInteractiveMessage ?? (async () => undefined),
663
- sendRichMessage: deps.sendSectionRichMessage ?? (async () => {
664
- throw new Error("Rich Message delivery is unavailable");
665
- }),
666
- enqueuePrompt: deps.enqueueSectionPrompt
667
- ? (prompt: string) =>
668
- deps.enqueueSectionPrompt!(prompt, ctx, target, query)
669
- : async () => {},
670
- deleteMessage: deps.deleteMessage ?? (async () => {}),
671
- },
497
+ sectionDeps,
672
498
  );
673
499
  if (handled) return;
674
500
  }
@@ -703,7 +529,8 @@ export async function handleTelegramMenuCallbackRuntime<
703
529
  deps.updateStatus(ctx);
704
530
  },
705
531
  getCurrentThinkingLevel: deps.getThinkingLevel,
706
- updateThinkingMenuMessage: () => deps.updateThinkingMenuMessage(state, ctx),
532
+ updateThinkingMenuMessage: () =>
533
+ deps.updateThinkingMenuMessage(state, ctx),
707
534
  answerCallbackQuery: deps.answerCallbackQuery,
708
535
  isVoiceReplyActive: deps.isVoiceReplyActive,
709
536
  },
@@ -756,45 +583,6 @@ export async function handleTelegramMenuCallbackRuntime<
756
583
  });
757
584
  }
758
585
 
759
- export interface TelegramMenuActionRuntimeWithStateBuilderDeps<
760
- TModel extends MenuModel = MenuModel,
761
- TContext extends TelegramModelMenuStateBuilderContext<TModel> =
762
- TelegramModelMenuStateBuilderContext<TModel>,
763
- >
764
- extends
765
- Omit<TelegramMenuActionRuntimeDeps<TContext, TModel>, "getModelMenuState">,
766
- TelegramModelMenuStateBuilderDeps<TModel, TContext> {
767
- isVoiceReplyActive?: () => boolean;
768
- }
769
-
770
- export function createTelegramMenuActionRuntimeWithStateBuilder<
771
- TModel extends MenuModel = MenuModel,
772
- TContext extends TelegramModelMenuStateBuilderContext<TModel> =
773
- TelegramModelMenuStateBuilderContext<TModel>,
774
- >(
775
- deps: TelegramMenuActionRuntimeWithStateBuilderDeps<TModel, TContext>,
776
- ): TelegramMenuActionRuntime<TContext, TModel> {
777
- return createTelegramMenuActionRuntime({
778
- getModelMenuState: createTelegramModelMenuStateBuilder({
779
- runtime: deps.runtime,
780
- createSettingsManager: deps.createSettingsManager,
781
- getActiveModel: deps.getActiveModel,
782
- }),
783
- getActiveModel: deps.getActiveModel,
784
- getThinkingLevel: deps.getThinkingLevel,
785
- getQueueItemCount: deps.getQueueItemCount,
786
- getPendingCancellationCount: deps.getPendingCancellationCount,
787
- buildStatusHtml: deps.buildStatusHtml,
788
- storeModelMenuState: deps.storeModelMenuState,
789
- isIdle: deps.isIdle,
790
- canOfferInFlightModelSwitch: deps.canOfferInFlightModelSwitch,
791
- sendTextReply: deps.sendTextReply,
792
- editInteractiveMessage: deps.editInteractiveMessage,
793
- sendInteractiveMessage: deps.sendInteractiveMessage,
794
- sectionRegistry: deps.sectionRegistry,
795
- });
796
- }
797
-
798
586
  export function createTelegramMenuActionRuntime<
799
587
  TContext,
800
588
  TModel extends MenuModel = MenuModel,
@@ -823,15 +611,26 @@ export function createTelegramMenuActionRuntime<
823
611
  deps.isVoiceReplyActive?.(),
824
612
  deps.getPendingCancellationCount?.() ?? 0,
825
613
  ),
826
- sendStatusMessage: (chatId, replyToMessageId, ctx, threadId) =>
827
- openTelegramStatusMenu({
614
+ sendStatusMessage: (chatId, replyToMessageId, ctx, threadId, options) => {
615
+ const delivery = createTelegramMenuDelivery(
616
+ { chatId, threadId },
617
+ options,
618
+ deps,
619
+ );
620
+ return openTelegramStatusMenu({
828
621
  isIdle: () => deps.isIdle(ctx),
829
622
  sendBusyMessage: async () => {
830
623
  await deps.sendTextReply(
831
624
  chatId,
832
625
  replyToMessageId,
833
626
  "<b>⏳ Cannot open status while Pi is busy. Send /abort, /next, or /stop.</b>",
834
- { target: { chatId, threadId }, parseMode: "HTML" },
627
+ {
628
+ target: { chatId, threadId },
629
+ parseMode: "HTML",
630
+ ...(delivery.assertAuthority
631
+ ? { assertAuthority: delivery.assertAuthority }
632
+ : {}),
633
+ },
835
634
  );
836
635
  },
837
636
  getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
@@ -851,16 +650,26 @@ export function createTelegramMenuActionRuntime<
851
650
  statusHtml,
852
651
  activeModel,
853
652
  thinkingLevel,
854
- deps,
653
+ {
654
+ ...deps,
655
+ sendInteractiveMessage: delivery.sendInteractiveMessage,
656
+ },
855
657
  queueItemCount,
856
658
  deps.sectionRegistry,
857
659
  deps.isVoiceReplyActive?.(),
858
660
  deps.getPendingCancellationCount?.() ?? 0,
859
661
  ),
860
- storeModelMenuState: deps.storeModelMenuState,
861
- }),
862
- openModelMenu: (chatId, replyToMessageId, ctx, threadId) =>
863
- openTelegramModelMenu({
662
+ storeModelMenuState: delivery.storeModelMenuState,
663
+ });
664
+ },
665
+ openModelMenu: (chatId, replyToMessageId, ctx, threadId, options) => {
666
+ const delivery = createTelegramMenuDelivery(
667
+ { chatId, threadId },
668
+ options,
669
+ deps,
670
+ );
671
+ const assertAuthority = delivery.assertAuthority;
672
+ return openTelegramModelMenu({
864
673
  isIdle: () => deps.isIdle(ctx),
865
674
  canOfferInFlightModelSwitch: () =>
866
675
  deps.canOfferInFlightModelSwitch(ctx),
@@ -869,7 +678,11 @@ export function createTelegramMenuActionRuntime<
869
678
  chatId,
870
679
  replyToMessageId,
871
680
  "<b>⏳ Cannot switch model while Pi is busy. Send /abort, /next, or /stop.</b>",
872
- { target: { chatId, threadId }, parseMode: "HTML" },
681
+ {
682
+ target: { chatId, threadId },
683
+ parseMode: "HTML",
684
+ ...(assertAuthority ? { assertAuthority } : {}),
685
+ },
873
686
  );
874
687
  },
875
688
  sendNoModelsMessage: async () => {
@@ -877,24 +690,38 @@ export function createTelegramMenuActionRuntime<
877
690
  chatId,
878
691
  replyToMessageId,
879
692
  "<b>🚫 No available models with configured auth.</b>",
880
- { target: { chatId, threadId }, parseMode: "HTML" },
693
+ {
694
+ target: { chatId, threadId },
695
+ parseMode: "HTML",
696
+ ...(assertAuthority ? { assertAuthority } : {}),
697
+ },
881
698
  );
882
699
  },
883
700
  getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
884
701
  getActiveModel: () => deps.getActiveModel(ctx),
885
702
  sendModelMenu: (state, activeModel) =>
886
- sendTelegramModelMenuMessage(state, activeModel, deps),
887
- storeModelMenuState: deps.storeModelMenuState,
888
- }),
889
- openThinkingMenu: (chatId, _replyToMessageId, ctx) =>
890
- openTelegramThinkingMenu({
891
- getModelMenuState: () => deps.getModelMenuState(chatId, ctx),
703
+ sendTelegramModelMenuMessage(state, activeModel, {
704
+ ...deps,
705
+ sendInteractiveMessage: delivery.sendInteractiveMessage,
706
+ }),
707
+ storeModelMenuState: delivery.storeModelMenuState,
708
+ });
709
+ },
710
+ openThinkingMenu: (chatId, _replyToMessageId, ctx, threadId, options) => {
711
+ const delivery = createTelegramMenuDelivery(
712
+ { chatId, threadId },
713
+ options,
714
+ deps,
715
+ );
716
+ return openTelegramThinkingMenu({
717
+ getModelMenuState: () => deps.getModelMenuState(chatId, ctx, threadId),
892
718
  getActiveModel: () => deps.getActiveModel(ctx),
893
719
  getThinkingLevel: deps.getThinkingLevel,
894
- storeModelMenuState: deps.storeModelMenuState,
720
+ storeModelMenuState: delivery.storeModelMenuState,
895
721
  editInteractiveMessage: deps.editInteractiveMessage,
896
- sendInteractiveMessage: deps.sendInteractiveMessage,
722
+ sendInteractiveMessage: delivery.sendInteractiveMessage,
897
723
  isVoiceReplyActive: deps.isVoiceReplyActive,
898
- }),
724
+ });
725
+ },
899
726
  };
900
727
  }
package/lib/model.ts CHANGED
@@ -4,7 +4,10 @@
4
4
  * Owns model identity, thinking levels, scoped resolution, current-model state, and in-flight model switching
5
5
  */
6
6
 
7
- import type { PendingTelegramTurn } from "./queue.ts";
7
+ import {
8
+ truncateTelegramQueueSummary,
9
+ type PendingTelegramTurn,
10
+ } from "./queue.ts";
8
11
  import { TELEGRAM_PREFIX } from "./turns.ts";
9
12
 
10
13
  export interface MenuModel {
@@ -428,24 +431,6 @@ export function restartTelegramModelSwitchContinuation<
428
431
  return true;
429
432
  }
430
433
 
431
- function truncateTelegramModelSwitchStatusSummary(
432
- text: string,
433
- maxWords = 4,
434
- maxLength = 32,
435
- ): string {
436
- const normalized = text.replace(/\s+/g, " ").trim();
437
- if (!normalized) return "";
438
- const words = normalized.split(" ");
439
- let summary = words.slice(0, maxWords).join(" ");
440
- if (summary.length === 0) summary = normalized;
441
- if (summary.length > maxLength) {
442
- summary = summary.slice(0, maxLength).trimEnd();
443
- }
444
- return summary.length < normalized.length || words.length > maxWords
445
- ? `${summary}…`
446
- : summary;
447
- }
448
-
449
434
  export function buildTelegramModelSwitchContinuationText<
450
435
  TModel extends MenuModel,
451
436
  >(
@@ -473,8 +458,10 @@ export function buildTelegramModelSwitchContinuationTurn<
473
458
  laneOrder: number;
474
459
  }): PendingTelegramTurn {
475
460
  const modelLabel = `${options.selection.model.provider}/${options.selection.model.id}`;
476
- const statusLabel = truncateTelegramModelSwitchStatusSummary(
461
+ const statusLabel = truncateTelegramQueueSummary(
477
462
  `continue on ${options.selection.model.id}`,
463
+ 4,
464
+ 32,
478
465
  );
479
466
  return {
480
467
  kind: "prompt",
@@ -643,8 +630,7 @@ export function createTelegramModelSwitchController<TContext, TSelection>(
643
630
  deps: TelegramModelSwitchControllerDeps<TContext, TSelection>,
644
631
  ): TelegramModelSwitchController<TContext, TSelection> {
645
632
  let pendingContinuationTurn:
646
- | TelegramModelSwitchContinuationSource
647
- | undefined;
633
+ TelegramModelSwitchContinuationSource | undefined;
648
634
  const triggerPendingAbort = (ctx: TContext): boolean => {
649
635
  const turn = pendingContinuationTurn ?? deps.getActiveTurn();
650
636
  if (