@llblab/pi-telegram 0.7.1 → 0.8.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.
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Telegram attachment domain helpers
2
+ * Telegram outbound attachment helpers
3
3
  * Zones: telegram outbound, pi agent tool, filesystem
4
- * Owns telegram_attach registration, attachment queueing, and attachment delivery so Telegram file output stays in one domain module
4
+ * Owns telegram_attach registration, outbound attachment queueing, and delivery so Telegram file output stays in one domain module
5
5
  */
6
6
 
7
7
  import { stat } from "node:fs/promises";
@@ -16,7 +16,7 @@ const MAX_ATTACHMENTS_PER_TURN = 10;
16
16
 
17
17
  export const TELEGRAM_OUTBOUND_ATTACHMENT_DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
18
18
 
19
- export function getTelegramAttachmentByteLimitFromEnv(
19
+ export function getTelegramOutboundAttachmentByteLimitFromEnv(
20
20
  env: NodeJS.ProcessEnv,
21
21
  names: string[],
22
22
  defaultValue = TELEGRAM_OUTBOUND_ATTACHMENT_DEFAULT_MAX_BYTES,
@@ -31,17 +31,17 @@ export function getTelegramAttachmentByteLimitFromEnv(
31
31
  }
32
32
 
33
33
  export const TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES =
34
- getTelegramAttachmentByteLimitFromEnv(process.env, [
34
+ getTelegramOutboundAttachmentByteLimitFromEnv(process.env, [
35
35
  "PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES",
36
36
  "TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES",
37
37
  ]);
38
38
 
39
- export interface TelegramAttachmentToolResult {
39
+ export interface TelegramOutboundAttachmentToolResult {
40
40
  content: Array<{ type: "text"; text: string }>;
41
41
  details: { paths: string[] };
42
42
  }
43
43
 
44
- export interface TelegramAttachmentRuntimeEventRecorderPort {
44
+ export interface TelegramOutboundAttachmentRuntimeEventRecorderPort {
45
45
  recordRuntimeEvent?: (
46
46
  category: string,
47
47
  error: unknown,
@@ -49,28 +49,28 @@ export interface TelegramAttachmentRuntimeEventRecorderPort {
49
49
  ) => void;
50
50
  }
51
51
 
52
- export interface TelegramAttachmentToolRegistrationDeps extends TelegramAttachmentRuntimeEventRecorderPort {
52
+ export interface TelegramOutboundAttachmentToolRegistrationDeps extends TelegramOutboundAttachmentRuntimeEventRecorderPort {
53
53
  maxAttachmentsPerTurn?: number;
54
54
  maxAttachmentSizeBytes?: number;
55
- getActiveTurn: () => TelegramAttachmentQueueTargetView | undefined;
55
+ getActiveTurn: () => TelegramOutboundAttachmentQueueTargetView | undefined;
56
56
  statPath?: (path: string) => Promise<{ isFile(): boolean; size?: number }>;
57
57
  }
58
58
 
59
- export interface TelegramQueuedAttachmentView {
59
+ export interface TelegramQueuedOutboundAttachmentView {
60
60
  path: string;
61
61
  fileName: string;
62
62
  }
63
63
 
64
- export interface TelegramAttachmentQueueTargetView {
65
- queuedAttachments: TelegramQueuedAttachmentView[];
64
+ export interface TelegramOutboundAttachmentQueueTargetView {
65
+ queuedAttachments: TelegramQueuedOutboundAttachmentView[];
66
66
  }
67
67
 
68
- export interface TelegramQueuedAttachmentTurnView extends TelegramAttachmentQueueTargetView {
68
+ export interface TelegramQueuedOutboundAttachmentTurnView extends TelegramOutboundAttachmentQueueTargetView {
69
69
  chatId: number;
70
70
  replyToMessageId: number;
71
71
  }
72
72
 
73
- function isTelegramPhotoAttachmentPath(path: string): boolean {
73
+ function isTelegramOutboundPhotoAttachmentPath(path: string): boolean {
74
74
  const normalized = path.toLowerCase();
75
75
  return (
76
76
  normalized.endsWith(".jpg") ||
@@ -81,7 +81,7 @@ function isTelegramPhotoAttachmentPath(path: string): boolean {
81
81
  );
82
82
  }
83
83
 
84
- function formatTelegramAttachmentSizeLimitError(
84
+ function formatTelegramOutboundAttachmentSizeLimitError(
85
85
  size: number,
86
86
  maxSize: number,
87
87
  path?: string,
@@ -90,14 +90,14 @@ function formatTelegramAttachmentSizeLimitError(
90
90
  return path ? `${message}: ${path}` : message;
91
91
  }
92
92
 
93
- function formatTelegramAttachmentToolResultText(count: number): string {
93
+ function formatTelegramOutboundAttachmentToolResultText(count: number): string {
94
94
  // Pi's compact tool rows need an empty first line to visually separate header and result
95
95
  return ["", `Queued ${count} Telegram attachment(s).`].join("\n");
96
96
  }
97
97
 
98
- export function registerTelegramAttachmentTool(
98
+ export function registerTelegramOutboundAttachmentTool(
99
99
  pi: ExtensionAPI,
100
- deps: TelegramAttachmentToolRegistrationDeps,
100
+ deps: TelegramOutboundAttachmentToolRegistrationDeps,
101
101
  ): void {
102
102
  const maxAttachmentsPerTurn =
103
103
  deps.maxAttachmentsPerTurn ?? MAX_ATTACHMENTS_PER_TURN;
@@ -120,7 +120,7 @@ export function registerTelegramAttachmentTool(
120
120
  }),
121
121
  async execute(_toolCallId, params) {
122
122
  try {
123
- return await queueTelegramAttachments({
123
+ return await queueTelegramOutboundAttachments({
124
124
  activeTurn: deps.getActiveTurn(),
125
125
  paths: params.paths,
126
126
  maxAttachmentsPerTurn,
@@ -138,7 +138,7 @@ export function registerTelegramAttachmentTool(
138
138
  });
139
139
  }
140
140
 
141
- export interface TelegramQueuedAttachmentDeliveryDeps {
141
+ export interface TelegramQueuedOutboundAttachmentDeliveryDeps {
142
142
  sendMultipart: (
143
143
  method: string,
144
144
  fields: Record<string, string>,
@@ -160,13 +160,13 @@ export interface TelegramQueuedAttachmentDeliveryDeps {
160
160
  maxAttachmentSizeBytes?: number;
161
161
  }
162
162
 
163
- export async function queueTelegramAttachments(options: {
164
- activeTurn: TelegramAttachmentQueueTargetView | undefined;
163
+ export async function queueTelegramOutboundAttachments(options: {
164
+ activeTurn: TelegramOutboundAttachmentQueueTargetView | undefined;
165
165
  paths: string[];
166
166
  maxAttachmentsPerTurn: number;
167
167
  maxAttachmentSizeBytes?: number;
168
168
  statPath?: (path: string) => Promise<{ isFile(): boolean; size?: number }>;
169
- }): Promise<TelegramAttachmentToolResult> {
169
+ }): Promise<TelegramOutboundAttachmentToolResult> {
170
170
  if (!options.activeTurn) {
171
171
  throw new Error(
172
172
  "telegram_attach can only be used while replying to an active Telegram turn",
@@ -180,7 +180,7 @@ export async function queueTelegramAttachments(options: {
180
180
  `Attachment limit reached (${options.maxAttachmentsPerTurn})`,
181
181
  );
182
182
  }
183
- const pendingAttachments: TelegramQueuedAttachmentView[] = [];
183
+ const pendingAttachments: TelegramQueuedOutboundAttachmentView[] = [];
184
184
  for (const inputPath of options.paths) {
185
185
  const stats = await (options.statPath ?? stat)(inputPath);
186
186
  if (!stats.isFile()) {
@@ -192,7 +192,7 @@ export async function queueTelegramAttachments(options: {
192
192
  stats.size > options.maxAttachmentSizeBytes
193
193
  ) {
194
194
  throw new Error(
195
- formatTelegramAttachmentSizeLimitError(
195
+ formatTelegramOutboundAttachmentSizeLimitError(
196
196
  stats.size,
197
197
  options.maxAttachmentSizeBytes,
198
198
  inputPath,
@@ -210,20 +210,20 @@ export async function queueTelegramAttachments(options: {
210
210
  content: [
211
211
  {
212
212
  type: "text",
213
- text: formatTelegramAttachmentToolResultText(added.length),
213
+ text: formatTelegramOutboundAttachmentToolResultText(added.length),
214
214
  },
215
215
  ],
216
216
  details: { paths: added },
217
217
  };
218
218
  }
219
219
 
220
- export function createTelegramQueuedAttachmentSender(
221
- deps: TelegramQueuedAttachmentDeliveryDeps,
220
+ export function createTelegramQueuedOutboundAttachmentSender(
221
+ deps: TelegramQueuedOutboundAttachmentDeliveryDeps,
222
222
  ) {
223
223
  return async function sendQueuedAttachments(
224
- turn: TelegramQueuedAttachmentTurnView,
224
+ turn: TelegramQueuedOutboundAttachmentTurnView,
225
225
  ): Promise<void> {
226
- await sendQueuedTelegramAttachments(turn, {
226
+ await sendQueuedTelegramOutboundAttachments(turn, {
227
227
  ...deps,
228
228
  maxAttachmentSizeBytes:
229
229
  deps.maxAttachmentSizeBytes ?? TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES,
@@ -231,9 +231,9 @@ export function createTelegramQueuedAttachmentSender(
231
231
  };
232
232
  }
233
233
 
234
- export async function sendQueuedTelegramAttachments(
235
- turn: TelegramQueuedAttachmentTurnView,
236
- deps: TelegramQueuedAttachmentDeliveryDeps,
234
+ export async function sendQueuedTelegramOutboundAttachments(
235
+ turn: TelegramQueuedOutboundAttachmentTurnView,
236
+ deps: TelegramQueuedOutboundAttachmentDeliveryDeps,
237
237
  ): Promise<void> {
238
238
  for (const attachment of turn.queuedAttachments) {
239
239
  try {
@@ -241,14 +241,14 @@ export async function sendQueuedTelegramAttachments(
241
241
  const stats = await (deps.statPath ?? stat)(attachment.path);
242
242
  if (stats.size > deps.maxAttachmentSizeBytes) {
243
243
  throw new Error(
244
- formatTelegramAttachmentSizeLimitError(
244
+ formatTelegramOutboundAttachmentSizeLimitError(
245
245
  stats.size,
246
246
  deps.maxAttachmentSizeBytes,
247
247
  ),
248
248
  );
249
249
  }
250
250
  }
251
- const isPhoto = isTelegramPhotoAttachmentPath(attachment.path);
251
+ const isPhoto = isTelegramOutboundPhotoAttachmentPath(attachment.path);
252
252
  const method = isPhoto ? "sendPhoto" : "sendDocument";
253
253
  const fieldName = isPhoto ? "photo" : "document";
254
254
  const replyParameters = buildTelegramMultipartReplyParameters(
@@ -97,6 +97,55 @@ export interface TelegramVoiceReplySenderDeps {
97
97
  ) => void;
98
98
  }
99
99
 
100
+ export interface TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup = unknown> {
101
+ execCommand: TelegramVoiceReplySenderDeps["execCommand"];
102
+ getHandlers?: () => TelegramOutboundHandlerConfig[] | undefined;
103
+ sendTextReply: (
104
+ chatId: number,
105
+ replyToMessageId: number | undefined,
106
+ text: string,
107
+ options?: { parseMode?: "HTML" },
108
+ ) => Promise<number | undefined>;
109
+ sendMarkdownReply: (
110
+ chatId: number,
111
+ replyToMessageId: number | undefined,
112
+ markdown: string,
113
+ options?: { replyMarkup?: TReplyMarkup },
114
+ ) => Promise<number | undefined>;
115
+ cwd?: string;
116
+ recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
117
+ }
118
+
119
+ export interface TelegramInlineKeyboardLike {
120
+ inline_keyboard: Array<Array<{ text: string; callback_data: string }>>;
121
+ }
122
+
123
+ export interface TelegramOutboundTextTransformOptions<TReplyMarkup = unknown> {
124
+ handlers?: TelegramOutboundHandlerConfig[];
125
+ cwd?: string;
126
+ execCommand: TelegramVoiceReplySenderDeps["execCommand"];
127
+ recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
128
+ replyMarkup?: TReplyMarkup;
129
+ }
130
+
131
+ export interface TelegramOutboundTextTransformResult<TReplyMarkup = unknown> {
132
+ text: string;
133
+ replyMarkup?: TReplyMarkup;
134
+ }
135
+
136
+ export interface TelegramOutboundTextPreviewRuntimeDeps<TReplyMarkup = unknown> {
137
+ execCommand: TelegramVoiceReplySenderDeps["execCommand"];
138
+ getHandlers?: () => TelegramOutboundHandlerConfig[] | undefined;
139
+ finalizeMarkdownPreview: (
140
+ chatId: number,
141
+ markdown: string,
142
+ replyToMessageId: number,
143
+ options?: { replyMarkup?: TReplyMarkup },
144
+ ) => Promise<boolean>;
145
+ cwd?: string;
146
+ recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
147
+ }
148
+
100
149
  interface TelegramTopLevelHtmlComment {
101
150
  raw: string;
102
151
  content: string;
@@ -665,6 +714,202 @@ export async function generateTelegramVoiceReplyFile(
665
714
  });
666
715
  }
667
716
 
717
+ function getOutboundTextTemplateValues(text: string): Record<string, string> {
718
+ return { text, type: "text" };
719
+ }
720
+
721
+ async function transformTelegramOutboundTextWithHandler(
722
+ text: string,
723
+ options: {
724
+ handler: TelegramOutboundHandlerConfig;
725
+ cwd: string;
726
+ execCommand: TelegramVoiceReplySenderDeps["execCommand"];
727
+ },
728
+ ): Promise<string> {
729
+ const values = getOutboundTextTemplateValues(text);
730
+ const steps = getTelegramVoiceHandlerCompositionSteps(options.handler);
731
+ if (steps.length > 0) {
732
+ const startedAt = Date.now();
733
+ let stdout = text;
734
+ for (const [index, step] of steps.entries()) {
735
+ try {
736
+ const result = await runVoiceReplyCommand(
737
+ `Outbound text template step ${index + 1}`,
738
+ step,
739
+ values,
740
+ {
741
+ cwd: options.cwd,
742
+ timeout: getVoiceReplyCompositionStepTimeout(
743
+ getVoiceReplyTimeout(options.handler),
744
+ step,
745
+ startedAt,
746
+ ),
747
+ execCommand: options.execCommand,
748
+ stdin: stdout,
749
+ },
750
+ );
751
+ stdout = result.stdout;
752
+ } catch (error) {
753
+ if (typeof step === "object" && step.critical) throw error;
754
+ stdout = "";
755
+ }
756
+ if (!stdout) stdout = text;
757
+ }
758
+ return stdout.trim() || text;
759
+ }
760
+ const result = await runVoiceReplyCommand(
761
+ "Outbound text template",
762
+ options.handler,
763
+ values,
764
+ {
765
+ cwd: options.cwd,
766
+ timeout: getVoiceReplyTimeout(options.handler),
767
+ execCommand: options.execCommand,
768
+ stdin: text,
769
+ },
770
+ );
771
+ return result.stdout.trim() || text;
772
+ }
773
+
774
+ export async function transformTelegramOutboundText(
775
+ text: string,
776
+ options: {
777
+ handlers?: TelegramOutboundHandlerConfig[];
778
+ cwd?: string;
779
+ execCommand: TelegramVoiceReplySenderDeps["execCommand"];
780
+ recordRuntimeEvent?: TelegramVoiceReplySenderDeps["recordRuntimeEvent"];
781
+ },
782
+ ): Promise<string> {
783
+ let transformed = text;
784
+ for (const handler of findTelegramOutboundHandlers(options.handlers, "text")) {
785
+ try {
786
+ transformed = await transformTelegramOutboundTextWithHandler(
787
+ transformed,
788
+ {
789
+ handler,
790
+ cwd: options.cwd ?? process.cwd(),
791
+ execCommand: options.execCommand,
792
+ },
793
+ );
794
+ } catch (error) {
795
+ options.recordRuntimeEvent?.("outbound-text-handler", error, {
796
+ handler: outboundHandlerMatchesType(handler, "text") ? "text" : "unknown",
797
+ });
798
+ }
799
+ }
800
+ return transformed;
801
+ }
802
+
803
+ function isTelegramInlineKeyboardLike(
804
+ replyMarkup: unknown,
805
+ ): replyMarkup is TelegramInlineKeyboardLike {
806
+ if (!replyMarkup || typeof replyMarkup !== "object") return false;
807
+ const keyboard = (replyMarkup as { inline_keyboard?: unknown }).inline_keyboard;
808
+ return Array.isArray(keyboard);
809
+ }
810
+
811
+ async function transformTelegramOutboundReplyMarkup<TReplyMarkup>(
812
+ replyMarkup: TReplyMarkup | undefined,
813
+ options: Omit<TelegramOutboundTextTransformOptions, "replyMarkup">,
814
+ ): Promise<TReplyMarkup | undefined> {
815
+ if (!isTelegramInlineKeyboardLike(replyMarkup)) return replyMarkup;
816
+ const translatedRows = [];
817
+ for (const row of replyMarkup.inline_keyboard) {
818
+ const translatedRow = [];
819
+ for (const button of row) {
820
+ const text = await transformTelegramOutboundText(button.text, options);
821
+ translatedRow.push({ ...button, text });
822
+ }
823
+ translatedRows.push(translatedRow);
824
+ }
825
+ return { ...replyMarkup, inline_keyboard: translatedRows } as TReplyMarkup;
826
+ }
827
+
828
+ export async function transformTelegramOutboundTextReply<TReplyMarkup = unknown>(
829
+ text: string,
830
+ options: TelegramOutboundTextTransformOptions<TReplyMarkup>,
831
+ ): Promise<TelegramOutboundTextTransformResult<TReplyMarkup>> {
832
+ const transformOptions = {
833
+ handlers: options.handlers,
834
+ cwd: options.cwd,
835
+ execCommand: options.execCommand,
836
+ recordRuntimeEvent: options.recordRuntimeEvent,
837
+ };
838
+ const transformedText = await transformTelegramOutboundText(
839
+ text,
840
+ transformOptions,
841
+ );
842
+ const replyMarkup = await transformTelegramOutboundReplyMarkup(
843
+ options.replyMarkup,
844
+ transformOptions,
845
+ );
846
+ return { text: transformedText, ...(replyMarkup ? { replyMarkup } : {}) };
847
+ }
848
+
849
+ export function createTelegramOutboundTextReplyRuntime<TReplyMarkup = unknown>(
850
+ deps: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>,
851
+ ): Pick<
852
+ TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>,
853
+ "sendTextReply" | "sendMarkdownReply"
854
+ > {
855
+ return {
856
+ sendTextReply: async (chatId, replyToMessageId, text, options) => {
857
+ const transformed = await transformTelegramOutboundText(text, {
858
+ handlers: deps.getHandlers?.(),
859
+ cwd: deps.cwd,
860
+ execCommand: deps.execCommand,
861
+ recordRuntimeEvent: deps.recordRuntimeEvent,
862
+ });
863
+ return deps.sendTextReply(chatId, replyToMessageId, transformed, options);
864
+ },
865
+ sendMarkdownReply: async (chatId, replyToMessageId, markdown, options) => {
866
+ const transformed = await transformTelegramOutboundTextReply(markdown, {
867
+ handlers: deps.getHandlers?.(),
868
+ cwd: deps.cwd,
869
+ execCommand: deps.execCommand,
870
+ recordRuntimeEvent: deps.recordRuntimeEvent,
871
+ replyMarkup: options?.replyMarkup,
872
+ });
873
+ return deps.sendMarkdownReply(chatId, replyToMessageId, transformed.text, {
874
+ ...options,
875
+ ...(transformed.replyMarkup
876
+ ? { replyMarkup: transformed.replyMarkup }
877
+ : {}),
878
+ });
879
+ },
880
+ };
881
+ }
882
+
883
+ export function createTelegramOutboundTextPreviewRuntime<TReplyMarkup = unknown>(
884
+ deps: TelegramOutboundTextPreviewRuntimeDeps<TReplyMarkup>,
885
+ ): Pick<
886
+ TelegramOutboundTextPreviewRuntimeDeps<TReplyMarkup>,
887
+ "finalizeMarkdownPreview"
888
+ > {
889
+ return {
890
+ finalizeMarkdownPreview: async (chatId, markdown, replyToMessageId, options) => {
891
+ const transformed = await transformTelegramOutboundTextReply(markdown, {
892
+ handlers: deps.getHandlers?.(),
893
+ cwd: deps.cwd,
894
+ execCommand: deps.execCommand,
895
+ recordRuntimeEvent: deps.recordRuntimeEvent,
896
+ replyMarkup: options?.replyMarkup,
897
+ });
898
+ return deps.finalizeMarkdownPreview(
899
+ chatId,
900
+ transformed.text,
901
+ replyToMessageId,
902
+ {
903
+ ...options,
904
+ ...(transformed.replyMarkup
905
+ ? { replyMarkup: transformed.replyMarkup }
906
+ : {}),
907
+ },
908
+ );
909
+ },
910
+ };
911
+ }
912
+
668
913
  export interface TelegramOutboundReplyPlan<TReplyMarkup = unknown> {
669
914
  markdown: string;
670
915
  replyMarkup?: TReplyMarkup;
package/lib/prompts.ts CHANGED
@@ -14,7 +14,7 @@ Telegram bridge extension is active.
14
14
  Inbound context:
15
15
  - \`[telegram]\` marks Telegram-originated messages.
16
16
  - \`[reply]\` is quoted context from the replied-to message, not a new instruction by itself. Use it to resolve references like "this", "it", or "that message"; the actual instruction is before [reply] unless it explicitly asks to act on the quote.
17
- - \`[attachments]\` gives a base directory plus relative local files; resolve and read them as needed. \`[outputs]\` contains attachment-handler stdout such as transcriptions or extracted text for those attachments.
17
+ - \`[attachments]\` gives a base directory plus relative local files; resolve and read them as needed. \`[outputs]\` contains inbound-handler stdout such as transcriptions or extracted text for those attachments.
18
18
  - Unknown \`[callback] ...\` messages may be intended for another extension; if you see one, say the callback was not handled and the environment may be misconfigured.
19
19
 
20
20
  Telegram-visible output:
package/lib/routing.ts CHANGED
@@ -7,13 +7,14 @@
7
7
  import * as OutboundHandlers from "./outbound-handlers.ts";
8
8
  import * as Commands from "./commands.ts";
9
9
  import type { TelegramConfigStore } from "./config.ts";
10
- import type { TelegramAttachmentHandlerRuntime } from "./attachment-handlers.ts";
10
+ import type { TelegramInboundHandlerRuntime } from "./inbound-handlers.ts";
11
11
  import * as Media from "./media.ts";
12
12
  import * as Menu from "./menu.ts";
13
13
  import * as Model from "./model.ts";
14
14
  import * as Queue from "./queue.ts";
15
15
  import * as PromptTemplates from "./prompt-templates.ts";
16
16
  import type { TelegramBridgeRuntime } from "./runtime.ts";
17
+ import * as TextGroups from "./text-groups.ts";
17
18
  import * as Turns from "./turns.ts";
18
19
  import * as Updates from "./updates.ts";
19
20
 
@@ -39,6 +40,7 @@ export interface TelegramInboundRouteRuntimeDeps<
39
40
  bridgeRuntime: TelegramBridgeRuntime;
40
41
  activeTurnRuntime: Queue.TelegramActiveTurnStore;
41
42
  mediaGroupRuntime: Media.TelegramMediaGroupController<TMessage, TContext>;
43
+ textGroupRuntime: TextGroups.TelegramTextGroupController<TMessage, TContext>;
42
44
  telegramQueueStore: Queue.TelegramQueueStateStore<TContext>;
43
45
  queueMutationRuntime: Queue.TelegramQueueMutationController<TContext>;
44
46
  modelMenuRuntime: Menu.TelegramModelMenuRuntime<TModel>;
@@ -58,7 +60,7 @@ export interface TelegramInboundRouteRuntimeDeps<
58
60
  ctx: TContext,
59
61
  ) => Promise<boolean>;
60
62
  buttonActionStore?: OutboundHandlers.TelegramButtonActionStore;
61
- attachmentHandlerRuntime: TelegramAttachmentHandlerRuntime<TContext>;
63
+ inboundHandlerRuntime: TelegramInboundHandlerRuntime<TContext>;
62
64
  updateStatus: (ctx: TContext, error?: string) => void;
63
65
  dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
64
66
  answerCallbackQuery: (
@@ -203,7 +205,7 @@ export function createTelegramInboundRouteRuntime<
203
205
  >({
204
206
  allocateQueueOrder: deps.bridgeRuntime.queue.allocateItemOrder,
205
207
  downloadFile: deps.downloadFile,
206
- processAttachments: deps.attachmentHandlerRuntime.process,
208
+ processAttachments: deps.inboundHandlerRuntime.process,
207
209
  });
208
210
  const enqueueContinueTurn = async (
209
211
  message: TMessage,
@@ -321,6 +323,14 @@ export function createTelegramInboundRouteRuntime<
321
323
  mediaGroups: deps.mediaGroupRuntime,
322
324
  dispatchMessages: commandOrPrompt.dispatchMessages,
323
325
  });
326
+ const textDispatch = TextGroups.createTelegramTextGroupDispatchRuntime<
327
+ TMessage,
328
+ TContext
329
+ >({
330
+ textGroups: deps.textGroupRuntime,
331
+ dispatchMessages: commandOrPrompt.dispatchMessages,
332
+ dispatchSingleMessage: mediaDispatch.handleMessage,
333
+ });
324
334
  const editRuntime = Turns.createTelegramQueuedPromptEditRuntime<
325
335
  TMessage,
326
336
  TContext
@@ -343,7 +353,7 @@ export function createTelegramInboundRouteRuntime<
343
353
  answerCallbackQuery: deps.answerCallbackQuery,
344
354
  handleAuthorizedTelegramCallbackQuery: callbackHandler,
345
355
  sendTextReply: deps.sendTextReply,
346
- handleAuthorizedTelegramMessage: mediaDispatch.handleMessage,
356
+ handleAuthorizedTelegramMessage: textDispatch.handleMessage,
347
357
  handleAuthorizedTelegramEditedMessage: editRuntime.updateFromEditedMessage,
348
358
  });
349
359
  }